1pub mod diagnostics;
2pub mod emitter;
3pub mod ir;
4pub mod options;
5pub mod python;
6
7pub use diagnostics::{Diagnostic, Diagnostics, Severity};
8pub use ir::{Expr, Module, Span, Stmt};
9pub use options::{DecimalMode, TranspileOptions};
10
11#[derive(Debug)]
12pub struct PythonToAetherResult {
13 pub aether: Option<String>,
14 pub diagnostics: Diagnostics,
15 pub numpy_used: bool,
16 pub io_used: bool,
17 pub console_used: bool,
18}
19
20pub fn python_to_aether(source: &str, opts: &TranspileOptions) -> PythonToAetherResult {
25 let ir_res = python::python_to_ir(source, opts);
26
27 if ir_res.diagnostics.has_errors() || ir_res.module.is_none() {
29 return PythonToAetherResult {
30 aether: None,
31 diagnostics: ir_res.diagnostics,
32 numpy_used: ir_res.numpy_used,
33 io_used: ir_res.io_used,
34 console_used: ir_res.console_used,
35 };
36 }
37
38 let module = ir_res.module.unwrap();
40
41 let emit_res = emitter::ir_to_aether(&module, opts);
42
43 let mut diags = ir_res.diagnostics;
45 for d in emit_res.diagnostics.0 {
46 diags.push(d);
47 }
48
49 if diags.has_errors() {
50 PythonToAetherResult {
51 aether: None,
52 diagnostics: diags,
53 numpy_used: ir_res.numpy_used,
54 io_used: ir_res.io_used,
55 console_used: ir_res.console_used,
56 }
57 } else {
58 PythonToAetherResult {
59 aether: emit_res.code,
60 diagnostics: diags,
61 numpy_used: ir_res.numpy_used,
62 io_used: ir_res.io_used,
63 console_used: ir_res.console_used,
64 }
65 }
66}
67
68pub fn python_to_aether_checked(
70 source: &str,
71 opts: &TranspileOptions,
72) -> Result<String, Diagnostics> {
73 let res = python_to_aether(source, opts);
74 match res.aether {
75 Some(code) if !res.diagnostics.has_errors() => Ok(code),
76 _ => Err(res.diagnostics),
77 }
78}
79
80pub fn aether_parse_ast(code: &str) -> Result<crate::ast::Program, crate::parser::ParseError> {
81 let mut parser = crate::parser::Parser::new(code);
82 parser.parse_program()
83}
84
85pub fn aether_parse_ast_json(code: &str) -> Result<serde_json::Value, Diagnostics> {
87 let program = aether_parse_ast(code).map_err(|e| {
88 let mut d = Diagnostics::new();
89 d.push(Diagnostic::error(
90 "AETHER_PARSE_ERROR",
91 e.to_string(),
92 Span::default(),
93 ));
94 d
95 })?;
96
97 Ok(program_to_json(&program))
98}
99
100pub fn aether_eval_safe(
103 code: &str,
104 opts: &TranspileOptions,
105) -> Result<crate::value::Value, Diagnostics> {
106 let diags = aether_check(code, opts);
107 if diags.has_errors() {
108 return Err(diags);
109 }
110
111 let mut engine = crate::Aether::new();
112 engine.eval(code).map_err(|e| {
113 let mut d = Diagnostics::new();
114 d.push(Diagnostic::error("AETHER_EVAL_ERROR", e, Span::default()));
115 d
116 })
117}
118
119pub fn aether_check(code: &str, opts: &TranspileOptions) -> Diagnostics {
123 let mut diagnostics = Diagnostics::new();
124
125 let program = match aether_parse_ast(code) {
126 Ok(p) => p,
127 Err(e) => {
128 diagnostics.push(Diagnostic::error(
129 "AETHER_PARSE_ERROR",
130 e.to_string(),
131 crate::pytranspile::ir::Span::default(),
132 ));
133 return diagnostics;
134 }
135 };
136
137 for stmt in &program {
138 check_stmt(stmt, opts, &mut diagnostics);
139 }
140
141 diagnostics
142}
143
144fn check_stmt(stmt: &crate::ast::Stmt, opts: &TranspileOptions, d: &mut Diagnostics) {
145 use crate::ast::Stmt;
146 match stmt {
147 Stmt::Set { value, .. } => check_expr(value, opts, d),
148 Stmt::SetIndex {
149 object,
150 index,
151 value,
152 } => {
153 check_expr(object, opts, d);
154 check_expr(index, opts, d);
155 check_expr(value, opts, d);
156 }
157 Stmt::FuncDef { body, .. } | Stmt::GeneratorDef { body, .. } => {
158 for s in body {
159 check_stmt(s, opts, d);
160 }
161 }
162 Stmt::LazyDef { expr, .. } => check_expr(expr, opts, d),
163 Stmt::Return(expr) | Stmt::Yield(expr) => check_expr(expr, opts, d),
164 Stmt::While { condition, body } => {
165 check_expr(condition, opts, d);
166 for s in body {
167 check_stmt(s, opts, d);
168 }
169 }
170 Stmt::For {
171 var,
172 iterable,
173 body,
174 } => {
175 let _ = var;
176 check_expr(iterable, opts, d);
177 for s in body {
178 check_stmt(s, opts, d);
179 }
180 }
181 Stmt::ForIndexed {
182 index_var,
183 value_var,
184 iterable,
185 body,
186 } => {
187 let _ = (index_var, value_var);
188 check_expr(iterable, opts, d);
189 for s in body {
190 check_stmt(s, opts, d);
191 }
192 }
193 Stmt::Switch {
194 expr,
195 cases,
196 default,
197 } => {
198 check_expr(expr, opts, d);
199 for (c, b) in cases {
200 check_expr(c, opts, d);
201 for s in b {
202 check_stmt(s, opts, d);
203 }
204 }
205 if let Some(b) = default {
206 for s in b {
207 check_stmt(s, opts, d);
208 }
209 }
210 }
211 Stmt::Expression(expr) => check_expr(expr, opts, d),
212 Stmt::Import { .. } | Stmt::Export(_) => {}
213 Stmt::Throw(expr) => check_expr(expr, opts, d),
214 Stmt::Break | Stmt::Continue => {}
215 }
216}
217
218fn check_expr(expr: &crate::ast::Expr, opts: &TranspileOptions, d: &mut Diagnostics) {
219 use crate::ast::Expr;
220 match expr {
221 Expr::Binary { left, right, .. } => {
222 check_expr(left, opts, d);
223 check_expr(right, opts, d);
224 }
225 Expr::Unary { expr, .. } => check_expr(expr, opts, d),
226 Expr::Call { func, args } => {
227 if let Expr::Identifier(name) = func.as_ref() {
228 if opts.reject_console && is_console_builtin(name) {
229 d.push(Diagnostic::error(
230 "AETHER_CONSOLE_REJECTED",
231 format!("console builtin '{name}' is rejected"),
232 crate::pytranspile::ir::Span::default(),
233 ));
234 }
235 if opts.reject_io && is_io_builtin(name) {
236 d.push(Diagnostic::error(
237 "AETHER_IO_REJECTED",
238 format!("io builtin '{name}' is rejected"),
239 crate::pytranspile::ir::Span::default(),
240 ));
241 }
242 }
243 check_expr(func, opts, d);
244 for a in args {
245 check_expr(a, opts, d);
246 }
247 }
248 Expr::Array(items) => {
249 for e in items {
250 check_expr(e, opts, d);
251 }
252 }
253 Expr::Dict(items) => {
254 for (_k, v) in items {
255 check_expr(v, opts, d);
256 }
257 }
258 Expr::Index { object, index } => {
259 check_expr(object, opts, d);
260 check_expr(index, opts, d);
261 }
262 Expr::If {
263 condition,
264 then_branch,
265 elif_branches,
266 else_branch,
267 } => {
268 check_expr(condition, opts, d);
269 for s in then_branch {
270 check_stmt(s, opts, d);
271 }
272 for (c, b) in elif_branches {
273 check_expr(c, opts, d);
274 for s in b {
275 check_stmt(s, opts, d);
276 }
277 }
278 if let Some(b) = else_branch {
279 for s in b {
280 check_stmt(s, opts, d);
281 }
282 }
283 }
284 Expr::Lambda { body, .. } => {
285 for s in body {
286 check_stmt(s, opts, d);
287 }
288 }
289 Expr::Number(_)
290 | Expr::BigInteger(_)
291 | Expr::String(_)
292 | Expr::Boolean(_)
293 | Expr::Null
294 | Expr::Identifier(_) => {}
295 }
296}
297
298fn is_console_builtin(name: &str) -> bool {
299 matches!(name, "PRINT" | "PRINTLN" | "INPUT")
300}
301
302fn is_io_builtin(name: &str) -> bool {
303 matches!(
304 name,
305 "READ_FILE"
306 | "WRITE_FILE"
307 | "APPEND_FILE"
308 | "DELETE_FILE"
309 | "FILE_EXISTS"
310 | "LIST_DIR"
311 | "CREATE_DIR"
312 | "HTTP_GET"
313 | "HTTP_POST"
314 | "HTTP_PUT"
315 | "HTTP_DELETE"
316 ) || name.starts_with("EXCEL_")
317}
318
319fn program_to_json(program: &crate::ast::Program) -> serde_json::Value {
320 serde_json::Value::Array(program.iter().map(stmt_to_json).collect())
321}
322
323fn stmt_to_json(stmt: &crate::ast::Stmt) -> serde_json::Value {
324 use crate::ast::Stmt;
325 match stmt {
326 Stmt::Set { name, value } => {
327 serde_json::json!({"type":"Set","name":name,"value":expr_to_json(value)})
328 }
329 Stmt::SetIndex {
330 object,
331 index,
332 value,
333 } => {
334 serde_json::json!({"type":"SetIndex","object":expr_to_json(object),"index":expr_to_json(index),"value":expr_to_json(value)})
335 }
336 Stmt::FuncDef { name, params, body } => {
337 serde_json::json!({"type":"FuncDef","name":name,"params":params,"body": program_to_json(body)})
338 }
339 Stmt::GeneratorDef { name, params, body } => {
340 serde_json::json!({"type":"GeneratorDef","name":name,"params":params,"body": program_to_json(body)})
341 }
342 Stmt::LazyDef { name, expr } => {
343 serde_json::json!({"type":"LazyDef","name":name,"expr":expr_to_json(expr)})
344 }
345 Stmt::Return(expr) => serde_json::json!({"type":"Return","value":expr_to_json(expr)}),
346 Stmt::Yield(expr) => serde_json::json!({"type":"Yield","value":expr_to_json(expr)}),
347 Stmt::Break => serde_json::json!({"type":"Break"}),
348 Stmt::Continue => serde_json::json!({"type":"Continue"}),
349 Stmt::While { condition, body } => {
350 serde_json::json!({"type":"While","condition":expr_to_json(condition),"body": program_to_json(body)})
351 }
352 Stmt::For {
353 var,
354 iterable,
355 body,
356 } => {
357 serde_json::json!({"type":"For","var":var,"iterable":expr_to_json(iterable),"body": program_to_json(body)})
358 }
359 Stmt::ForIndexed {
360 index_var,
361 value_var,
362 iterable,
363 body,
364 } => {
365 serde_json::json!({"type":"ForIndexed","index_var":index_var,"value_var":value_var,"iterable":expr_to_json(iterable),"body": program_to_json(body)})
366 }
367 Stmt::Switch {
368 expr,
369 cases,
370 default,
371 } => {
372 let cases_json: Vec<_> = cases
373 .iter()
374 .map(|(c, b)| serde_json::json!({"when":expr_to_json(c),"body":program_to_json(b)}))
375 .collect();
376 let default_json = default.as_ref().map(program_to_json);
377 serde_json::json!({"type":"Switch","expr":expr_to_json(expr),"cases":cases_json,"default":default_json})
378 }
379 Stmt::Import {
380 names,
381 path,
382 aliases,
383 namespace,
384 } => serde_json::json!({
385 "type":"Import",
386 "names": names,
387 "path": path,
388 "aliases": aliases,
389 "namespace": namespace,
390 }),
391 Stmt::Export(name) => serde_json::json!({"type":"Export","name":name}),
392 Stmt::Throw(expr) => serde_json::json!({"type":"Throw","value":expr_to_json(expr)}),
393 Stmt::Expression(expr) => {
394 serde_json::json!({"type":"Expression","value":expr_to_json(expr)})
395 }
396 }
397}
398
399fn expr_to_json(expr: &crate::ast::Expr) -> serde_json::Value {
400 use crate::ast::Expr;
401 match expr {
402 Expr::Number(n) => serde_json::json!({"type":"Number","value":n}),
403 Expr::BigInteger(s) => serde_json::json!({"type":"BigInteger","value":s}),
404 Expr::String(s) => serde_json::json!({"type":"String","value":s}),
405 Expr::Boolean(b) => serde_json::json!({"type":"Boolean","value":b}),
406 Expr::Null => serde_json::json!({"type":"Null"}),
407 Expr::Identifier(name) => serde_json::json!({"type":"Identifier","name":name}),
408 Expr::Binary { left, op, right } => {
409 serde_json::json!({"type":"Binary","op":binop_to_str(op),"left":expr_to_json(left),"right":expr_to_json(right)})
410 }
411 Expr::Unary { op, expr } => {
412 serde_json::json!({"type":"Unary","op":unaryop_to_str(op),"expr":expr_to_json(expr)})
413 }
414 Expr::Call { func, args } => {
415 serde_json::json!({"type":"Call","func":expr_to_json(func),"args": serde_json::Value::Array(args.iter().map(expr_to_json).collect())})
416 }
417 Expr::Array(items) => {
418 serde_json::json!({"type":"Array","items": serde_json::Value::Array(items.iter().map(expr_to_json).collect())})
419 }
420 Expr::Dict(items) => {
421 let pairs: Vec<_> = items
422 .iter()
423 .map(|(k, v)| serde_json::json!({"key":k,"value":expr_to_json(v)}))
424 .collect();
425 serde_json::json!({"type":"Dict","items":pairs})
426 }
427 Expr::Index { object, index } => {
428 serde_json::json!({"type":"Index","object":expr_to_json(object),"index":expr_to_json(index)})
429 }
430 Expr::If {
431 condition,
432 then_branch,
433 elif_branches,
434 else_branch,
435 } => {
436 let elifs: Vec<_> = elif_branches.iter().map(|(c, b)| serde_json::json!({"condition":expr_to_json(c),"body":program_to_json(b)})).collect();
437 let else_json = else_branch.as_ref().map(program_to_json);
438 serde_json::json!({"type":"If","condition":expr_to_json(condition),"then":program_to_json(then_branch),"elifs":elifs,"else":else_json})
439 }
440 Expr::Lambda { params, body } => {
441 serde_json::json!({"type":"Lambda","params":params,"body":program_to_json(body)})
442 }
443 }
444}
445
446fn binop_to_str(op: &crate::ast::BinOp) -> &'static str {
447 use crate::ast::BinOp;
448 match op {
449 BinOp::Add => "+",
450 BinOp::Subtract => "-",
451 BinOp::Multiply => "*",
452 BinOp::Divide => "/",
453 BinOp::Modulo => "%",
454 BinOp::Equal => "==",
455 BinOp::NotEqual => "!=",
456 BinOp::Less => "<",
457 BinOp::LessEqual => "<=",
458 BinOp::Greater => ">",
459 BinOp::GreaterEqual => ">=",
460 BinOp::And => "&&",
461 BinOp::Or => "||",
462 }
463}
464
465fn unaryop_to_str(op: &crate::ast::UnaryOp) -> &'static str {
466 use crate::ast::UnaryOp;
467 match op {
468 UnaryOp::Minus => "-",
469 UnaryOp::Not => "!",
470 }
471}