product_farm_farmscript/
lib.rs1mod token;
27mod lexer;
28mod ast;
29mod parser;
30mod compiler;
31mod builtins;
32
33pub use token::{Token, TokenKind, Span};
34pub use lexer::Lexer;
35pub use ast::{Expr, BinaryOp, UnaryOp, Literal};
36pub use parser::{Parser, ParseError};
37pub use compiler::{Compiler, CompileError, CompileOptions};
38pub use builtins::{BuiltinFn, BUILTINS, FnCategory, get_builtin};
39
40use serde_json::Value as JsonValue;
41
42pub fn compile(source: &str) -> Result<JsonValue, CompileError> {
44 compile_with_options(source, &CompileOptions::default())
45}
46
47pub fn compile_with_options(source: &str, options: &CompileOptions) -> Result<JsonValue, CompileError> {
49 let lexer = Lexer::new(source);
50 let mut parser = Parser::new(lexer);
51 let ast = parser.parse()?;
52 let compiler = Compiler::new(options.clone());
53 compiler.compile(&ast)
54}
55
56pub fn parse(source: &str) -> Result<Expr, ParseError> {
58 let lexer = Lexer::new(source);
59 let mut parser = Parser::new(lexer);
60 parser.parse()
61}
62
63pub fn tokenize(source: &str) -> Vec<Token> {
65 let mut lexer = Lexer::new(source);
66 lexer.collect_tokens()
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn test_simple_comparison() {
75 let result = compile("x < 10").unwrap();
76 assert_eq!(result, serde_json::json!({"<": [{"var": "x"}, 10]}));
77 }
78
79 #[test]
80 fn test_and_expression() {
81 let result = compile("a and b").unwrap();
82 assert_eq!(result, serde_json::json!({"and": [{"var": "a"}, {"var": "b"}]}));
83 }
84
85 #[test]
86 fn test_complex_expression() {
87 let result = compile("alert_acknowledged and time_since_alert_secs < 120").unwrap();
88 assert_eq!(
89 result,
90 serde_json::json!({
91 "and": [
92 {"var": "alert_acknowledged"},
93 {"<": [{"var": "time_since_alert_secs"}, 120]}
94 ]
95 })
96 );
97 }
98
99 #[test]
100 fn test_clamp_expression() {
101 let result = compile("clamp(0, 100, max_possible_score * (positive_signals - negative_signals * 0.5))").unwrap();
102 let json_str = serde_json::to_string(&result).unwrap();
104 assert!(json_str.contains("max"));
105 assert!(json_str.contains("min"));
106 }
107
108 #[test]
109 fn test_if_chain() {
110 let source = r#"
111 if critical_failures > 0 then "strong_no_hire"
112 else if overall_score >= 85 then "strong_hire"
113 else if overall_score >= 65 then "hire"
114 else if overall_score >= 45 then "no_hire"
115 else "strong_no_hire"
116 "#;
117 let result = compile(source).unwrap();
118 let json_str = serde_json::to_string(&result).unwrap();
119 assert!(json_str.contains("if"));
120 assert!(json_str.contains("strong_hire"));
121 }
122
123 #[test]
124 fn test_path_variable() {
125 let result = compile("/users/active/count").unwrap();
126 assert_eq!(result, serde_json::json!({"var": "users.active.count"}));
127 }
128
129 #[test]
130 fn test_safe_division() {
131 let result = compile("revenue /? expenses").unwrap();
132 let json_str = serde_json::to_string(&result).unwrap();
134 assert!(json_str.contains("if"));
135 assert!(json_str.contains("/"));
136 }
137
138 #[test]
139 fn test_method_chain() {
140 let result = compile("items.filter(x => x > 0).map(x => x * 2)").unwrap();
141 let json_str = serde_json::to_string(&result).unwrap();
142 assert!(json_str.contains("filter"));
143 assert!(json_str.contains("map"));
144 }
145
146 #[test]
147 fn test_equality_synonyms() {
148 let sources = ["a === b", "a is b", "a eq b", "a equals b", "a same_as b"];
150 for source in sources {
151 let result = compile(source).unwrap();
152 assert_eq!(
153 result,
154 serde_json::json!({"===": [{"var": "a"}, {"var": "b"}]}),
155 "Failed for: {}", source
156 );
157 }
158 }
159
160 #[test]
161 fn test_in_operator() {
162 let result = compile("x in [1, 2, 3]").unwrap();
163 assert_eq!(result, serde_json::json!({"in": [{"var": "x"}, [1, 2, 3]]}));
164 }
165
166 #[test]
167 fn test_contains_method() {
168 let result = compile("[1, 2, 3].contains(x)").unwrap();
169 assert_eq!(result, serde_json::json!({"in": [{"var": "x"}, [1, 2, 3]]}));
170 }
171
172 #[test]
173 fn test_truthy_operator() {
174 let result = compile("x?").unwrap();
175 assert_eq!(result, serde_json::json!({"!!": {"var": "x"}}));
176 }
177
178 #[test]
179 fn test_null_coalesce() {
180 let result = compile("a ?? b").unwrap();
181 let json_str = serde_json::to_string(&result).unwrap();
183 assert!(json_str.contains("if"));
184 assert!(json_str.contains("!="));
185 }
186
187 #[test]
188 fn test_db_outage_detect_quick_response() {
189 let source = "alert_acknowledged and time_since_alert_secs < 120";
191 let result = compile(source).unwrap();
192 assert_eq!(
193 result,
194 serde_json::json!({
195 "and": [
196 {"var": "alert_acknowledged"},
197 {"<": [{"var": "time_since_alert_secs"}, 120]}
198 ]
199 })
200 );
201 }
202
203 #[test]
204 fn test_db_outage_compute_signal_score() {
205 let source = "clamp(0, 100, max_possible_score * (positive_signals - negative_signals * 0.5))";
207 let result = compile(source).unwrap();
208 let obj = result.as_object().unwrap();
210 assert!(obj.contains_key("max")); }
212
213 #[test]
214 fn test_db_outage_compute_recommendation() {
215 let source = r#"
217 if critical_failures > 0 then "strong_no_hire"
218 else if overall_score >= 85 then "strong_hire"
219 else if overall_score >= 65 then "hire"
220 else if overall_score >= 45 then "no_hire"
221 else "strong_no_hire"
222 "#;
223 let result = compile(source).unwrap();
224 let obj = result.as_object().unwrap();
225 assert!(obj.contains_key("if"));
226 }
227}