diff options
author | Akshay <[email protected]> | 2024-09-08 21:43:32 +0100 |
---|---|---|
committer | Akshay <[email protected]> | 2024-09-08 21:43:32 +0100 |
commit | b8308b51b0f4a95f9e211506a8bc61fd04d2edab (patch) | |
tree | ad13a5543c388d945b45202cdc74173ce1a96116 | |
parent | 1702f955a4546828cd535be6cecad57b90128de8 (diff) |
simplify builtins, more list features
-rw-r--r-- | flake.lock | 69 | ||||
-rw-r--r-- | flake.nix | 63 | ||||
-rw-r--r-- | src/ast.rs | 78 | ||||
-rw-r--r-- | src/builtins.rs | 214 | ||||
-rw-r--r-- | src/eval.rs | 282 | ||||
-rw-r--r-- | src/lib.rs | 3 |
6 files changed, 528 insertions, 181 deletions
diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..261d530 --- /dev/null +++ b/flake.lock | |||
@@ -0,0 +1,69 @@ | |||
1 | { | ||
2 | "nodes": { | ||
3 | "gitignore": { | ||
4 | "inputs": { | ||
5 | "nixpkgs": [ | ||
6 | "nixpkgs" | ||
7 | ] | ||
8 | }, | ||
9 | "locked": { | ||
10 | "lastModified": 1709087332, | ||
11 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", | ||
12 | "owner": "hercules-ci", | ||
13 | "repo": "gitignore.nix", | ||
14 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", | ||
15 | "type": "github" | ||
16 | }, | ||
17 | "original": { | ||
18 | "owner": "hercules-ci", | ||
19 | "repo": "gitignore.nix", | ||
20 | "type": "github" | ||
21 | } | ||
22 | }, | ||
23 | "nixpkgs": { | ||
24 | "locked": { | ||
25 | "lastModified": 1724748588, | ||
26 | "narHash": "sha256-NlpGA4+AIf1dKNq76ps90rxowlFXUsV9x7vK/mN37JM=", | ||
27 | "owner": "nixos", | ||
28 | "repo": "nixpkgs", | ||
29 | "rev": "a6292e34000dc93d43bccf78338770c1c5ec8a99", | ||
30 | "type": "github" | ||
31 | }, | ||
32 | "original": { | ||
33 | "owner": "nixos", | ||
34 | "ref": "nixpkgs-unstable", | ||
35 | "repo": "nixpkgs", | ||
36 | "type": "github" | ||
37 | } | ||
38 | }, | ||
39 | "root": { | ||
40 | "inputs": { | ||
41 | "gitignore": "gitignore", | ||
42 | "nixpkgs": "nixpkgs", | ||
43 | "rust-overlay": "rust-overlay" | ||
44 | } | ||
45 | }, | ||
46 | "rust-overlay": { | ||
47 | "inputs": { | ||
48 | "nixpkgs": [ | ||
49 | "nixpkgs" | ||
50 | ] | ||
51 | }, | ||
52 | "locked": { | ||
53 | "lastModified": 1724811750, | ||
54 | "narHash": "sha256-PvhVgQ1rm3gfhK7ts4emprhh/KMkFwXogmgsQ3srR7g=", | ||
55 | "owner": "oxalica", | ||
56 | "repo": "rust-overlay", | ||
57 | "rev": "6a1c4915dca7149e7258d8c7f3ac634d8c65f6c6", | ||
58 | "type": "github" | ||
59 | }, | ||
60 | "original": { | ||
61 | "owner": "oxalica", | ||
62 | "repo": "rust-overlay", | ||
63 | "type": "github" | ||
64 | } | ||
65 | } | ||
66 | }, | ||
67 | "root": "root", | ||
68 | "version": 7 | ||
69 | } | ||
diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..4f69fe8 --- /dev/null +++ b/flake.nix | |||
@@ -0,0 +1,63 @@ | |||
1 | { | ||
2 | inputs = { | ||
3 | |||
4 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; | ||
5 | |||
6 | gitignore = { | ||
7 | url = "github:hercules-ci/gitignore.nix"; | ||
8 | inputs.nixpkgs.follows = "nixpkgs"; | ||
9 | }; | ||
10 | |||
11 | rust-overlay = { | ||
12 | url = "github:oxalica/rust-overlay"; | ||
13 | inputs.nixpkgs.follows = "nixpkgs"; | ||
14 | }; | ||
15 | |||
16 | }; | ||
17 | |||
18 | outputs = | ||
19 | { self | ||
20 | , nixpkgs | ||
21 | , gitignore | ||
22 | , rust-overlay | ||
23 | }: | ||
24 | let | ||
25 | inherit (gitignore.lib) gitignoreSource; | ||
26 | |||
27 | supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; | ||
28 | forAllSystems = nixpkgs.lib.genAttrs supportedSystems; | ||
29 | nixpkgsFor = forAllSystems (system: | ||
30 | import nixpkgs { | ||
31 | inherit system; | ||
32 | overlays = [rust-overlay.overlays.default]; | ||
33 | }); | ||
34 | |||
35 | in | ||
36 | { | ||
37 | |||
38 | devShell = forAllSystems (system: | ||
39 | let | ||
40 | pkgs = nixpkgsFor."${system}"; | ||
41 | in | ||
42 | pkgs.mkShell { | ||
43 | nativeBuildInputs = [ | ||
44 | pkgs.cargo-watch | ||
45 | pkgs.bacon | ||
46 | pkgs.rustfmt | ||
47 | pkgs.cargo | ||
48 | |||
49 | pkgs.rust-bin.nightly.latest.default | ||
50 | pkgs.rust-bin.nightly.latest.rust-analyzer | ||
51 | pkgs.lld | ||
52 | pkgs.trunk | ||
53 | |||
54 | |||
55 | pkgs.mermaid-cli | ||
56 | ]; | ||
57 | RUST_LOG = "info"; | ||
58 | RUST_BACKTRACE = 1; | ||
59 | }); | ||
60 | }; | ||
61 | } | ||
62 | |||
63 | |||
@@ -56,6 +56,17 @@ pub enum Statement { | |||
56 | Declaration(Declaration), | 56 | Declaration(Declaration), |
57 | } | 57 | } |
58 | 58 | ||
59 | impl Statement { | ||
60 | #[cfg(test)] | ||
61 | pub fn decl(ty: Type, ident: &str, init: Expr) -> Self { | ||
62 | Self::Declaration(Declaration { | ||
63 | ty, | ||
64 | name: ident.to_owned(), | ||
65 | init: Some(init.boxed()), | ||
66 | }) | ||
67 | } | ||
68 | } | ||
69 | |||
59 | #[derive(Debug, Eq, PartialEq, Clone)] | 70 | #[derive(Debug, Eq, PartialEq, Clone)] |
60 | pub enum Expr { | 71 | pub enum Expr { |
61 | Node, | 72 | Node, |
@@ -77,11 +88,30 @@ impl Expr { | |||
77 | Box::new(self) | 88 | Box::new(self) |
78 | } | 89 | } |
79 | 90 | ||
80 | pub fn as_ident(self) -> Option<Identifier> { | 91 | #[cfg(test)] |
81 | match self { | 92 | pub fn bin(lhs: Expr, op: &str, rhs: Expr) -> Expr { |
82 | Self::Ident(i) => Some(i), | 93 | use std::str::FromStr; |
83 | _ => None, | 94 | Self::Bin(lhs.boxed(), BinOp::from_str(op).unwrap(), rhs.boxed()) |
84 | } | 95 | } |
96 | |||
97 | #[cfg(test)] | ||
98 | pub fn ident(i: &str) -> Expr { | ||
99 | Self::Ident(i.to_owned()) | ||
100 | } | ||
101 | |||
102 | #[cfg(test)] | ||
103 | pub fn call<const N: usize>(fun: &str, params: [Expr; N]) -> Expr { | ||
104 | Self::Call(Call { | ||
105 | function: fun.to_owned(), | ||
106 | parameters: params.to_vec(), | ||
107 | }) | ||
108 | } | ||
109 | |||
110 | #[cfg(test)] | ||
111 | pub fn list<const N: usize>(items: [Expr; N]) -> Expr { | ||
112 | Self::List(List { | ||
113 | items: items.to_vec() | ||
114 | }) | ||
85 | } | 115 | } |
86 | } | 116 | } |
87 | 117 | ||
@@ -99,6 +129,44 @@ pub enum BinOp { | |||
99 | Assign(AssignOp), | 129 | Assign(AssignOp), |
100 | } | 130 | } |
101 | 131 | ||
132 | impl std::str::FromStr for BinOp { | ||
133 | type Err = (); | ||
134 | fn from_str(val: &str) -> Result<Self, Self::Err> { | ||
135 | match val { | ||
136 | "+" => Ok(Self::Arith(ArithOp::Add)), | ||
137 | "-" => Ok(Self::Arith(ArithOp::Sub)), | ||
138 | "*" => Ok(Self::Arith(ArithOp::Mul)), | ||
139 | "/" => Ok(Self::Arith(ArithOp::Div)), | ||
140 | "%" => Ok(Self::Arith(ArithOp::Mod)), | ||
141 | ">" => Ok(Self::Cmp(CmpOp::Gt)), | ||
142 | "<" => Ok(Self::Cmp(CmpOp::Lt)), | ||
143 | ">=" => Ok(Self::Cmp(CmpOp::Gte)), | ||
144 | "<=" => Ok(Self::Cmp(CmpOp::Lte)), | ||
145 | "==" => Ok(Self::Cmp(CmpOp::Eq)), | ||
146 | "!=" => Ok(Self::Cmp(CmpOp::Neq)), | ||
147 | "&&" => Ok(Self::Logic(LogicOp::And)), | ||
148 | "||" => Ok(Self::Logic(LogicOp::Or)), | ||
149 | "=" => Ok(Self::Assign(AssignOp { op: None })), | ||
150 | "+=" => Ok(Self::Assign(AssignOp { | ||
151 | op: Some(ArithOp::Add), | ||
152 | })), | ||
153 | "-=" => Ok(Self::Assign(AssignOp { | ||
154 | op: Some(ArithOp::Sub), | ||
155 | })), | ||
156 | "*=" => Ok(Self::Assign(AssignOp { | ||
157 | op: Some(ArithOp::Mul), | ||
158 | })), | ||
159 | "/=" => Ok(Self::Assign(AssignOp { | ||
160 | op: Some(ArithOp::Div), | ||
161 | })), | ||
162 | "%=" => Ok(Self::Assign(AssignOp { | ||
163 | op: Some(ArithOp::Mod), | ||
164 | })), | ||
165 | _ => Err(()), | ||
166 | } | ||
167 | } | ||
168 | } | ||
169 | |||
102 | // + - * / | 170 | // + - * / |
103 | #[derive(Debug, Eq, PartialEq, Clone, Copy)] | 171 | #[derive(Debug, Eq, PartialEq, Clone, Copy)] |
104 | pub enum ArithOp { | 172 | pub enum ArithOp { |
diff --git a/src/builtins.rs b/src/builtins.rs new file mode 100644 index 0000000..8f15603 --- /dev/null +++ b/src/builtins.rs | |||
@@ -0,0 +1,214 @@ | |||
1 | use std::{collections::HashMap, sync::LazyLock}; | ||
2 | |||
3 | use crate::{ | ||
4 | ast, | ||
5 | eval::{Context, Error, Result, Value}, | ||
6 | Wrap | ||
7 | }; | ||
8 | |||
9 | pub static BUILTINS: LazyLock<HashMap<&'static str, Box<dyn Builtin + Sync + Send>>> = | ||
10 | LazyLock::new(|| { | ||
11 | [ | ||
12 | Print.boxed(), | ||
13 | IsUpper.boxed(), | ||
14 | IsLower.boxed(), | ||
15 | Substr.boxed(), | ||
16 | Text.boxed(), | ||
17 | Parent.boxed(), | ||
18 | Children.boxed(), | ||
19 | Length.boxed(), | ||
20 | Kind.boxed(), | ||
21 | ] | ||
22 | .into_iter() | ||
23 | .map(|b| (b.id(), b)) | ||
24 | .collect() | ||
25 | }); | ||
26 | |||
27 | pub(crate) trait Builtin | ||
28 | where | ||
29 | Self: 'static, | ||
30 | { | ||
31 | /// Name of this function | ||
32 | fn id(&self) -> &'static str; | ||
33 | |||
34 | /// Function description | ||
35 | fn eval(&self, ctx: &mut Context, args: &[ast::Expr]) -> Result; | ||
36 | |||
37 | /// Anything that is Builtin can be turned into a trait object | ||
38 | fn boxed(self) -> Box<dyn Builtin + Sync + Send> | ||
39 | where | ||
40 | Self: Sized + Sync + Send, | ||
41 | { | ||
42 | Box::new(self) as Box<dyn Builtin + Sync + Send> | ||
43 | } | ||
44 | } | ||
45 | |||
46 | fn get_args<const N: usize>(args: &[ast::Expr]) -> std::result::Result<&[ast::Expr; N], Error> { | ||
47 | args.try_into().map_err(|_| Error::IncorrectArgFormat { | ||
48 | wanted: N, | ||
49 | got: args.len(), | ||
50 | }) | ||
51 | } | ||
52 | |||
53 | struct Print; | ||
54 | impl Builtin for Print { | ||
55 | fn id(&self) -> &'static str { | ||
56 | "print" | ||
57 | } | ||
58 | |||
59 | fn eval(&self, ctx: &mut Context, args: &[ast::Expr]) -> Result { | ||
60 | for arg in args { | ||
61 | let val = ctx.eval_expr(arg)?; | ||
62 | print!("{val}"); | ||
63 | } | ||
64 | Ok(Value::Unit) | ||
65 | } | ||
66 | } | ||
67 | |||
68 | struct IsUpper; | ||
69 | impl Builtin for IsUpper { | ||
70 | fn id(&self) -> &'static str { | ||
71 | "isupper" | ||
72 | } | ||
73 | |||
74 | fn eval(&self, ctx: &mut Context, args: &[ast::Expr]) -> Result { | ||
75 | Ok(ctx | ||
76 | .eval_expr(&get_args::<1>(args)?[0])? | ||
77 | .as_str()? | ||
78 | .chars() | ||
79 | .all(|c| c.is_ascii_uppercase()) | ||
80 | .into()) | ||
81 | } | ||
82 | } | ||
83 | |||
84 | struct IsLower; | ||
85 | impl Builtin for IsLower { | ||
86 | fn id(&self) -> &'static str { | ||
87 | "islower" | ||
88 | } | ||
89 | |||
90 | fn eval(&self, ctx: &mut Context, args: &[ast::Expr]) -> Result { | ||
91 | Ok(ctx | ||
92 | .eval_expr(&get_args::<1>(args)?[0])? | ||
93 | .as_str()? | ||
94 | .chars() | ||
95 | .all(|c| c.is_ascii_lowercase()) | ||
96 | .into()) | ||
97 | } | ||
98 | } | ||
99 | |||
100 | struct Substr; | ||
101 | impl Builtin for Substr { | ||
102 | fn id(&self) -> &'static str { | ||
103 | "substr" | ||
104 | } | ||
105 | |||
106 | fn eval(&self, ctx: &mut Context, args: &[ast::Expr]) -> Result { | ||
107 | if let Ok([string, start, end]) = get_args::<3>(args) { | ||
108 | let v = ctx.eval_expr(string)?; | ||
109 | let s = v.as_str()?; | ||
110 | let start = ctx.eval_expr(start)?.as_int()?; | ||
111 | let end = ctx.eval_expr(end)?.as_int()?; | ||
112 | if start < 0 || start >= s.len() as i128 || end >= s.len() as i128 || start > end { | ||
113 | return Err(Error::InvalidStringSlice { | ||
114 | length: s.len(), | ||
115 | start, | ||
116 | end, | ||
117 | }); | ||
118 | } | ||
119 | Ok(s[start as usize..end as usize].into()) | ||
120 | } else { | ||
121 | let [string, end] = get_args::<2>(args)?; | ||
122 | let v = ctx.eval_expr(string)?; | ||
123 | let s = v.as_str()?; | ||
124 | let end = ctx.eval_expr(end)?.as_int()?; | ||
125 | if end >= s.len() as i128 { | ||
126 | return Err(Error::InvalidStringSlice { | ||
127 | length: s.len(), | ||
128 | start: 0, | ||
129 | end, | ||
130 | }); | ||
131 | } | ||
132 | Ok(s[..end as usize].into()) | ||
133 | } | ||
134 | } | ||
135 | } | ||
136 | |||
137 | struct Text; | ||
138 | impl Builtin for Text { | ||
139 | fn id(&self) -> &'static str { | ||
140 | "text" | ||
141 | } | ||
142 | |||
143 | fn eval(&self, ctx: &mut Context, args: &[ast::Expr]) -> Result { | ||
144 | let v = ctx.eval_expr(&get_args::<1>(args)?[0])?; | ||
145 | let id = v.as_node()?; | ||
146 | let node = ctx.get_node_by_id(id).unwrap(); | ||
147 | let text = node | ||
148 | .utf8_text(ctx.input_src.as_ref().unwrap().as_bytes()) | ||
149 | .unwrap(); | ||
150 | text.to_owned().wrap(Value::String).wrap(Ok) | ||
151 | } | ||
152 | } | ||
153 | |||
154 | struct Parent; | ||
155 | impl Builtin for Parent { | ||
156 | fn id(&self) -> &'static str { | ||
157 | "parent" | ||
158 | } | ||
159 | |||
160 | fn eval(&self, ctx: &mut Context, args: &[ast::Expr]) -> Result { | ||
161 | let v = ctx.eval_expr(&get_args::<1>(args)?[0])?; | ||
162 | let id = v.as_node()?; | ||
163 | let node = ctx.get_node_by_id(id).unwrap(); | ||
164 | let parent = node.parent(); | ||
165 | parent | ||
166 | .map(|n| Value::Node(n.id())) | ||
167 | .ok_or(Error::NoParentNode(node)) | ||
168 | } | ||
169 | } | ||
170 | |||
171 | struct Children; | ||
172 | impl Builtin for Children { | ||
173 | fn id(&self) -> &'static str { | ||
174 | "children" | ||
175 | } | ||
176 | |||
177 | fn eval(&self, ctx: &mut Context, args: &[ast::Expr]) -> Result { | ||
178 | let v = ctx.eval_expr(&get_args::<1>(args)?[0])?; | ||
179 | let id = v.as_node()?; | ||
180 | let node = ctx.get_node_by_id(id).unwrap(); | ||
181 | let children = node | ||
182 | .children(&mut node.walk()) | ||
183 | .map(|c| Value::Node(c.id())) | ||
184 | .collect::<Vec<_>>(); | ||
185 | Ok(Value::List(children)) | ||
186 | } | ||
187 | } | ||
188 | |||
189 | struct Length; | ||
190 | impl Builtin for Length { | ||
191 | fn id(&self) -> &'static str { | ||
192 | "length" | ||
193 | } | ||
194 | |||
195 | fn eval(&self, ctx: &mut Context, args: &[ast::Expr]) -> Result { | ||
196 | let v = ctx.eval_expr(&get_args::<1>(args)?[0])?; | ||
197 | let l = v.as_list()?; | ||
198 | (l.len() as i128).wrap(Value::Integer).wrap(Ok) | ||
199 | } | ||
200 | } | ||
201 | |||
202 | struct Kind; | ||
203 | impl Builtin for Kind { | ||
204 | fn id(&self) -> &'static str { | ||
205 | "kind" | ||
206 | } | ||
207 | |||
208 | fn eval(&self, ctx: &mut Context, args: &[ast::Expr]) -> Result { | ||
209 | let v = ctx.eval_expr(&get_args::<1>(args)?[0])?; | ||
210 | let id = v.as_node()?; | ||
211 | let node = ctx.get_node_by_id(id).unwrap(); | ||
212 | node.kind().to_owned().wrap(Value::String).wrap(Ok) | ||
213 | } | ||
214 | } | ||
diff --git a/src/eval.rs b/src/eval.rs index 104571b..35cba82 100644 --- a/src/eval.rs +++ b/src/eval.rs | |||
@@ -93,7 +93,7 @@ impl Value { | |||
93 | } | 93 | } |
94 | } | 94 | } |
95 | 95 | ||
96 | fn as_str(&self) -> std::result::Result<&str, Error> { | 96 | pub(crate) fn as_str(&self) -> std::result::Result<&str, Error> { |
97 | match self { | 97 | match self { |
98 | Self::String(s) => Ok(s.as_str()), | 98 | Self::String(s) => Ok(s.as_str()), |
99 | v => Err(Error::TypeMismatch { | 99 | v => Err(Error::TypeMismatch { |
@@ -103,7 +103,7 @@ impl Value { | |||
103 | } | 103 | } |
104 | } | 104 | } |
105 | 105 | ||
106 | fn as_int(&self) -> std::result::Result<i128, Error> { | 106 | pub(crate) fn as_int(&self) -> std::result::Result<i128, Error> { |
107 | match self { | 107 | match self { |
108 | Self::Integer(i) => Ok(*i), | 108 | Self::Integer(i) => Ok(*i), |
109 | v => Err(Error::TypeMismatch { | 109 | v => Err(Error::TypeMismatch { |
@@ -113,7 +113,7 @@ impl Value { | |||
113 | } | 113 | } |
114 | } | 114 | } |
115 | 115 | ||
116 | fn as_node(&self) -> std::result::Result<NodeId, Error> { | 116 | pub(crate) fn as_node(&self) -> std::result::Result<NodeId, Error> { |
117 | match self { | 117 | match self { |
118 | Self::Node(id) => Ok(*id), | 118 | Self::Node(id) => Ok(*id), |
119 | v => Err(Error::TypeMismatch { | 119 | v => Err(Error::TypeMismatch { |
@@ -123,7 +123,7 @@ impl Value { | |||
123 | } | 123 | } |
124 | } | 124 | } |
125 | 125 | ||
126 | fn as_list(&self) -> std::result::Result<Vec<Value>, Error> { | 126 | pub(crate) fn as_list(&self) -> std::result::Result<Vec<Value>, Error> { |
127 | match self { | 127 | match self { |
128 | Self::List(values) => Ok(values.clone()), | 128 | Self::List(values) => Ok(values.clone()), |
129 | v => Err(Error::TypeMismatch { | 129 | v => Err(Error::TypeMismatch { |
@@ -137,6 +137,7 @@ impl Value { | |||
137 | match (self, other) { | 137 | match (self, other) { |
138 | (Self::Integer(s), Self::Integer(o)) => Ok(Self::Integer(*s + *o)), | 138 | (Self::Integer(s), Self::Integer(o)) => Ok(Self::Integer(*s + *o)), |
139 | (Self::String(s), Self::String(o)) => Ok(Self::String(format!("{s}{o}"))), | 139 | (Self::String(s), Self::String(o)) => Ok(Self::String(format!("{s}{o}"))), |
140 | (Self::List(l), o) => Ok(Self::List(l.iter().cloned().chain([o.clone()]).collect())), | ||
140 | _ => Err(Error::UndefinedBinOp( | 141 | _ => Err(Error::UndefinedBinOp( |
141 | ast::BinOp::Arith(ast::ArithOp::Add), | 142 | ast::BinOp::Arith(ast::ArithOp::Add), |
142 | self.ty(), | 143 | self.ty(), |
@@ -297,8 +298,13 @@ impl fmt::Display for Value { | |||
297 | Self::Node(id) => write!(f, "<node #{id}>"), | 298 | Self::Node(id) => write!(f, "<node #{id}>"), |
298 | Self::List(items) => { | 299 | Self::List(items) => { |
299 | write!(f, "[")?; | 300 | write!(f, "[")?; |
300 | for i in items { | 301 | let mut iterable = items.iter().peekable(); |
301 | write!(f, "{i}")?; | 302 | while let Some(item) = iterable.next() { |
303 | if iterable.peek().is_none() { | ||
304 | write!(f, "{item}")?; | ||
305 | } else { | ||
306 | write!(f, "{item}, ")?; | ||
307 | } | ||
302 | } | 308 | } |
303 | write!(f, "]") | 309 | write!(f, "]") |
304 | } | 310 | } |
@@ -318,6 +324,12 @@ impl From<i128> for Value { | |||
318 | } | 324 | } |
319 | } | 325 | } |
320 | 326 | ||
327 | impl From<usize> for Value { | ||
328 | fn from(value: usize) -> Self { | ||
329 | (value as i128).into() | ||
330 | } | ||
331 | } | ||
332 | |||
321 | impl From<&str> for Value { | 333 | impl From<&str> for Value { |
322 | fn from(value: &str) -> Self { | 334 | fn from(value: &str) -> Self { |
323 | Self::String(value.to_owned()) | 335 | Self::String(value.to_owned()) |
@@ -402,6 +414,10 @@ pub enum Error { | |||
402 | MalformedExpr(String), | 414 | MalformedExpr(String), |
403 | InvalidNodeKind(String), | 415 | InvalidNodeKind(String), |
404 | NoParentNode(tree_sitter::Node<'static>), | 416 | NoParentNode(tree_sitter::Node<'static>), |
417 | IncorrectArgFormat { | ||
418 | wanted: usize, | ||
419 | got: usize, | ||
420 | }, | ||
405 | InvalidStringSlice { | 421 | InvalidStringSlice { |
406 | length: usize, | 422 | length: usize, |
407 | start: i128, | 423 | start: i128, |
@@ -409,7 +425,7 @@ pub enum Error { | |||
409 | }, | 425 | }, |
410 | ArrayOutOfBounds { | 426 | ArrayOutOfBounds { |
411 | idx: i128, | 427 | idx: i128, |
412 | len: usize | 428 | len: usize, |
413 | }, | 429 | }, |
414 | // current node is only set in visitors, not in BEGIN or END blocks | 430 | // current node is only set in visitors, not in BEGIN or END blocks |
415 | CurrentNodeNotPresent, | 431 | CurrentNodeNotPresent, |
@@ -421,7 +437,7 @@ pub struct Context { | |||
421 | variables: HashMap<ast::Identifier, Variable>, | 437 | variables: HashMap<ast::Identifier, Variable>, |
422 | language: tree_sitter::Language, | 438 | language: tree_sitter::Language, |
423 | visitors: Visitors, | 439 | visitors: Visitors, |
424 | input_src: Option<String>, | 440 | pub(crate) input_src: Option<String>, |
425 | cursor: Option<tree_sitter::TreeCursor<'static>>, | 441 | cursor: Option<tree_sitter::TreeCursor<'static>>, |
426 | tree: Option<&'static tree_sitter::Tree>, | 442 | tree: Option<&'static tree_sitter::Tree>, |
427 | cache: HashMap<NodeId, tree_sitter::Node<'static>>, | 443 | cache: HashMap<NodeId, tree_sitter::Node<'static>>, |
@@ -511,7 +527,7 @@ impl Context { | |||
511 | self | 527 | self |
512 | } | 528 | } |
513 | 529 | ||
514 | fn eval_expr(&mut self, expr: &ast::Expr) -> Result { | 530 | pub(crate) fn eval_expr(&mut self, expr: &ast::Expr) -> Result { |
515 | match expr { | 531 | match expr { |
516 | ast::Expr::Unit => Ok(Value::Unit), | 532 | ast::Expr::Unit => Ok(Value::Unit), |
517 | ast::Expr::Lit(lit) => self.eval_lit(lit), | 533 | ast::Expr::Lit(lit) => self.eval_lit(lit), |
@@ -680,78 +696,10 @@ impl Context { | |||
680 | } | 696 | } |
681 | 697 | ||
682 | fn eval_call(&mut self, call: &ast::Call) -> Result { | 698 | fn eval_call(&mut self, call: &ast::Call) -> Result { |
683 | match (call.function.as_str(), call.parameters.as_slice()) { | 699 | (&*crate::builtins::BUILTINS) |
684 | ("print", args) => { | 700 | .get(call.function.as_str()) |
685 | for arg in args { | 701 | .ok_or_else(|| Error::FailedLookup(call.function.to_owned()))? |
686 | let val = self.eval_expr(arg)?; | 702 | .eval(self, call.parameters.as_slice()) |
687 | print!("{val}"); | ||
688 | } | ||
689 | Ok(Value::Unit) | ||
690 | } | ||
691 | (predicate @ ("isupper" | "islower"), [arg]) => Ok(self | ||
692 | .eval_expr(arg)? | ||
693 | .as_str()? | ||
694 | .chars() | ||
695 | .all(|c| match predicate { | ||
696 | "isupper" => c.is_ascii_uppercase(), | ||
697 | "islower" => c.is_ascii_lowercase(), | ||
698 | _ => unreachable!(), | ||
699 | }) | ||
700 | .into()), | ||
701 | ("substr", [string, indices @ ..]) => { | ||
702 | let v = self.eval_expr(string)?; | ||
703 | let s = v.as_str()?; | ||
704 | match indices { | ||
705 | [start, end] => { | ||
706 | let start = self.eval_expr(start)?.as_int()?; | ||
707 | let end = self.eval_expr(end)?.as_int()?; | ||
708 | if start < 0 | ||
709 | || start >= s.len() as i128 | ||
710 | || end >= s.len() as i128 | ||
711 | || start > end | ||
712 | { | ||
713 | return Err(Error::InvalidStringSlice { | ||
714 | length: s.len(), | ||
715 | start, | ||
716 | end, | ||
717 | }); | ||
718 | } | ||
719 | Ok(s[start as usize..end as usize].into()) | ||
720 | } | ||
721 | [end] => { | ||
722 | let end = self.eval_expr(end)?.as_int()?; | ||
723 | if end >= s.len() as i128 { | ||
724 | return Err(Error::InvalidStringSlice { | ||
725 | length: s.len(), | ||
726 | start: 0, | ||
727 | end, | ||
728 | }); | ||
729 | } | ||
730 | Ok(s[..end as usize].into()) | ||
731 | } | ||
732 | _ => todo!(), | ||
733 | } | ||
734 | } | ||
735 | ("text", [arg]) => { | ||
736 | let v = self.eval_expr(arg)?; | ||
737 | let id = v.as_node()?; | ||
738 | let node = self.get_node_by_id(id).unwrap(); | ||
739 | let text = node | ||
740 | .utf8_text(self.input_src.as_ref().unwrap().as_bytes()) | ||
741 | .unwrap(); | ||
742 | Ok(Value::String(text.to_owned())) | ||
743 | } | ||
744 | ("parent", [arg]) => { | ||
745 | let v = self.eval_expr(arg)?; | ||
746 | let id = v.as_node()?; | ||
747 | let node = self.get_node_by_id(id).unwrap(); | ||
748 | let parent = node.parent(); | ||
749 | parent | ||
750 | .map(|n| Value::Node(n.id())) | ||
751 | .ok_or(Error::NoParentNode(node)) | ||
752 | } | ||
753 | (s, _) => Err(Error::FailedLookup(s.to_owned())), | ||
754 | } | ||
755 | } | 703 | } |
756 | 704 | ||
757 | fn eval_list(&mut self, list: &ast::List) -> Result { | 705 | fn eval_list(&mut self, list: &ast::List) -> Result { |
@@ -768,7 +716,7 @@ impl Context { | |||
768 | if idx < 0 || idx >= target.len() as i128 { | 716 | if idx < 0 || idx >= target.len() as i128 { |
769 | Err(Error::ArrayOutOfBounds { | 717 | Err(Error::ArrayOutOfBounds { |
770 | idx, | 718 | idx, |
771 | len: target.len() | 719 | len: target.len(), |
772 | }) | 720 | }) |
773 | } else { | 721 | } else { |
774 | Ok(target.remove(idx as usize)) | 722 | Ok(target.remove(idx as usize)) |
@@ -881,47 +829,29 @@ pub fn evaluate(file: &str, program: &str, language: tree_sitter::Language) -> R | |||
881 | #[cfg(test)] | 829 | #[cfg(test)] |
882 | mod test { | 830 | mod test { |
883 | use super::*; | 831 | use super::*; |
832 | use crate::ast::*; | ||
884 | 833 | ||
885 | #[test] | 834 | #[test] |
886 | fn bin() { | 835 | fn bin() { |
887 | let language = tree_sitter_python::language(); | 836 | let language = tree_sitter_python::language(); |
888 | let mut ctx = Context::new(language) | 837 | let mut ctx = Context::new(language).with_program(Program::new()).unwrap(); |
889 | .with_program(ast::Program::new()) | ||
890 | .unwrap(); | ||
891 | assert_eq!( | 838 | assert_eq!( |
892 | ctx.eval_expr(&ast::Expr::Bin( | 839 | ctx.eval_expr(&Expr::bin(Expr::int(5), "+", Expr::int(10),)), |
893 | ast::Expr::int(5).boxed(), | ||
894 | ast::BinOp::Arith(ast::ArithOp::Add), | ||
895 | ast::Expr::int(10).boxed(), | ||
896 | )), | ||
897 | Ok(Value::Integer(15)) | 840 | Ok(Value::Integer(15)) |
898 | ); | 841 | ); |
899 | assert_eq!( | 842 | assert_eq!( |
900 | ctx.eval_expr(&ast::Expr::Bin( | 843 | ctx.eval_expr(&Expr::bin(Expr::int(5), "==", Expr::int(10),)), |
901 | ast::Expr::int(5).boxed(), | ||
902 | ast::BinOp::Cmp(ast::CmpOp::Eq), | ||
903 | ast::Expr::int(10).boxed(), | ||
904 | )), | ||
905 | Ok(Value::Boolean(false)) | 844 | Ok(Value::Boolean(false)) |
906 | ); | 845 | ); |
907 | assert_eq!( | 846 | assert_eq!( |
908 | ctx.eval_expr(&ast::Expr::Bin( | 847 | ctx.eval_expr(&Expr::bin(Expr::int(5), "<", Expr::int(10),)), |
909 | ast::Expr::int(5).boxed(), | ||
910 | ast::BinOp::Cmp(ast::CmpOp::Lt), | ||
911 | ast::Expr::int(10).boxed(), | ||
912 | )), | ||
913 | Ok(Value::Boolean(true)) | 848 | Ok(Value::Boolean(true)) |
914 | ); | 849 | ); |
915 | assert_eq!( | 850 | assert_eq!( |
916 | ctx.eval_expr(&ast::Expr::Bin( | 851 | ctx.eval_expr(&Expr::bin( |
917 | ast::Expr::Bin( | 852 | Expr::bin(Expr::int(5), "<", Expr::int(10),), |
918 | ast::Expr::int(5).boxed(), | 853 | "&&", |
919 | ast::BinOp::Cmp(ast::CmpOp::Lt), | 854 | Expr::false_(), |
920 | ast::Expr::int(10).boxed(), | ||
921 | ) | ||
922 | .boxed(), | ||
923 | ast::BinOp::Logic(ast::LogicOp::And), | ||
924 | ast::Expr::false_().boxed() | ||
925 | )), | 855 | )), |
926 | Ok(Value::Boolean(false)) | 856 | Ok(Value::Boolean(false)) |
927 | ); | 857 | ); |
@@ -930,24 +860,16 @@ mod test { | |||
930 | #[test] | 860 | #[test] |
931 | fn test_evaluate_blocks() { | 861 | fn test_evaluate_blocks() { |
932 | let language = tree_sitter_python::language(); | 862 | let language = tree_sitter_python::language(); |
933 | let mut ctx = Context::new(language) | 863 | let mut ctx = Context::new(language).with_program(Program::new()).unwrap(); |
934 | .with_program(ast::Program::new()) | ||
935 | .unwrap(); | ||
936 | assert_eq!( | 864 | assert_eq!( |
937 | ctx.eval_block(&ast::Block { | 865 | ctx.eval_block(&Block { |
938 | body: vec![ | 866 | body: vec![ |
939 | ast::Statement::Declaration(ast::Declaration { | 867 | Statement::Declaration(Declaration { |
940 | ty: ast::Type::Integer, | 868 | ty: Type::Integer, |
941 | name: "a".to_owned(), | 869 | name: "a".to_owned(), |
942 | init: None, | 870 | init: None, |
943 | }), | 871 | }), |
944 | ast::Statement::Bare(ast::Expr::Bin( | 872 | Statement::Bare(Expr::bin(Expr::ident("a"), "+=", Expr::int(5),)), |
945 | ast::Expr::Ident("a".to_owned()).boxed(), | ||
946 | ast::BinOp::Assign(ast::AssignOp { | ||
947 | op: Some(ast::ArithOp::Add) | ||
948 | }), | ||
949 | ast::Expr::int(5).boxed() | ||
950 | )), | ||
951 | ] | 873 | ] |
952 | }), | 874 | }), |
953 | Ok(Value::Unit) | 875 | Ok(Value::Unit) |
@@ -955,7 +877,7 @@ mod test { | |||
955 | assert_eq!( | 877 | assert_eq!( |
956 | ctx.lookup(&String::from("a")).unwrap().clone(), | 878 | ctx.lookup(&String::from("a")).unwrap().clone(), |
957 | Variable { | 879 | Variable { |
958 | ty: ast::Type::Integer, | 880 | ty: Type::Integer, |
959 | name: "a".to_owned(), | 881 | name: "a".to_owned(), |
960 | value: Value::Integer(5) | 882 | value: Value::Integer(5) |
961 | } | 883 | } |
@@ -965,35 +887,29 @@ mod test { | |||
965 | #[test] | 887 | #[test] |
966 | fn test_evaluate_if() { | 888 | fn test_evaluate_if() { |
967 | let language = tree_sitter_python::language(); | 889 | let language = tree_sitter_python::language(); |
968 | let mut ctx = Context::new(language) | 890 | let mut ctx = Context::new(language).with_program(Program::new()).unwrap(); |
969 | .with_program(ast::Program::new()) | ||
970 | .unwrap(); | ||
971 | assert_eq!( | 891 | assert_eq!( |
972 | ctx.eval_block(&ast::Block { | 892 | ctx.eval_block(&Block { |
973 | body: vec![ | 893 | body: vec![ |
974 | ast::Statement::Declaration(ast::Declaration { | 894 | Statement::Declaration(Declaration { |
975 | ty: ast::Type::Integer, | 895 | ty: Type::Integer, |
976 | name: "a".to_owned(), | 896 | name: "a".to_owned(), |
977 | init: Some(ast::Expr::int(1).boxed()), | 897 | init: Some(Expr::int(1).boxed()), |
978 | }), | 898 | }), |
979 | ast::Statement::Bare(ast::Expr::If(ast::IfExpr { | 899 | Statement::Bare(Expr::If(IfExpr { |
980 | condition: ast::Expr::true_().boxed(), | 900 | condition: Expr::true_().boxed(), |
981 | then: ast::Block { | 901 | then: Block { |
982 | body: vec![ast::Statement::Bare(ast::Expr::Bin( | 902 | body: vec![Statement::Bare(Expr::bin( |
983 | ast::Expr::Ident("a".to_owned()).boxed(), | 903 | Expr::Ident("a".to_owned()), |
984 | ast::BinOp::Assign(ast::AssignOp { | 904 | "+=", |
985 | op: Some(ast::ArithOp::Add) | 905 | Expr::int(5), |
986 | }), | ||
987 | ast::Expr::int(5).boxed() | ||
988 | ))] | 906 | ))] |
989 | }, | 907 | }, |
990 | else_: ast::Block { | 908 | else_: Block { |
991 | body: vec![ast::Statement::Bare(ast::Expr::Bin( | 909 | body: vec![Statement::Bare(Expr::bin( |
992 | ast::Expr::Ident("a".to_owned()).boxed(), | 910 | Expr::ident("a"), |
993 | ast::BinOp::Assign(ast::AssignOp { | 911 | "+=", |
994 | op: Some(ast::ArithOp::Add) | 912 | Expr::int(10), |
995 | }), | ||
996 | ast::Expr::int(10).boxed() | ||
997 | ))] | 913 | ))] |
998 | } | 914 | } |
999 | })) | 915 | })) |
@@ -1004,7 +920,7 @@ mod test { | |||
1004 | assert_eq!( | 920 | assert_eq!( |
1005 | ctx.lookup(&String::from("a")).unwrap().clone(), | 921 | ctx.lookup(&String::from("a")).unwrap().clone(), |
1006 | Variable { | 922 | Variable { |
1007 | ty: ast::Type::Integer, | 923 | ty: Type::Integer, |
1008 | name: "a".to_owned(), | 924 | name: "a".to_owned(), |
1009 | value: Value::Integer(6) | 925 | value: Value::Integer(6) |
1010 | } | 926 | } |
@@ -1014,29 +930,23 @@ mod test { | |||
1014 | #[test] | 930 | #[test] |
1015 | fn test_substring() { | 931 | fn test_substring() { |
1016 | let language = tree_sitter_python::language(); | 932 | let language = tree_sitter_python::language(); |
1017 | let mut ctx = Context::new(language) | 933 | let mut ctx = Context::new(language).with_program(Program::new()).unwrap(); |
1018 | .with_program(ast::Program::new()) | ||
1019 | .unwrap(); | ||
1020 | assert_eq!( | 934 | assert_eq!( |
1021 | ctx.eval_block(&ast::Block { | 935 | ctx.eval_block(&Block { |
1022 | body: vec![ | 936 | body: vec![ |
1023 | ast::Statement::Declaration(ast::Declaration { | 937 | Statement::Declaration(Declaration { |
1024 | ty: ast::Type::String, | 938 | ty: Type::String, |
1025 | name: "a".to_owned(), | 939 | name: "a".to_owned(), |
1026 | init: Some(ast::Expr::str("foobar").boxed()), | 940 | init: Some(Expr::str("foobar").boxed()), |
1027 | }), | 941 | }), |
1028 | ast::Statement::Declaration(ast::Declaration { | 942 | Statement::Declaration(Declaration { |
1029 | ty: ast::Type::String, | 943 | ty: Type::String, |
1030 | name: "b".to_owned(), | 944 | name: "b".to_owned(), |
1031 | init: Some( | 945 | init: Some( |
1032 | ast::Expr::Call(ast::Call { | 946 | Expr::call( |
1033 | function: "substr".into(), | 947 | "substr", |
1034 | parameters: vec![ | 948 | [Expr::Ident("a".to_owned()), Expr::int(0), Expr::int(3),] |
1035 | ast::Expr::Ident("a".to_owned()), | 949 | ) |
1036 | ast::Expr::int(0), | ||
1037 | ast::Expr::int(3), | ||
1038 | ] | ||
1039 | }) | ||
1040 | .boxed() | 950 | .boxed() |
1041 | ), | 951 | ), |
1042 | }), | 952 | }), |
@@ -1047,7 +957,7 @@ mod test { | |||
1047 | assert_eq!( | 957 | assert_eq!( |
1048 | ctx.lookup(&String::from("b")).unwrap().clone(), | 958 | ctx.lookup(&String::from("b")).unwrap().clone(), |
1049 | Variable { | 959 | Variable { |
1050 | ty: ast::Type::String, | 960 | ty: Type::String, |
1051 | name: "b".to_owned(), | 961 | name: "b".to_owned(), |
1052 | value: "foo".into() | 962 | value: "foo".into() |
1053 | } | 963 | } |
@@ -1057,17 +967,15 @@ mod test { | |||
1057 | #[test] | 967 | #[test] |
1058 | fn test_list() { | 968 | fn test_list() { |
1059 | let language = tree_sitter_python::language(); | 969 | let language = tree_sitter_python::language(); |
1060 | let mut ctx = Context::new(language) | 970 | let mut ctx = Context::new(language).with_program(Program::new()).unwrap(); |
1061 | .with_program(ast::Program::new()) | ||
1062 | .unwrap(); | ||
1063 | assert_eq!( | 971 | assert_eq!( |
1064 | ctx.eval_block(&ast::Block { | 972 | ctx.eval_block(&Block { |
1065 | body: vec![ast::Statement::Declaration(ast::Declaration { | 973 | body: vec![Statement::Declaration(Declaration { |
1066 | ty: ast::Type::List, | 974 | ty: Type::List, |
1067 | name: "a".to_owned(), | 975 | name: "a".to_owned(), |
1068 | init: Some( | 976 | init: Some( |
1069 | ast::Expr::List(ast::List { | 977 | Expr::List(List { |
1070 | items: vec![ast::Expr::int(5)] | 978 | items: vec![Expr::int(5)] |
1071 | }) | 979 | }) |
1072 | .boxed() | 980 | .boxed() |
1073 | ), | 981 | ), |
@@ -1078,9 +986,33 @@ mod test { | |||
1078 | assert_eq!( | 986 | assert_eq!( |
1079 | ctx.lookup(&String::from("a")).unwrap().clone(), | 987 | ctx.lookup(&String::from("a")).unwrap().clone(), |
1080 | Variable { | 988 | Variable { |
1081 | ty: ast::Type::List, | 989 | ty: Type::List, |
990 | name: "a".to_owned(), | ||
991 | value: vec![5usize.into()].into(), | ||
992 | } | ||
993 | ); | ||
994 | } | ||
995 | |||
996 | #[test] | ||
997 | fn test_ts_builtins() { | ||
998 | let language = tree_sitter_python::language(); | ||
999 | let mut ctx = Context::new(language).with_program(Program::new()).unwrap(); | ||
1000 | assert_eq!( | ||
1001 | ctx.eval_block(&Block { | ||
1002 | body: vec![Statement::decl( | ||
1003 | Type::List, | ||
1004 | "a", | ||
1005 | Expr::list([Expr::int(5)]), | ||
1006 | )] | ||
1007 | }), | ||
1008 | Ok(Value::Unit) | ||
1009 | ); | ||
1010 | assert_eq!( | ||
1011 | ctx.lookup(&String::from("a")).unwrap().clone(), | ||
1012 | Variable { | ||
1013 | ty: Type::List, | ||
1082 | name: "a".to_owned(), | 1014 | name: "a".to_owned(), |
1083 | value: vec![5.into()].into(), | 1015 | value: vec![5usize.into()].into(), |
1084 | } | 1016 | } |
1085 | ); | 1017 | ); |
1086 | } | 1018 | } |
@@ -1,6 +1,7 @@ | |||
1 | mod ast; | 1 | mod ast; |
2 | mod builtins; | ||
2 | mod eval; | 3 | mod eval; |
3 | pub mod parser; | 4 | mod parser; |
4 | mod string; | 5 | mod string; |
5 | 6 | ||
6 | pub use eval::evaluate; | 7 | pub use eval::evaluate; |