diff --git a/rust/src/emitter.rs b/rust/src/emitter.rs new file mode 100644 index 0000000..d40df94 --- /dev/null +++ b/rust/src/emitter.rs @@ -0,0 +1,72 @@ +use std::io::Write; +use std::collections::HashSet; + +use crate::parser; + +pub struct LexicalContext<'a> { + parent: Option<&'a LexicalContext<'a>>, + local: HashSet +} + +impl <'a> LexicalContext<'a> { + pub fn toplevel() -> Self { + LexicalContext { + parent: None, + local: HashSet::new() + } + } + #[allow(dead_code)] + fn new(parent: &'a LexicalContext<'a>) -> Self { + LexicalContext { + parent: Some(parent), + local: HashSet::new(), + } + } + fn contains(&self, s: &str) -> bool { + self.local.contains(s) || self.parent.map_or(false, |c| c.contains(s)) + } + fn insert(&mut self, s: String) -> bool { + self.local.insert(s) + } +} + +pub fn emit_all(w: &mut dyn Write, ast: &Vec, ctx: &mut LexicalContext) -> std::io::Result<()> { + for stmt in ast { + emit(w, stmt, ctx)?; + } + Ok(()) +} + +pub fn emit(w: &mut dyn Write, stmt: &parser::Stmt, ctx: &mut LexicalContext) -> std::io::Result<()> { + match &stmt { + parser::Stmt::ReplPrint(expr) => { + write!(w, "console.log((")?; + emit_expr(w, expr, ctx)?; + writeln!(w, "));")?; + } + parser::Stmt::Assignment(id, expr) => { + if !ctx.contains(id) { + ctx.insert(id.clone()); + write!(w, "let ")?; + } + write!(w, "{} = ", id)?; + emit_expr(w, expr, ctx)?; + writeln!(w, ";")?; + } + _ => todo!(), + } + Ok(()) +} + +pub fn emit_expr(w: &mut dyn Write, expr: &parser::Expr, ctx: &mut LexicalContext) -> std::io::Result<()> { + match &expr { + parser::Expr::Id(id) => { + write!(w, "{}", id)?; + } + parser::Expr::Atom(atom) => { + write!(w, "{}", atom)?; + } + _ => todo!(), + } + Ok(()) +} diff --git a/rust/src/main.rs b/rust/src/main.rs index ee34e7b..cc47a4a 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -1,5 +1,6 @@ mod parser; mod evaluator; +mod emitter; use clap::Parser; @@ -15,17 +16,23 @@ struct Cli { file: Option, #[clap(short, long, help="Only parse, do not evaluate")] parse_only: bool, + #[clap(short, long, help="Cross-compile to ECMAScript")] + ecmascript: bool, } fn repl(cli: &Cli) { let mut global = evaluator::Env::global(); + let mut toplevel = emitter::LexicalContext::toplevel(); + let mut out = io::stdout(); loop { let mut line = String::new(); io::stdin().read_line(&mut line).unwrap(); let tree = parser::parse_stmt(&line); if cli.parse_only { println!("{:#?}", tree); - } else { + } else if cli.ecmascript { + emitter::emit(&mut out, &tree, &mut toplevel).ok(); + } else { evaluator::eval(&tree, &mut global); } } @@ -38,6 +45,10 @@ fn script(cli: &Cli) { let tree = parser::parse(&prgm); if cli.parse_only { println!("{:#?}", tree); + } else if cli.ecmascript { + let mut out = io::stdout(); + let mut toplevel = emitter::LexicalContext::toplevel(); + emitter::emit_all(&mut out, &tree, &mut toplevel).ok(); } else { todo!(); }