aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock23
-rw-r--r--Cargo.toml1
-rw-r--r--flake.nix1
-rw-r--r--src/eval/builtins.rs (renamed from src/builtins.rs)70
-rw-r--r--src/eval/mod.rs (renamed from src/eval.rs)172
-rw-r--r--src/lib.rs1
7 files changed, 211 insertions, 58 deletions
diff --git a/.gitignore b/.gitignore
index 4075fb2..3906c5c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
1target 1target
2result 2result
3.direnv
diff --git a/Cargo.lock b/Cargo.lock
index afa07fe..164709d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -49,6 +49,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
49checksum = "066fce287b1d4eafef758e89e09d724a24808a9196fe9756b8ca90e86d0719a2" 49checksum = "066fce287b1d4eafef758e89e09d724a24808a9196fe9756b8ca90e86d0719a2"
50 50
51[[package]] 51[[package]]
52name = "dissimilar"
53version = "1.0.9"
54source = "registry+https://github.com/rust-lang/crates.io-index"
55checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d"
56
57[[package]]
58name = "expect-test"
59version = "1.5.0"
60source = "registry+https://github.com/rust-lang/crates.io-index"
61checksum = "9e0be0a561335815e06dab7c62e50353134c796e7a6155402a64bcff66b6a5e0"
62dependencies = [
63 "dissimilar",
64 "once_cell",
65]
66
67[[package]]
52name = "la-arena" 68name = "la-arena"
53version = "0.3.1" 69version = "0.3.1"
54source = "registry+https://github.com/rust-lang/crates.io-index" 70source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -77,6 +93,12 @@ dependencies = [
77] 93]
78 94
79[[package]] 95[[package]]
96name = "once_cell"
97version = "1.20.2"
98source = "registry+https://github.com/rust-lang/crates.io-index"
99checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
100
101[[package]]
80name = "proc-macro2" 102name = "proc-macro2"
81version = "1.0.86" 103version = "1.0.86"
82source = "registry+https://github.com/rust-lang/crates.io-index" 104source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -159,6 +181,7 @@ name = "tbsp"
159version = "0.1.0" 181version = "0.1.0"
160dependencies = [ 182dependencies = [
161 "argh", 183 "argh",
184 "expect-test",
162 "la-arena", 185 "la-arena",
163 "nom", 186 "nom",
164 "regex", 187 "regex",
diff --git a/Cargo.toml b/Cargo.toml
index 5d43ae0..894ea00 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,3 +29,4 @@ python = ["tree-sitter-python"]
29rust = ["tree-sitter-rust"] 29rust = ["tree-sitter-rust"]
30 30
31[dev-dependencies] 31[dev-dependencies]
32expect-test = "1.5.0"
diff --git a/flake.nix b/flake.nix
index b27c441..531d73f 100644
--- a/flake.nix
+++ b/flake.nix
@@ -68,7 +68,6 @@
68 pkgs.rust-bin.nightly.latest.default 68 pkgs.rust-bin.nightly.latest.default
69 pkgs.rust-analyzer 69 pkgs.rust-analyzer
70 70
71 pkgs.mermaid-cli
72 ]; 71 ];
73 RUST_LOG = "info"; 72 RUST_LOG = "info";
74 RUST_BACKTRACE = 1; 73 RUST_BACKTRACE = 1;
diff --git a/src/builtins.rs b/src/eval/builtins.rs
index 0c9dbf3..7820a8e 100644
--- a/src/builtins.rs
+++ b/src/eval/builtins.rs
@@ -24,6 +24,7 @@ macro_rules! builtins {
24 24
25builtins! { 25builtins! {
26 print, 26 print,
27 println,
27 28
28 // string 29 // string
29 isupper, 30 isupper,
@@ -41,16 +42,27 @@ builtins! {
41 member, 42 member,
42 push, 43 push,
43 pop, 44 pop,
45 isempty,
44} 46}
45 47
46fn print(ctx: &mut Context, args: &[ast::Expr]) -> Result { 48fn print(ctx: &mut Context, args: &[ast::Expr]) -> Result {
47 for arg in args { 49 for arg in args {
48 let val = ctx.eval_expr(arg)?; 50 let val = ctx.eval_expr(arg)?;
49 print!("{val}"); 51 let mut default_stream =Box::new(std::io::stdout()) as Box<dyn std::io::Write> ;
52 let stream = ctx
53 .output_stream
54 .as_mut()
55 .unwrap_or(&mut default_stream);
56 write!(stream, "{val}").unwrap();
50 } 57 }
51 Ok(Value::Unit) 58 Ok(Value::Unit)
52} 59}
53 60
61fn println(ctx: &mut Context, args: &[ast::Expr]) -> Result {
62 print(ctx, args)?;
63 print(ctx, &[ast::Expr::Lit(ast::Literal::Str("\n".to_owned()))])
64}
65
54fn isupper(ctx: &mut Context, args: &[ast::Expr]) -> Result { 66fn isupper(ctx: &mut Context, args: &[ast::Expr]) -> Result {
55 Ok(ctx 67 Ok(ctx
56 .eval_expr(&get_args::<1>(args)?[0])? 68 .eval_expr(&get_args::<1>(args)?[0])?
@@ -169,10 +181,7 @@ fn push(ctx: &mut Context, args: &[ast::Expr]) -> Result {
169 l.push(element); 181 l.push(element);
170 Ok(Value::Unit) 182 Ok(Value::Unit)
171 } 183 }
172 _ => Err(Error::TypeMismatch { 184 _ => Err(v.ty().expected([ast::Type::List])),
173 expected: ast::Type::List,
174 got: v.ty().clone(),
175 }),
176 }) 185 })
177} 186}
178 187
@@ -189,16 +198,59 @@ fn pop(ctx: &mut Context, args: &[ast::Expr]) -> Result {
189 Value::List(l) => l 198 Value::List(l) => l
190 .pop() 199 .pop()
191 .ok_or_else(|| Error::ArrayOutOfBounds { idx: 0, len: 0 }), 200 .ok_or_else(|| Error::ArrayOutOfBounds { idx: 0, len: 0 }),
192 _ => Err(Error::TypeMismatch { 201 _ => Err(v.ty().expected([ast::Type::List])),
193 expected: ast::Type::List,
194 got: v.ty().clone(),
195 }),
196 }) 202 })
197} 203}
198 204
205fn isempty(ctx: &mut Context, args: &[ast::Expr]) -> Result {
206 let v = ctx.eval_expr(&get_args::<1>(args)?[0])?;
207 match v.ty() {
208 ast::Type::List => v
209 .as_list()
210 .unwrap()
211 .is_empty()
212 .wrap(Value::Boolean)
213 .wrap(Ok),
214 ast::Type::String => v
215 .as_str()
216 .unwrap()
217 .is_empty()
218 .wrap(Value::Boolean)
219 .wrap(Ok),
220 _ => Err(v.ty().expected([ast::Type::List])),
221 }
222}
223
199fn get_args<const N: usize>(args: &[ast::Expr]) -> std::result::Result<&[ast::Expr; N], Error> { 224fn get_args<const N: usize>(args: &[ast::Expr]) -> std::result::Result<&[ast::Expr; N], Error> {
200 args.try_into().map_err(|_| Error::IncorrectArgFormat { 225 args.try_into().map_err(|_| Error::IncorrectArgFormat {
201 wanted: N, 226 wanted: N,
202 got: args.len(), 227 got: args.len(),
203 }) 228 })
204} 229}
230
231#[cfg(test)]
232mod test {
233 use super::*;
234 use crate::{ast::*, eval::*};
235
236 #[test]
237 fn test_ts_builtins() {
238 let language = tree_sitter_python::language();
239 let mut ctx = Context::new(language).with_program(Program::new());
240
241 assert_eq!(
242 ctx.eval_block(&Block {
243 body: vec![Statement::decl(Type::List, "a", Expr::list([Expr::int(5)]),)]
244 }),
245 Ok(Value::Unit)
246 );
247 assert_eq!(
248 ctx.lookup(&String::from("a")).unwrap().clone(),
249 Variable {
250 ty: Type::List,
251 name: "a".to_owned(),
252 value: vec![5usize.into()].into(),
253 }
254 );
255 }
256}
diff --git a/src/eval.rs b/src/eval/mod.rs
index e9fbbf2..c4460c0 100644
--- a/src/eval.rs
+++ b/src/eval/mod.rs
@@ -1,7 +1,9 @@
1//! tree walking interpreter for tbsp 1//! tree walking interpreter for tbsp
2 2
3use crate::{ast, Wrap}; 3use crate::{ast, Wrap};
4use std::{collections::HashMap, fmt}; 4use std::{collections::HashMap, fmt, io};
5
6mod builtins;
5 7
6#[derive(Debug, PartialEq, Eq, Clone)] 8#[derive(Debug, PartialEq, Eq, Clone)]
7pub struct Variable { 9pub struct Variable {
@@ -15,19 +17,16 @@ impl Variable {
15 &self.value 17 &self.value
16 } 18 }
17 19
18 pub(crate) fn ty(&self) -> &ast::Type { 20 pub(crate) fn ty(&self) -> ast::Type {
19 &self.ty 21 self.ty
20 } 22 }
21 23
22 fn assign(&mut self, value: Value) -> Result { 24 fn assign(&mut self, value: Value) -> Result {
23 if self.ty() == &value.ty() { 25 if self.ty() == value.ty() {
24 self.value = value; 26 self.value = value;
25 Ok(self.value.clone()) 27 Ok(self.value.clone())
26 } else { 28 } else {
27 Err(Error::TypeMismatch { 29 Err(value.ty().expected([self.ty()]))
28 expected: self.ty().clone(),
29 got: value.ty(),
30 })
31 } 30 }
32 } 31 }
33 32
@@ -90,50 +89,35 @@ impl Value {
90 fn as_boolean(&self) -> std::result::Result<bool, Error> { 89 fn as_boolean(&self) -> std::result::Result<bool, Error> {
91 match self { 90 match self {
92 Self::Boolean(b) => Ok(*b), 91 Self::Boolean(b) => Ok(*b),
93 v => Err(Error::TypeMismatch { 92 v => Err(v.ty().expected([ast::Type::Boolean])),
94 expected: ast::Type::Boolean,
95 got: v.ty(),
96 }),
97 } 93 }
98 } 94 }
99 95
100 pub(crate) fn as_str(&self) -> std::result::Result<&str, Error> { 96 pub(crate) fn as_str(&self) -> std::result::Result<&str, Error> {
101 match self { 97 match self {
102 Self::String(s) => Ok(s.as_str()), 98 Self::String(s) => Ok(s.as_str()),
103 v => Err(Error::TypeMismatch { 99 v => Err(v.ty().expected([ast::Type::String])),
104 expected: ast::Type::String,
105 got: v.ty(),
106 }),
107 } 100 }
108 } 101 }
109 102
110 pub(crate) fn as_int(&self) -> std::result::Result<i128, Error> { 103 pub(crate) fn as_int(&self) -> std::result::Result<i128, Error> {
111 match self { 104 match self {
112 Self::Integer(i) => Ok(*i), 105 Self::Integer(i) => Ok(*i),
113 v => Err(Error::TypeMismatch { 106 v => Err(v.ty().expected([ast::Type::Integer])),
114 expected: ast::Type::Integer,
115 got: v.ty(),
116 }),
117 } 107 }
118 } 108 }
119 109
120 pub(crate) fn as_node(&self) -> std::result::Result<NodeId, Error> { 110 pub(crate) fn as_node(&self) -> std::result::Result<NodeId, Error> {
121 match self { 111 match self {
122 Self::Node(id) => Ok(*id), 112 Self::Node(id) => Ok(*id),
123 v => Err(Error::TypeMismatch { 113 v => Err(v.ty().expected([ast::Type::Node])),
124 expected: ast::Type::Node,
125 got: v.ty(),
126 }),
127 } 114 }
128 } 115 }
129 116
130 pub(crate) fn as_list(&self) -> std::result::Result<Vec<Value>, Error> { 117 pub(crate) fn as_list(&self) -> std::result::Result<Vec<Value>, Error> {
131 match self { 118 match self {
132 Self::List(values) => Ok(values.clone()), 119 Self::List(values) => Ok(values.clone()),
133 v => Err(Error::TypeMismatch { 120 v => Err(v.ty().expected([ast::Type::List])),
134 expected: ast::Type::List,
135 got: v.ty(),
136 }),
137 } 121 }
138 } 122 }
139 123
@@ -350,7 +334,7 @@ impl From<Vec<Value>> for Value {
350pub enum Error { 334pub enum Error {
351 FailedLookup(ast::Identifier), 335 FailedLookup(ast::Identifier),
352 TypeMismatch { 336 TypeMismatch {
353 expected: ast::Type, 337 expected: Vec<ast::Type>,
354 got: ast::Type, 338 got: ast::Type,
355 }, 339 },
356 UndefinedBinOp(ast::BinOp, ast::Type, ast::Type), 340 UndefinedBinOp(ast::BinOp, ast::Type, ast::Type),
@@ -376,9 +360,18 @@ pub enum Error {
376 CurrentNodeNotPresent, 360 CurrentNodeNotPresent,
377} 361}
378 362
363impl ast::Type {
364 pub fn expected<const N: usize>(self, expected: [Self; N]) -> Error {
365 Error::TypeMismatch {
366 expected: expected.to_vec(),
367 got: self,
368 }
369 }
370}
371
379pub type Result = std::result::Result<Value, Error>; 372pub type Result = std::result::Result<Value, Error>;
380 373
381pub struct Context { 374pub struct Context<'s> {
382 variables: HashMap<ast::Identifier, Variable>, 375 variables: HashMap<ast::Identifier, Variable>,
383 language: tree_sitter::Language, 376 language: tree_sitter::Language,
384 program: ast::Program, 377 program: ast::Program,
@@ -386,9 +379,10 @@ pub struct Context {
386 cursor: Option<tree_sitter::TreeCursor<'static>>, 379 cursor: Option<tree_sitter::TreeCursor<'static>>,
387 tree: Option<&'static tree_sitter::Tree>, 380 tree: Option<&'static tree_sitter::Tree>,
388 cache: HashMap<NodeId, tree_sitter::Node<'static>>, 381 cache: HashMap<NodeId, tree_sitter::Node<'static>>,
382 output_stream: Option<Box<dyn io::Write + 's>>,
389} 383}
390 384
391impl fmt::Debug for Context { 385impl<'s> fmt::Debug for Context<'s> {
392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 f.debug_struct("Context") 387 f.debug_struct("Context")
394 .field("variables", &self.variables) 388 .field("variables", &self.variables)
@@ -406,7 +400,7 @@ impl fmt::Debug for Context {
406 } 400 }
407} 401}
408 402
409impl Context { 403impl<'s> Context<'s> {
410 pub fn new(language: tree_sitter::Language) -> Self { 404 pub fn new(language: tree_sitter::Language) -> Self {
411 Self { 405 Self {
412 program: Default::default(), 406 program: Default::default(),
@@ -416,6 +410,7 @@ impl Context {
416 cursor: None, 410 cursor: None,
417 tree: None, 411 tree: None,
418 cache: HashMap::default(), 412 cache: HashMap::default(),
413 output_stream: Some(Box::new(io::stdout()) as Box<dyn io::Write + 's>),
419 } 414 }
420 } 415 }
421 416
@@ -469,6 +464,11 @@ impl Context {
469 self 464 self
470 } 465 }
471 466
467 pub fn with_output_stream(mut self, stream: Box<dyn io::Write + 's>) -> Self {
468 self.output_stream = Some(stream);
469 self
470 }
471
472 pub(crate) fn eval_expr(&mut self, expr: &ast::Expr) -> Result { 472 pub(crate) fn eval_expr(&mut self, expr: &ast::Expr) -> Result {
473 match expr { 473 match expr {
474 ast::Expr::Unit => Ok(Value::Unit), 474 ast::Expr::Unit => Ok(Value::Unit),
@@ -644,7 +644,7 @@ impl Context {
644 } 644 }
645 645
646 fn eval_call(&mut self, call: &ast::Call) -> Result { 646 fn eval_call(&mut self, call: &ast::Call) -> Result {
647 ((&*crate::builtins::BUILTINS) 647 ((&*builtins::BUILTINS)
648 .get(call.function.as_str()) 648 .get(call.function.as_str())
649 .ok_or_else(|| Error::FailedLookup(call.function.to_owned()))?)( 649 .ok_or_else(|| Error::FailedLookup(call.function.to_owned()))?)(
650 self, 650 self,
@@ -779,6 +779,7 @@ pub fn evaluate(file: &str, program: &str, language: tree_sitter::Language) -> R
779 let mut ctx = Context::new(language) 779 let mut ctx = Context::new(language)
780 .with_input(file.to_owned()) 780 .with_input(file.to_owned())
781 .with_tree(tree) 781 .with_tree(tree)
782 .with_output_stream(Box::new(io::stdout()))
782 .with_program(program); 783 .with_program(program);
783 784
784 ctx.eval() 785 ctx.eval()
@@ -788,6 +789,28 @@ pub fn evaluate(file: &str, program: &str, language: tree_sitter::Language) -> R
788mod test { 789mod test {
789 use super::*; 790 use super::*;
790 use crate::ast::*; 791 use crate::ast::*;
792 use expect_test::{expect, Expect};
793
794 fn gen_test(file: &str, program: &str, expected: Expect) {
795 let language = tree_sitter_python::language();
796 let mut parser = tree_sitter::Parser::new();
797 let _ = parser.set_language(&language);
798 let tree = parser.parse(file, None).unwrap();
799 let program = ast::Program::new().from_str(program).unwrap();
800
801 let mut output = Vec::new();
802 let mut ctx = Context::new(language)
803 .with_input(file.to_owned())
804 .with_tree(tree)
805 .with_program(program)
806 .with_output_stream(Box::new(&mut output) as Box<dyn io::Write>);
807
808 ctx.eval().unwrap();
809
810 drop(ctx);
811
812 expected.assert_eq(&String::from_utf8(output).unwrap())
813 }
791 814
792 #[test] 815 #[test]
793 fn bin() { 816 fn bin() {
@@ -952,22 +975,77 @@ mod test {
952 } 975 }
953 976
954 #[test] 977 #[test]
955 fn test_ts_builtins() { 978 fn list_builtins() {
956 let language = tree_sitter_python::language(); 979 gen_test(
957 let mut ctx = Context::new(language).with_program(Program::new()); 980 "",
958 assert_eq!( 981 "BEGIN {
959 ctx.eval_block(&Block { 982 list a = [5];
960 body: vec![Statement::decl(Type::List, "a", Expr::list([Expr::int(5)]),)] 983 print(a);
961 }), 984 }
962 Ok(Value::Unit) 985 ",
986 expect!["[5]"]
963 ); 987 );
964 assert_eq!( 988
965 ctx.lookup(&String::from("a")).unwrap().clone(), 989 gen_test(
966 Variable { 990 "",
967 ty: Type::List, 991 "BEGIN {
968 name: "a".to_owned(), 992 list a = [5, 4, 3];
969 value: vec![5usize.into()].into(), 993 print(length(a));
994 }
995 ",
996 expect!["3"]
997 );
998
999 gen_test(
1000 "",
1001 r#"BEGIN {
1002 list a = [5, 4, 3];
1003 print(member(a, 3));
1004 print(", ");
1005 print(member(a, 6));
970 } 1006 }
1007 "#,
1008 expect!["true, false"]
971 ); 1009 );
1010
1011 gen_test(
1012 "",
1013 r#"BEGIN {
1014 list a = [5];
1015 println(a);
1016 push(a, 4);
1017 println(a);
1018 push(a, 3);
1019 println(a);
1020 pop(a);
1021 println(a);
1022 pop(a);
1023 println(a);
1024 }
1025 "#,
1026 expect![[r#"
1027 [5]
1028 [5, 4]
1029 [5, 4, 3]
1030 [5, 4]
1031 [5]
1032 "#]]
1033 );
1034
1035 gen_test(
1036 "",
1037 r#"BEGIN {
1038 list a = [5];
1039 println(isempty(a));
1040 pop(a);
1041 println(isempty(a));
1042 }
1043 "#,
1044 expect![[r#"
1045 false
1046 true
1047 "#]]
1048 );
1049
972 } 1050 }
973} 1051}
diff --git a/src/lib.rs b/src/lib.rs
index afce26c..ec4e2d9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,4 @@
1mod ast; 1mod ast;
2mod builtins;
3mod eval; 2mod eval;
4mod parser; 3mod parser;
5mod string; 4mod string;