From 02b8ff860bc13df733b9298f6cffac701a8c1125 Mon Sep 17 00:00:00 2001 From: Dennis Ranke Date: Mon, 1 Nov 2021 00:10:40 +0100 Subject: [PATCH] finish porting to new parser --- src/ast.rs | 166 ++++--- src/constfold.rs | 52 ++- src/emit.rs | 158 ++++--- src/main.rs | 25 +- src/parser.rs | 1156 +++++++++++++++++++++++++--------------------- src/parser2.rs | 830 --------------------------------- src/typecheck.rs | 523 ++++++++++++++------- 7 files changed, 1184 insertions(+), 1726 deletions(-) delete mode 100644 src/parser2.rs diff --git a/src/ast.rs b/src/ast.rs index 67b91e8..a11cb99 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,154 +1,148 @@ -#[derive(Debug, Clone, Copy)] -pub struct Position(pub usize); +use crate::Span; #[derive(Debug)] -pub struct Script<'a> { - pub imports: Vec>, - pub global_vars: Vec>, - pub functions: Vec>, +pub struct Script { + pub imports: Vec, + pub global_vars: Vec, + pub functions: Vec, } #[derive(Debug)] -pub enum TopLevelItem<'a> { - Import(Import<'a>), - GlobalVar(GlobalVar<'a>), - Function(Function<'a>), +pub enum TopLevelItem { + Import(Import), + GlobalVar(GlobalVar), + Function(Function), } #[derive(Debug)] -pub struct Import<'a> { - pub position: Position, - pub import: &'a str, - pub type_: ImportType<'a>, +pub struct Import { + pub span: Span, + pub import: String, + pub type_: ImportType, } #[derive(Debug)] -pub enum ImportType<'a> { +pub enum ImportType { Memory(u32), Variable { - name: &'a str, + name: String, type_: Type, mutable: bool, }, - // Function { name: &'a str, params: Vec, result: Option } + // Function { name: String, params: Vec, result: Option } } #[derive(Debug)] -pub struct GlobalVar<'a> { - pub position: Position, - pub name: &'a str, +pub struct GlobalVar { + pub span: Span, + pub name: String, pub type_: Type, } #[derive(Debug)] -pub struct Function<'a> { - pub position: Position, +pub struct Function { + pub span: Span, pub export: bool, - pub name: &'a str, - pub params: Vec<(&'a str, Type)>, + pub name: String, + pub params: Vec<(String, Type)>, pub type_: Option, - pub body: Block<'a>, + pub body: Block, } #[derive(Debug)] -pub struct Block<'a> { - pub statements: Vec>, - pub final_expression: Option>, +pub struct Block { + pub statements: Vec, + pub final_expression: Option>, } -impl<'a> Block<'a> { +impl Block { pub fn type_(&self) -> Option { self.final_expression.as_ref().and_then(|e| e.type_) } } #[derive(Debug)] -pub enum Statement<'a> { - LocalVariable(LocalVariable<'a>), - Poke { - position: Position, - mem_location: MemoryLocation<'a>, - value: Expression<'a>, - }, - Expression(Expression<'a>), -} - -#[derive(Debug)] -pub struct MemoryLocation<'a> { - pub position: Position, +pub struct MemoryLocation { + pub span: Span, pub size: MemSize, - pub left: Expression<'a>, - pub right: Expression<'a>, + pub left: Box, + pub right: Box, } #[derive(Debug)] -pub struct LocalVariable<'a> { - pub position: Position, - pub name: &'a str, +pub struct Expression { pub type_: Option, - pub value: Option>, - pub defer: bool + pub expr: Expr, + pub span: Span, } #[derive(Debug)] -pub struct Expression<'a> { - pub type_: Option, - pub expr: Expr<'a>, -} - -impl<'a> From> for Expression<'a> { - fn from(expr: Expr<'a>) -> Expression<'a> { - Expression { type_: None, expr } - } -} - -#[derive(Debug)] -pub enum Expr<'a> { +pub enum Expr { I32Const(i32), F32Const(f32), - Variable { - position: Position, - name: &'a str, + Variable(String), + Let { + name: String, + type_: Option, + value: Option>, + defer: bool, + }, + Poke { + mem_location: MemoryLocation, + value: Box, }, Loop { - position: Position, - label: &'a str, - block: Box>, + label: String, + block: Box, }, BranchIf { - position: Position, - condition: Box>, - label: &'a str, + condition: Box, + label: String, + }, + UnaryOp { + op: UnaryOp, + value: Box, }, BinOp { - position: Position, op: BinOp, - left: Box>, - right: Box>, + left: Box, + right: Box, }, LocalTee { - position: Position, - name: &'a str, - value: Box>, + name: String, + value: Box, }, Cast { - position: Position, - value: Box>, + value: Box, type_: Type, }, FuncCall { - position: Position, - name: &'a str, - params: Vec> + name: String, + params: Vec, }, Select { - position: Position, - condition: Box>, - if_true: Box>, - if_false: Box> + condition: Box, + if_true: Box, + if_false: Box, + }, + Error, +} + +impl Expr { + pub fn with_span(self, span: Span) -> Expression { + Expression { + type_: None, + expr: self, + span: span, + } } } +#[derive(Debug, Clone, Copy)] +pub enum UnaryOp { + Negate, +} + #[derive(Debug, Clone, Copy)] pub enum BinOp { Add, diff --git a/src/constfold.rs b/src/constfold.rs index ad3d232..d67c6ba 100644 --- a/src/constfold.rs +++ b/src/constfold.rs @@ -8,22 +8,7 @@ pub fn fold_script(script: &mut ast::Script) { fn fold_block(block: &mut ast::Block) { for stmt in &mut block.statements { - match stmt { - ast::Statement::LocalVariable(lv) => { - if let Some(ref mut expr) = lv.value { - fold_expr(expr); - } - } - ast::Statement::Expression(expr) => fold_expr(expr), - ast::Statement::Poke { - mem_location, - value, - .. - } => { - fold_mem_location(mem_location); - fold_expr(value); - } - } + fold_expr(stmt); } if let Some(ref mut expr) = block.final_expression { fold_expr(expr); @@ -38,6 +23,29 @@ fn fold_mem_location(mem_location: &mut ast::MemoryLocation) { fn fold_expr(expr: &mut ast::Expression) { use ast::BinOp::*; match expr.expr { + ast::Expr::Let { ref mut value, .. } => { + if let Some(ref mut expr) = value { + fold_expr(expr); + } + } + ast::Expr::Poke { + ref mut mem_location, + ref mut value, + .. + } => { + fold_mem_location(mem_location); + fold_expr(value); + } + ast::Expr::UnaryOp { op, ref mut value } => { + fold_expr(value); + let result = match (op, &value.expr) { + (ast::UnaryOp::Negate, ast::Expr::I32Const(value)) => Some(ast::Expr::I32Const(-*value)), + _ => None + }; + if let Some(result) = result { + expr.expr = result; + } + } ast::Expr::BinOp { ref mut left, op, @@ -105,7 +113,7 @@ fn fold_expr(expr: &mut ast::Expression) { } => fold_expr(condition), ast::Expr::Cast { ref mut value, .. } => fold_expr(value), ast::Expr::FuncCall { - name, + ref name, ref mut params, .. } => { @@ -114,15 +122,21 @@ fn fold_expr(expr: &mut ast::Expression) { } use ast::Expr::*; let params: Vec<_> = params.iter().map(|e| &e.expr).collect(); - expr.expr = match (name, params.as_slice()) { + expr.expr = match (name.as_str(), params.as_slice()) { ("sqrt", [F32Const(v)]) if *v >= 0.0 => F32Const(v.sqrt()), _ => return, }; } - ast::Expr::Select { ref mut condition, ref mut if_true, ref mut if_false, .. } => { + ast::Expr::Select { + ref mut condition, + ref mut if_true, + ref mut if_false, + .. + } => { fold_expr(condition); fold_expr(if_true); fold_expr(if_false); } + ast::Expr::Error => unreachable!() } } diff --git a/src/emit.rs b/src/emit.rs index ea88a0c..e892ba6 100644 --- a/src/emit.rs +++ b/src/emit.rs @@ -35,7 +35,7 @@ pub fn emit(script: &ast::Script) -> Vec { Some(&import.import[(dot_index + 1)..]), ) } else { - (import.import, None) + (import.import.as_str(), None) }; let type_: EntityType = match import.type_ { ast::ImportType::Memory(min_size) => MemoryType { @@ -46,7 +46,7 @@ pub fn emit(script: &ast::Script) -> Vec { .into(), ast::ImportType::Variable { type_, - name, + ref name, mutable, } => { globals.insert(name, globals.len() as u32); @@ -72,7 +72,7 @@ pub fn emit(script: &ast::Script) -> Vec { let type_ = *function_types.get(&function_type_key(func)).unwrap(); functions.function(type_ as u32); if func.export { - exports.export(func.name, Export::Function(index as u32)); + exports.export(&func.name, Export::Function(index as u32)); } code.function(&emit_function(func, &globals)); @@ -109,9 +109,9 @@ fn function_type_key(func: &ast::Function) -> FunctionTypeKey { struct FunctionContext<'a> { function: &'a mut Function, globals: &'a HashMap<&'a str, u32>, - locals: &'a HashMap<&'a str, u32>, + locals: &'a HashMap, labels: Vec, - deferred_inits: HashMap<&'a str, &'a ast::Expression<'a>>, + deferred_inits: HashMap<&'a str, &'a ast::Expression>, } fn emit_function(func: &ast::Function, globals: &HashMap<&str, u32>) -> Function { @@ -121,7 +121,7 @@ fn emit_function(func: &ast::Function, globals: &HashMap<&str, u32>) -> Function let mut function = Function::new_with_locals_types(locals.iter().map(|(_, t)| map_type(*t))); - let locals: HashMap<&str, u32> = locals + let locals: HashMap = locals .into_iter() .enumerate() .map(|(index, (name, _))| (name, index as u32)) @@ -144,34 +144,33 @@ fn emit_function(func: &ast::Function, globals: &HashMap<&str, u32>) -> Function function } -fn collect_locals<'a>(block: &ast::Block<'a>, locals: &mut Vec<(&'a str, ast::Type)>) { +fn collect_locals<'a>(block: &ast::Block, locals: &mut Vec<(String, ast::Type)>) { for stmt in &block.statements { - match stmt { - ast::Statement::LocalVariable(v) => { - locals.push((v.name, v.type_.unwrap())); - if let Some(ref value) = v.value { - collect_locals_expr(value, locals); - } - } - ast::Statement::Expression(e) => collect_locals_expr(e, locals), - ast::Statement::Poke { - mem_location, - value, - .. - } => { - collect_locals_expr(&mem_location.left, locals); - collect_locals_expr(value, locals); - } - } + collect_locals_expr(stmt, locals); } if let Some(ref expr) = block.final_expression { collect_locals_expr(expr, locals); } } -fn collect_locals_expr<'a>(expr: &ast::Expression<'a>, locals: &mut Vec<(&'a str, ast::Type)>) { +fn collect_locals_expr<'a>(expr: &ast::Expression, locals: &mut Vec<(String, ast::Type)>) { match &expr.expr { + ast::Expr::Let {name, type_, value, ..} => { + locals.push((name.clone(), type_.unwrap())); + if let Some(ref value) = value { + collect_locals_expr(value, locals); + } + } + ast::Expr::Poke { + mem_location, + value, + .. + } => { + collect_locals_expr(&mem_location.left, locals); + collect_locals_expr(value, locals); + } ast::Expr::Variable { .. } | ast::Expr::I32Const(_) | ast::Expr::F32Const(_) => (), + ast::Expr::UnaryOp { value, .. } => collect_locals_expr(value, locals), ast::Expr::BinOp { left, right, .. } => { collect_locals_expr(left, locals); collect_locals_expr(right, locals); @@ -195,54 +194,15 @@ fn collect_locals_expr<'a>(expr: &ast::Expression<'a>, locals: &mut Vec<(&'a str collect_locals_expr(if_true, locals); collect_locals_expr(if_false, locals); } + ast::Expr::Error => unreachable!() } } fn emit_block<'a>(ctx: &mut FunctionContext<'a>, block: &'a ast::Block) { for stmt in &block.statements { - match stmt { - ast::Statement::Expression(e) => { - emit_expression(ctx, e); - if e.type_.is_some() { - ctx.function.instruction(&Instruction::Drop); - } - } - ast::Statement::LocalVariable(v) => { - if let Some(ref val) = v.value { - if v.defer { - ctx.deferred_inits.insert(v.name, val); - } else { - emit_expression(ctx, val); - ctx.function - .instruction(&Instruction::LocalSet(*ctx.locals.get(v.name).unwrap())); - } - } - } - ast::Statement::Poke { - mem_location, - value, - .. - } => { - emit_expression(ctx, &mem_location.left); - emit_expression(ctx, value); - let offset = if let ast::Expr::I32Const(v) = mem_location.right.expr { - v as u32 as u64 - } else { - unreachable!() - }; - ctx.function.instruction(&match mem_location.size { - ast::MemSize::Byte => Instruction::I32Store8(MemArg { - align: 0, - memory_index: 0, - offset, - }), - ast::MemSize::Word => Instruction::I32Store(MemArg { - align: 2, - memory_index: 0, - offset, - }), - }); - } + emit_expression(ctx, stmt); + if stmt.type_.is_some() { + ctx.function.instruction(&Instruction::Drop); } } if let Some(ref expr) = block.final_expression { @@ -252,6 +212,55 @@ fn emit_block<'a>(ctx: &mut FunctionContext<'a>, block: &'a ast::Block) { fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression) { match &expr.expr { + ast::Expr::Let { value, name, defer, ..} => { + if let Some(ref val) = value { + if *defer { + ctx.deferred_inits.insert(name, val); + } else { + emit_expression(ctx, val); + ctx.function + .instruction(&Instruction::LocalSet(*ctx.locals.get(name).unwrap())); + } + } + } + ast::Expr::Poke { + mem_location, + value, + .. + } => { + emit_expression(ctx, &mem_location.left); + emit_expression(ctx, value); + let offset = if let ast::Expr::I32Const(v) = mem_location.right.expr { + v as u32 as u64 + } else { + unreachable!() + }; + ctx.function.instruction(&match mem_location.size { + ast::MemSize::Byte => Instruction::I32Store8(MemArg { + align: 0, + memory_index: 0, + offset, + }), + ast::MemSize::Word => Instruction::I32Store(MemArg { + align: 2, + memory_index: 0, + offset, + }), + }); + } + ast::Expr::UnaryOp { op, value } => { + use ast::UnaryOp::*; + use ast::Type::*; + match (value.type_.unwrap(), op) { + (I32, Negate) => { + // TODO: try to improve this uglyness + ctx.function.instruction(&Instruction::I32Const(0)); + emit_expression(ctx, value); + ctx.function.instruction(&Instruction::I32Sub); + } + _ => unreachable!() + }; + } ast::Expr::BinOp { left, op, right, .. } => { @@ -300,7 +309,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression) .iter() .rev() .enumerate() - .find(|(_, l)| l == label) + .find(|(_, l)| *l == label) .unwrap() .0; ctx.function.instruction(&Instruction::BrIf(depth as u32)); @@ -313,7 +322,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression) } ast::Expr::LocalTee { name, value, .. } => { emit_expression(ctx, value); - let index = ctx.locals.get(*name).unwrap(); + let index = ctx.locals.get(name).unwrap(); ctx.function.instruction(&Instruction::LocalTee(*index)); } ast::Expr::Loop { label, block, .. } => { @@ -324,15 +333,15 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression) ctx.labels.pop(); ctx.function.instruction(&Instruction::End); } - ast::Expr::Variable { name, .. } => { - if let Some(index) = ctx.locals.get(*name) { - if let Some(expr) = ctx.deferred_inits.remove(*name) { + ast::Expr::Variable(name) => { + if let Some(index) = ctx.locals.get(name) { + if let Some(expr) = ctx.deferred_inits.remove(name.as_str()) { emit_expression(ctx, expr); ctx.function.instruction(&Instruction::LocalTee(*index)); } else { ctx.function.instruction(&Instruction::LocalGet(*index)); } - } else if let Some(index) = ctx.globals.get(*name) { + } else if let Some(index) = ctx.globals.get(name.as_str()) { ctx.function.instruction(&Instruction::GlobalGet(*index)); } else { unreachable!() @@ -371,6 +380,7 @@ fn emit_expression<'a>(ctx: &mut FunctionContext<'a>, expr: &'a ast::Expression) emit_expression(ctx, condition); ctx.function.instruction(&Instruction::Select); } + ast::Expr::Error => unreachable!() } } diff --git a/src/main.rs b/src/main.rs index ad7ddd5..07b99f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,9 +6,10 @@ mod ast; mod constfold; mod emit; mod parser; -mod parser2; mod typecheck; +type Span = std::ops::Range; + fn main() -> Result<()> { let mut filename = PathBuf::from( std::env::args() @@ -18,28 +19,14 @@ fn main() -> Result<()> { let mut input = String::new(); File::open(&filename)?.read_to_string(&mut input)?; - if let Err(_) = parser2::parse(&input) { - bail!("Parse failed"); - } - - let mut script = match parser::parse(input.as_str()) { + let mut script = match parser::parse(&input) { Ok(script) => script, - Err(err) => { - bail!( - "parse error: {}", - nom::error::convert_error(input.as_str(), err) - ); - } + Err(_) => bail!("Parse failed") }; constfold::fold_script(&mut script); - if let Err(err) = typecheck::tc_script(&mut script) { - let line = input[..(input.len() - err.position.0)] - .chars() - .filter(|c| *c == '\n') - .count() - + 1; - bail!("{} in line {}", err.message, line); + if let Err(_) = typecheck::tc_script(&mut script, &input) { + bail!("Parse failed"); } let wasm = emit::emit(&script); diff --git a/src/parser.rs b/src/parser.rs index 9311171..8f20dfa 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,555 +1,641 @@ +use ariadne::{Color, Fmt, Label, Report, ReportKind, Source}; +use chumsky::{prelude::*, stream::Stream}; +use std::fmt; use crate::ast; -use nom::{ - branch::alt, - bytes::complete::tag, - character::complete::{alpha1, alphanumeric1, char, digit1, multispace0, none_of}, - combinator::{self, cut, map, map_res, not, opt, peek, recognize, value}, - error::VerboseError, - multi::{fold_many0, many0, many1, separated_list0}, - sequence::{delimited, pair, preceded, separated_pair, terminated, tuple}, - Finish, -}; +use crate::Span; -type IResult<'a, O> = nom::IResult<&'a str, O, VerboseError<&'a str>>; +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +enum Token { + Import, + Export, + Fn, + Let, + Memory, + Global, + Mut, + Loop, + BranchIf, + Defer, + As, + Select, + Ident(String), + Str(String), + Int(i32), + Float(String), + Op(String), + Ctrl(char), +} -pub fn parse(s: &str) -> Result> { - let (_, script) = combinator::all_consuming(terminated(script, multispace0))(s).finish()?; +impl fmt::Display for Token { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Token::Import => write!(f, "import"), + Token::Export => write!(f, "export"), + Token::Fn => write!(f, "fn"), + Token::Let => write!(f, "let"), + Token::Memory => write!(f, "memory"), + Token::Global => write!(f, "global"), + Token::Mut => write!(f, "mut"), + Token::Loop => write!(f, "loop"), + Token::BranchIf => write!(f, "branch_if"), + Token::Defer => write!(f, "defer"), + Token::As => write!(f, "as"), + Token::Select => write!(f, "select"), + Token::Ident(s) => write!(f, "{}", s), + Token::Str(s) => write!(f, "{:?}", s), + Token::Int(v) => write!(f, "{}", v), + Token::Float(v) => write!(f, "{}", v), + Token::Op(s) => write!(f, "{}", s), + Token::Ctrl(c) => write!(f, "{}", c), + } + } +} + +pub fn parse(source: &str) -> Result { + let tokens = match lexer().parse(source) { + Ok(tokens) => tokens, + Err(errors) => { + report_errors( + errors + .into_iter() + .map(|e| e.map(|c| c.to_string())) + .collect(), + source, + ); + return Err(()); + } + }; + + let source_len = source.chars().count(); + let script = match script_parser().parse(Stream::from_iter( + source_len..source_len + 1, + tokens.into_iter(), + )) { + Ok(script) => script, + Err(errors) => { + report_errors( + errors + .into_iter() + .map(|e| e.map(|t| t.to_string())) + .collect(), + source, + ); + return Err(()); + } + }; Ok(script) } -fn script(s: &str) -> IResult { - let (s, items) = many0(top_level_item)(s)?; - let mut imports = vec![]; - let mut global_vars = vec![]; - let mut functions = vec![]; - for item in items { - match item { - ast::TopLevelItem::Import(i) => imports.push(i), - ast::TopLevelItem::GlobalVar(v) => global_vars.push(v), - ast::TopLevelItem::Function(f) => functions.push(f), - } - } - Ok(( - s, - ast::Script { - imports, - global_vars, - functions, - }, - )) -} +fn report_errors(errors: Vec>, source: &str) { + for error in errors { + let report = Report::build(ReportKind::Error, (), error.span().start()); -fn top_level_item(s: &str) -> IResult { - alt(( - map(import, ast::TopLevelItem::Import), - map(function, ast::TopLevelItem::Function), - map(global_var, ast::TopLevelItem::GlobalVar), - ))(s) -} - -fn import(s: &str) -> IResult { - let (s, position) = ws(position)(s)?; - let (s, _) = tag("import")(s)?; - let (s, import) = ws(delimited( - char('"'), - recognize(many1(none_of("\""))), - char('"'), - ))(s)?; - let (s, type_) = alt(( - map_res( - preceded( - ws(tag("memory")), - delimited(ws(char('(')), ws(digit1), ws(char(')'))), - ), - |num| num.parse().map(ast::ImportType::Memory), - ), - map( - preceded( - ws(tag("global")), - pair( - pair(opt(ws(tag("mut"))), identifier), - preceded(ws(char(':')), type_), + let report = match error.reason() { + chumsky::error::SimpleReason::Unclosed { span, delimiter } => report + .with_message(format!( + "Unclosed delimiter {}", + delimiter.fg(Color::Yellow) + )) + .with_label( + Label::new(span.clone()) + .with_message(format!( + "Unclosed delimiter {}", + delimiter.fg(Color::Yellow) + )) + .with_color(Color::Yellow), + ) + .with_label( + Label::new(error.span()) + .with_message(format!( + "Must be closed before this {}", + error + .found() + .unwrap_or(&"end of file".to_string()) + .fg(Color::Red) + )) + .with_color(Color::Red), ), + chumsky::error::SimpleReason::Unexpected => report + .with_message(format!( + "{}, expected one of {}", + if error.found().is_some() { + "Unexpected token in input" + } else { + "Unexpted end of input" + }, + if error.expected().len() == 0 { + "end of input".to_string() + } else { + error + .expected() + .map(|x| x.to_string()) + .collect::>() + .join(", ") + } + )) + .with_label( + Label::new(error.span()) + .with_message(format!( + "Unexpected token {}", + error + .found() + .unwrap_or(&"end of file".to_string()) + .fg(Color::Red) + )) + .with_color(Color::Red), + ), + chumsky::error::SimpleReason::Custom(msg) => report.with_message(msg).with_label( + Label::new(error.span()) + .with_message(format!("{}", msg.fg(Color::Red))) + .with_color(Color::Red), ), - |((mutable, name), type_)| ast::ImportType::Variable { - name, + }; + + report.finish().eprint(Source::from(source)).unwrap(); + } +} + +fn lexer() -> impl Parser, Error = Simple> { + let float = text::int(10) + .chain::(just('.').chain(text::digits(10))) + .collect::() + .map(Token::Float); + + let int = text::int(10).map(|s: String| Token::Int(s.parse().unwrap())); + + let str_ = just('"') + .ignore_then(filter(|c| *c != '"').repeated()) + .then_ignore(just('"')) + .collect::() + .map(Token::Str); + + let op = one_of("+-*/%&^|<=>".chars()) + .repeated() + .at_least(1) + .or(just(':').chain(just('='))) + .collect::() + .map(Token::Op); + + let ctrl = one_of("(){};,:?!".chars()).map(Token::Ctrl); + + let ident = text::ident().map(|ident: String| match ident.as_str() { + "import" => Token::Import, + "export" => Token::Export, + "fn" => Token::Fn, + "let" => Token::Let, + "memory" => Token::Memory, + "global" => Token::Global, + "mut" => Token::Mut, + "loop" => Token::Loop, + "branch_if" => Token::BranchIf, + "defer" => Token::Defer, + "as" => Token::As, + "select" => Token::Select, + _ => Token::Ident(ident), + }); + + let single_line = + seq::<_, _, Simple>("//".chars()).then_ignore(take_until(text::newline())); + + let multi_line = + seq::<_, _, Simple>("/*".chars()).then_ignore(take_until(seq("*/".chars()))); + + let comment = single_line.or(multi_line); + + let token = float + .or(int) + .or(str_) + .or(op) + .or(ctrl) + .or(ident) + .recover_with(skip_then_retry_until([])); + + token + .map_with_span(|tok, span| (tok, span)) + .padded() + .padded_by(comment.padded().repeated()) + .repeated() +} + +fn map_token( + f: impl Fn(&Token) -> Option + 'static + Clone, +) -> impl Parser> + Clone { + filter_map(move |span, tok: Token| { + if let Some(output) = f(&tok) { + Ok(output) + } else { + Err(Simple::expected_input_found(span, Vec::new(), Some(tok))) + } + }) +} + +fn block_parser() -> impl Parser> + Clone { + recursive(|block| { + let mut block_expression = None; + let expression = recursive(|expression| { + let val = map_token(|tok| match tok { + Token::Int(v) => Some(ast::Expr::I32Const(*v)), + Token::Float(v) => Some(ast::Expr::F32Const(v.parse().unwrap())), + _ => None, + }) + .labelled("value"); + + let variable = filter_map(|span, tok| match tok { + Token::Ident(id) => Ok(ast::Expr::Variable(id)), + _ => Err(Simple::expected_input_found(span, Vec::new(), Some(tok))), + }) + .labelled("variable"); + + let ident = filter_map(|span, tok| match tok { + Token::Ident(id) => Ok(id), + _ => Err(Simple::expected_input_found(span, Vec::new(), Some(tok))), + }) + .labelled("identifier"); + + let local_tee = ident + .then(just(Token::Op(":=".to_string())).ignore_then(expression.clone())) + .map(|(name, expr)| ast::Expr::LocalTee { + name, + value: Box::new(expr), + }).boxed(); + + let loop_expr = just(Token::Loop) + .ignore_then(ident) + .then( + block + .clone() + .delimited_by(Token::Ctrl('{'), Token::Ctrl('}')), + ) + .map(|(label, block)| ast::Expr::Loop { + label, + block: Box::new(block), + }); + + let block_expr = loop_expr.boxed(); + + block_expression = Some(block_expr.clone()); + + let branch_if = just(Token::BranchIf) + .ignore_then(expression.clone()) + .then_ignore(just(Token::Ctrl(':'))) + .then(ident) + .map(|(condition, label)| ast::Expr::BranchIf { + condition: Box::new(condition), + label, + }).boxed(); + + let let_ = just(Token::Let) + .ignore_then(just(Token::Defer).or_not()) + .then(ident.clone()) + .then(just(Token::Ctrl(':')).ignore_then(type_parser()).or_not()) + .then( + just(Token::Op("=".to_string())) + .ignore_then(expression.clone()) + .or_not(), + ) + .map(|(((defer, name), type_), value)| ast::Expr::Let { + name, + type_, + value: value.map(Box::new), + defer: defer.is_some(), + }).boxed(); + + let tee = ident + .clone() + .then_ignore(just(Token::Op(":=".to_string()))) + .then(expression.clone()) + .map(|(name, value)| ast::Expr::LocalTee { + name, + value: Box::new(value), + }).boxed(); + + let select = just(Token::Select) + .ignore_then( + expression + .clone() + .then_ignore(just(Token::Ctrl(','))) + .then(expression.clone()) + .then_ignore(just(Token::Ctrl(','))) + .then(expression.clone()) + .delimited_by(Token::Ctrl('('), Token::Ctrl(')')), + ) + .map(|((condition, if_true), if_false)| ast::Expr::Select { + condition: Box::new(condition), + if_true: Box::new(if_true), + if_false: Box::new(if_false), + }).boxed(); + + let function_call = ident + .clone() + .then( + expression + .clone() + .separated_by(just(Token::Ctrl(','))) + .delimited_by(Token::Ctrl('('), Token::Ctrl(')')), + ) + .map(|(name, params)| ast::Expr::FuncCall { name, params }).boxed(); + + let atom = val + .or(tee) + .or(function_call) + .or(variable) + .or(local_tee) + .or(block_expr) + .or(branch_if) + .or(let_) + .or(select) + .map_with_span(|expr, span| expr.with_span(span)) + .or(expression + .clone() + .delimited_by(Token::Ctrl('('), Token::Ctrl(')'))) + .recover_with(nested_delimiters( + Token::Ctrl('('), + Token::Ctrl(')'), + [(Token::Ctrl('{'), Token::Ctrl('}'))], + |span| ast::Expr::Error.with_span(span), + )).boxed(); + + let unary_op = just(Token::Op("-".to_string())) + .to(ast::UnaryOp::Negate) + .map_with_span(|op, span| (op, span)) + .repeated() + .then(atom) + .map(|(ops, value)| { + ops.into_iter().rev().fold(value, |acc, (op, span)| { + let span = span.start..acc.span.end; + ast::Expr::UnaryOp { + op, + value: Box::new(acc), + } + .with_span(span) + }) + }).boxed(); + + let op_cast = unary_op + .clone() + .then( + just(Token::As) + .ignore_then(type_parser()) + .map_with_span(|type_, span| (type_, span)) + .repeated(), + ) + .foldl(|value, (type_, span)| { + ast::Expr::Cast { + value: Box::new(value), + type_, + } + .with_span(span) + }).boxed(); + + let mem_size = just(Token::Ctrl('?')) + .to(ast::MemSize::Byte) + .or(just(Token::Ctrl('!')).to(ast::MemSize::Word)); + + let memory_op = op_cast + .clone() + .then( + mem_size + .then(op_cast.clone()) + .then_ignore(just(Token::Op("=".to_string()))) + .then(expression.clone()) + .repeated(), + ) + .foldl(|left, ((size, right), value)| { + let span = left.span.start..value.span.end; + ast::Expr::Poke { + mem_location: ast::MemoryLocation { + span: span.clone(), + left: Box::new(left), + size, + right: Box::new(right), + }, + value: Box::new(value), + } + .with_span(span) + }).boxed(); + + let op_product = memory_op + .clone() + .then( + just(Token::Op("*".to_string())) + .to(ast::BinOp::Mul) + .or(just(Token::Op("/".to_string())).to(ast::BinOp::Div)) + .or(just(Token::Op("%".to_string())).to(ast::BinOp::Rem)) + .then(memory_op.clone()) + .repeated(), + ) + .foldl(|left, (op, right)| { + let span = left.span.start..right.span.end; + ast::Expr::BinOp { + op, + left: Box::new(left), + right: Box::new(right), + } + .with_span(span) + }).boxed(); + + let op_sum = op_product + .clone() + .then( + just(Token::Op("+".to_string())) + .to(ast::BinOp::Add) + .or(just(Token::Op("-".to_string())).to(ast::BinOp::Sub)) + .then(op_product.clone()) + .repeated(), + ) + .foldl(|left, (op, right)| { + let span = left.span.start..right.span.end; + ast::Expr::BinOp { + op, + left: Box::new(left), + right: Box::new(right), + } + .with_span(span) + }).boxed(); + + let op_cmp = op_sum + .clone() + .then( + just(Token::Op("==".to_string())) + .to(ast::BinOp::Eq) + .or(just(Token::Op("!=".to_string())).to(ast::BinOp::Ne)) + .or(just(Token::Op("<".to_string())).to(ast::BinOp::Lt)) + .or(just(Token::Op("<=".to_string())).to(ast::BinOp::Le)) + .or(just(Token::Op(">".to_string())).to(ast::BinOp::Gt)) + .or(just(Token::Op(">=".to_string())).to(ast::BinOp::Ge)) + .then(op_sum.clone()) + .repeated(), + ) + .foldl(|left, (op, right)| { + let span = left.span.start..right.span.end; + ast::Expr::BinOp { + op, + left: Box::new(left), + right: Box::new(right), + } + .with_span(span) + }).boxed(); + + let op_bit = op_cmp + .clone() + .then( + just(Token::Op("&".to_string())) + .to(ast::BinOp::And) + .or(just(Token::Op("|".to_string())).to(ast::BinOp::Or)) + .or(just(Token::Op("^".to_string())).to(ast::BinOp::Xor)) + .then(op_cmp.clone()) + .repeated(), + ) + .foldl(|left, (op, right)| { + let span = left.span.start..right.span.end; + ast::Expr::BinOp { + op, + left: Box::new(left), + right: Box::new(right), + } + .with_span(span) + }).boxed(); + + op_bit + }); + + let block_expression = block_expression.unwrap(); + + expression + .clone() + .then_ignore(just(Token::Ctrl(';'))) + .or(block_expression.map_with_span(|expr, span| expr.with_span(span))) + .repeated() + .then(expression.clone().or_not()) + .map(|(statements, final_expression)| ast::Block { + statements, + final_expression: final_expression.map(|e| Box::new(e)), + }) + }) +} + +fn type_parser() -> impl Parser> + Clone { + filter_map(|span, tok| match tok { + Token::Ident(id) if id == "i32" => Ok(ast::Type::I32), + Token::Ident(id) if id == "i64" => Ok(ast::Type::I64), + Token::Ident(id) if id == "f32" => Ok(ast::Type::F32), + Token::Ident(id) if id == "f64" => Ok(ast::Type::F64), + _ => Err(Simple::expected_input_found( + span, + vec![ + Token::Ident("i32".into()), + Token::Ident("i64".into()), + Token::Ident("f32".into()), + Token::Ident("f64".into()), + ], + Some(tok), + )), + }) +} + +fn top_level_item_parser() -> impl Parser> + Clone { + let integer = map_token(|tok| match tok { + Token::Int(v) => Some(*v), + _ => None, + }); + + let string = map_token(|tok| match tok { + Token::Str(s) => Some(s.clone()), + _ => None, + }); + + let identifier = map_token(|tok| match tok { + Token::Ident(id) => Some(id.clone()), + _ => None, + }); + + let import_memory = just(Token::Memory) + .ignore_then(integer.delimited_by(Token::Ctrl('('), Token::Ctrl(')'))) + .map(|min_size| ast::ImportType::Memory(min_size as u32)); + + let import_global = just(Token::Global) + .ignore_then(just(Token::Mut).or_not()) + .then(identifier.clone()) + .then_ignore(just(Token::Ctrl(':'))) + .then(type_parser()) + .map(|((mut_opt, name), type_)| ast::ImportType::Variable { + mutable: mut_opt.is_some(), + name, + type_, + }); + + let import = just(Token::Import) + .ignore_then(string) + .then(import_memory.or(import_global)) + .then_ignore(just(Token::Ctrl(';'))) + .map_with_span(|(import, type_), span| { + ast::TopLevelItem::Import(ast::Import { + span, + import, type_, - mutable: mutable.is_some(), - }, - ), - ))(s)?; - let (s, _) = ws(char(';'))(s)?; + }) + }); - Ok(( - s, - ast::Import { - position, - import, - type_, - }, - )) -} + let parameter = identifier + .clone() + .then_ignore(just(Token::Ctrl(':'))) + .then(type_parser()); -fn global_var(s: &str) -> IResult { - let (s, _) = ws(tag("global"))(s)?; - let (s, position) = ws(position)(s)?; - let (s, name) = identifier(s)?; - let (s, type_) = preceded(ws(char(':')), type_)(s)?; - let (s, _) = ws(char(';'))(s)?; - - Ok(( - s, - ast::GlobalVar { - position, - name: name, - type_, - }, - )) -} - -fn function(s: &str) -> IResult { - let (s, export) = map(ws(opt(tag("export"))), |e| e.is_some())(s)?; - let (s, _) = ws(tag("fn"))(s)?; - cut(move |s| { - let (s, position) = ws(position)(s)?; - let (s, name) = identifier(s)?; - let (s, params) = delimited( - ws(char('(')), - separated_list0( - ws(char(',')), - pair(map(identifier, |i| i), preceded(ws(tag(":")), type_)), - ), - ws(char(')')), - )(s)?; - let (s, type_) = opt(preceded(ws(tag("->")), type_))(s)?; - let (s, body) = block(s)?; - - Ok(( - s, - ast::Function { - position, - export, - name: name, + let function = just(Token::Export) + .or_not() + .then_ignore(just(Token::Fn)) + .then(identifier.clone()) + .then( + parameter + .separated_by(just(Token::Ctrl(','))) + .delimited_by(Token::Ctrl('('), Token::Ctrl(')')), + ) + .then( + just(Token::Op("->".to_string())) + .ignore_then(type_parser()) + .or_not(), + ) + .then(block_parser().delimited_by(Token::Ctrl('{'), Token::Ctrl('}'))) + .map_with_span(|((((export, name), params), type_), body), span| { + ast::TopLevelItem::Function(ast::Function { + span, params, + export: export.is_some(), + name, type_, body, - }, - )) - })(s) + }) + }); + + let global = just(Token::Global) + .ignore_then(identifier.clone()) + .then_ignore(just(Token::Ctrl(':'))) + .then(type_parser()) + .then_ignore(just(Token::Ctrl(';'))) + .map_with_span(|(name, type_), span| { + ast::TopLevelItem::GlobalVar(ast::GlobalVar { name, type_, span }) + }); + + import.or(function).or(global) } -fn block(s: &str) -> IResult { - let (s, (statements, final_expression)) = delimited( - ws(char('{')), - pair(many0(statement), opt(expression)), - ws(char('}')), - )(s)?; - Ok(( - s, - ast::Block { - statements, - final_expression: final_expression.map(|e| e.into()), - }, - )) -} - -fn statement(s: &str) -> IResult { - alt(( - map(local_var, ast::Statement::LocalVariable), - map(terminated(expression, ws(char(';'))), |e| { - ast::Statement::Expression(e.into()) - }), - map( - terminated(block_expression, not(peek(ws(char('}'))))), - |e| ast::Statement::Expression(e.into()), - ), - map( - terminated( - pair( - mem_location, - cut(ws(pair(position, preceded(char('='), expression)))), - ), - ws(char(';')), - ), - |(mem_location, (position, value))| ast::Statement::Poke { - position, - mem_location, - value: value.into(), - }, - ), - ))(s) -} - -fn local_var(s: &str) -> IResult { - let (s, _) = ws(tag("let"))(s)?; - cut(move |s| { - let (s, defer) = opt(ws(tag("defer")))(s)?; - let (s, position) = ws(position)(s)?; - let (s, name) = identifier(s)?; - let (s, type_) = opt(preceded(ws(char(':')), type_))(s)?; - let (s, value) = opt(preceded(ws(char('=')), expression))(s)?; - let (s, _) = ws(char(';'))(s)?; - - Ok(( - s, - ast::LocalVariable { - position, - name: name, - type_, - value: value.map(|v| v.into()), - defer: defer.is_some(), - }, - )) - })(s) -} - -fn mem_location(s: &str) -> IResult { - let (s, position) = ws(position)(s)?; - let (s, left) = expression(s)?; - let (s, size) = map(ws(alt((char('?'), char('!')))), |op| match op { - '?' => ast::MemSize::Byte, - '!' => ast::MemSize::Word, - _ => unreachable!(), - })(s)?; - let (s, right) = expression(s)?; - - Ok(( - s, - ast::MemoryLocation { - position, - size, - left: left.into(), - right: right.into(), - }, - )) -} - -fn expression(s: &str) -> IResult { - expression_bit(s) -} - -fn expression_atom(s: &str) -> IResult { - alt(( - branch_if, - block_expression, - map( - separated_pair(pair(ws(position), identifier), ws(tag(":=")), expression), - |((position, name), value)| ast::Expr::LocalTee { - position, - name: name, - value: Box::new(value.into()), - }, - ), - map(float, ast::Expr::F32Const), - map(integer, ast::Expr::I32Const), - map( - tuple(( - terminated(ws(position), tag("select")), - preceded(ws(char('(')), expression), - preceded(ws(char(',')), expression), - delimited(ws(char(',')), expression, ws(char(')'))), - )), - |(position, condition, if_true, if_false)| ast::Expr::Select { - position, - condition: Box::new(condition.into()), - if_true: Box::new(if_true.into()), - if_false: Box::new(if_false.into()), - }, - ), - map( - tuple(( - ws(position), - identifier, - delimited( - ws(char('(')), - separated_list0(ws(char(',')), expression), - ws(char(')')), - ), - )), - |(position, name, params)| ast::Expr::FuncCall { - position, - name, - params: params.into_iter().map(|p| p.into()).collect(), - }, - ), - map(ws(pair(position, identifier)), |(position, name)| { - ast::Expr::Variable { - position, - name: name, - } - }), - delimited(ws(char('(')), cut(expression), ws(char(')'))), - ))(s) -} - -fn branch_if(s: &str) -> IResult { - let (s, position) = ws(position)(s)?; - let (s, _) = tag("branch_if")(s)?; - cut(move |s| { - let (s, condition) = expression(s)?; - let (s, _) = ws(char(':'))(s)?; - let (s, label) = identifier(s)?; - - Ok(( - s, - ast::Expr::BranchIf { - position, - condition: Box::new(condition.into()), - label: label, - }, - )) - })(s) -} - -fn expression_cast(s: &str) -> IResult { - let (s, mut init) = map(expression_atom, Some)(s)?; - fold_many0( - pair(ws(terminated(position, tag("as"))), type_), - move || init.take().unwrap(), - |value, (position, type_)| ast::Expr::Cast { - position, - value: Box::new(value.into()), - type_, - }, - )(s) -} - -fn expression_product(s: &str) -> IResult { - let (s, mut init) = map(expression_cast, Some)(s)?; - fold_many0( - pair( - ws(pair(position, alt((char('*'), char('/'), char('%'))))), - expression_cast, - ), - move || init.take().unwrap(), - |left, ((position, op), right)| { - let op = match op { - '*' => ast::BinOp::Mul, - '/' => ast::BinOp::Div, - '%' => ast::BinOp::Rem, - _ => unreachable!(), +fn script_parser() -> impl Parser> + Clone { + top_level_item_parser() + .repeated() + .then_ignore(end()) + .map(|items| { + let mut script = ast::Script { + imports: Vec::new(), + global_vars: Vec::new(), + functions: Vec::new(), }; - ast::Expr::BinOp { - position, - op, - left: Box::new(left.into()), - right: Box::new(right.into()), + for item in items { + match item { + ast::TopLevelItem::Import(i) => script.imports.push(i), + ast::TopLevelItem::GlobalVar(v) => script.global_vars.push(v), + ast::TopLevelItem::Function(f) => script.functions.push(f), + } } - }, - )(s) -} - -fn expression_sum(s: &str) -> IResult { - let (s, mut init) = map(expression_product, Some)(s)?; - fold_many0( - pair( - ws(pair(position, alt((char('+'), char('-'))))), - expression_product, - ), - move || init.take().unwrap(), - |left, ((position, op), right)| { - let op = if op == '+' { - ast::BinOp::Add - } else { - ast::BinOp::Sub - }; - ast::Expr::BinOp { - position, - op, - left: Box::new(left.into()), - right: Box::new(right.into()), - } - }, - )(s) -} - -fn expression_cmp(s: &str) -> IResult { - let (s, mut init) = map(expression_sum, Some)(s)?; - fold_many0( - pair( - ws(pair( - position, - alt(( - tag("=="), - tag("!="), - tag("<="), - tag("<"), - tag(">="), - tag(">"), - )), - )), - expression_sum, - ), - move || init.take().unwrap(), - |left, ((position, op), right)| { - let op = match op { - "==" => ast::BinOp::Eq, - "!=" => ast::BinOp::Ne, - "<=" => ast::BinOp::Le, - "<" => ast::BinOp::Lt, - ">=" => ast::BinOp::Ge, - ">" => ast::BinOp::Gt, - _ => unreachable!(), - }; - ast::Expr::BinOp { - position, - op, - left: Box::new(left.into()), - right: Box::new(right.into()), - } - }, - )(s) -} - -fn expression_bit(s: &str) -> IResult { - let (s, mut init) = map(expression_cmp, Some)(s)?; - fold_many0( - pair( - ws(pair(position, alt((char('&'), char('|'), char('^'))))), - expression_cmp, - ), - move || init.take().unwrap(), - |left, ((position, op), right)| { - let op = match op { - '&' => ast::BinOp::And, - '|' => ast::BinOp::Or, - '^' => ast::BinOp::Xor, - _ => unreachable!(), - }; - ast::Expr::BinOp { - position, - op, - left: Box::new(left.into()), - right: Box::new(right.into()), - } - }, - )(s) -} - -fn block_expression(s: &str) -> IResult { - loop_(s) -} - -fn loop_(s: &str) -> IResult { - let (s, position) = ws(position)(s)?; - let (s, _) = tag("loop")(s)?; - cut(move |s| { - let (s, label) = identifier(s)?; - let (s, block) = block(s)?; - - Ok(( - s, - ast::Expr::Loop { - position, - label: label, - block: Box::new(block.into()), - }, - )) - })(s) -} - -fn integer(s: &str) -> IResult { - ws(map_res( - recognize(pair(opt(char('-')), digit1)), - |n: &str| n.parse::(), - ))(s) -} - -fn float(s: &str) -> IResult { - ws(map_res( - recognize(tuple((opt(char('-')), digit1, char('.'), digit1))), - |n: &str| n.parse::(), - ))(s) -} - -fn type_(s: &str) -> IResult { - ws(alt(( - value(ast::Type::I32, tag("i32")), - value(ast::Type::I64, tag("i64")), - value(ast::Type::F32, tag("f32")), - value(ast::Type::F64, tag("f64")), - )))(s) -} - -fn identifier(s: &str) -> IResult<&str> { - ws(recognize(pair( - alt((alpha1, tag("_"))), - many0(alt((alphanumeric1, tag("_")))), - )))(s) -} - -fn position(s: &str) -> IResult { - Ok((s, ast::Position(s.len()))) -} - -fn ws<'a, F: 'a, O>(inner: F) -> impl FnMut(&'a str) -> IResult -where - F: FnMut(&'a str) -> IResult<'a, O>, -{ - preceded(multispace0, inner) -} - -#[cfg(test)] -mod test { - use nom::combinator::all_consuming; - - #[test] - fn identifier() { - all_consuming(super::identifier)("_froobaz123").unwrap(); - } - - #[test] - fn type_() { - all_consuming(super::type_)("i32").unwrap(); - all_consuming(super::type_)("i64").unwrap(); - all_consuming(super::type_)("f32").unwrap(); - all_consuming(super::type_)("f64").unwrap(); - } - - #[test] - fn integer() { - all_consuming(super::integer)("123").unwrap(); - all_consuming(super::integer)("-123").unwrap(); - } - - #[test] - fn local_var() { - all_consuming(super::local_var)("let foo: i32;").unwrap(); - all_consuming(super::local_var)("let bar = 42;").unwrap(); - } - - #[test] - fn function() { - all_consuming(super::function)("export fn foo(a: i32, b: f32) -> i32 { let x = 42; x }") - .unwrap(); - } - - #[test] - fn loop_() { - all_consuming(super::loop_)("loop foo { 42 }").unwrap(); - all_consuming(super::loop_)("loop foo { i?64 = (i % 320 + time / 10) ^ (i / 320); }") - .unwrap(); - } - - #[test] - fn block() { - all_consuming(super::block)("{loop frame {}}").unwrap(); - } - - #[test] - fn expression() { - all_consuming(super::expression)("foo + 2 * (bar ^ 23)").unwrap(); - all_consuming(super::expression)("i := i + 1").unwrap(); - all_consuming(super::expression)("(i := i + 1)").unwrap(); - } - - #[test] - fn poke() { - all_consuming(super::statement)("i?64 = (i % 320 + time / 10) ^ (i / 320);").unwrap(); - } - - #[test] - fn branch_if() { - all_consuming(super::branch_if)("branch_if (i := i + 1) < 10: foo").unwrap(); - } + script + }) } diff --git a/src/parser2.rs b/src/parser2.rs deleted file mode 100644 index 71f7368..0000000 --- a/src/parser2.rs +++ /dev/null @@ -1,830 +0,0 @@ -use ariadne::{Color, Fmt, Label, Report, ReportKind, Source}; -use chumsky::{prelude::*, stream::Stream}; -use std::fmt; - -pub type Span = std::ops::Range; - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -enum Token { - Import, - Export, - Fn, - Let, - Memory, - Global, - Mut, - Loop, - BranchIf, - Defer, - As, - Select, - Ident(String), - Str(String), - Int(i32), - Float(String), - Op(String), - Ctrl(char), -} - -impl fmt::Display for Token { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Token::Import => write!(f, "import"), - Token::Export => write!(f, "export"), - Token::Fn => write!(f, "fn"), - Token::Let => write!(f, "let"), - Token::Memory => write!(f, "memory"), - Token::Global => write!(f, "global"), - Token::Mut => write!(f, "mut"), - Token::Loop => write!(f, "loop"), - Token::BranchIf => write!(f, "branch_if"), - Token::Defer => write!(f, "defer"), - Token::As => write!(f, "as"), - Token::Select => write!(f, "select"), - Token::Ident(s) => write!(f, "{}", s), - Token::Str(s) => write!(f, "{:?}", s), - Token::Int(v) => write!(f, "{}", v), - Token::Float(v) => write!(f, "{}", v), - Token::Op(s) => write!(f, "{}", s), - Token::Ctrl(c) => write!(f, "{}", c), - } - } -} - -pub fn parse(source: &str) -> Result<(), ()> { - let tokens = match lexer().parse(source) { - Ok(tokens) => tokens, - Err(errors) => { - report_errors( - errors - .into_iter() - .map(|e| e.map(|c| c.to_string())) - .collect(), - source, - ); - return Err(()); - } - }; - - let source_len = source.chars().count(); - let script = match script_parser().parse(Stream::from_iter( - source_len..source_len + 1, - tokens.into_iter(), - )) { - Ok(script) => script, - Err(errors) => { - report_errors( - errors - .into_iter() - .map(|e| e.map(|t| t.to_string())) - .collect(), - source, - ); - return Err(()); - } - }; - dbg!(script); - Ok(()) -} - -fn report_errors(errors: Vec>, source: &str) { - for error in errors { - let report = Report::build(ReportKind::Error, (), error.span().start()); - - let report = match error.reason() { - chumsky::error::SimpleReason::Unclosed { span, delimiter } => report - .with_message(format!( - "Unclosed delimiter {}", - delimiter.fg(Color::Yellow) - )) - .with_label( - Label::new(span.clone()) - .with_message(format!( - "Unclosed delimiter {}", - delimiter.fg(Color::Yellow) - )) - .with_color(Color::Yellow), - ) - .with_label( - Label::new(error.span()) - .with_message(format!( - "Must be closed before this {}", - error - .found() - .unwrap_or(&"end of file".to_string()) - .fg(Color::Red) - )) - .with_color(Color::Red), - ), - chumsky::error::SimpleReason::Unexpected => report - .with_message(format!( - "{}, expected one of {}", - if error.found().is_some() { - "Unexpected token in input" - } else { - "Unexpted end of input" - }, - if error.expected().len() == 0 { - "end of input".to_string() - } else { - error - .expected() - .map(|x| x.to_string()) - .collect::>() - .join(", ") - } - )) - .with_label( - Label::new(error.span()) - .with_message(format!( - "Unexpected token {}", - error - .found() - .unwrap_or(&"end of file".to_string()) - .fg(Color::Red) - )) - .with_color(Color::Red), - ), - chumsky::error::SimpleReason::Custom(msg) => report.with_message(msg).with_label( - Label::new(error.span()) - .with_message(format!("{}", msg.fg(Color::Red))) - .with_color(Color::Red), - ), - }; - - report.finish().eprint(Source::from(source)).unwrap(); - } -} - -fn lexer() -> impl Parser, Error = Simple> { - let float = text::int(10) - .chain::(just('.').chain(text::digits(10))) - .collect::() - .map(Token::Float); - - let int = text::int(10).map(|s: String| Token::Int(s.parse().unwrap())); - - let str_ = just('"') - .ignore_then(filter(|c| *c != '"').repeated()) - .then_ignore(just('"')) - .collect::() - .map(Token::Str); - - let op = one_of("+-*/%&^|<=>".chars()) - .repeated() - .at_least(1) - .or(just(':').chain(just('='))) - .collect::() - .map(Token::Op); - - let ctrl = one_of("(){};,:?!".chars()).map(Token::Ctrl); - - let ident = text::ident().map(|ident: String| match ident.as_str() { - "import" => Token::Import, - "export" => Token::Export, - "fn" => Token::Fn, - "let" => Token::Let, - "memory" => Token::Memory, - "global" => Token::Global, - "mut" => Token::Mut, - "loop" => Token::Loop, - "branch_if" => Token::BranchIf, - "defer" => Token::Defer, - "as" => Token::As, - "select" => Token::Select, - _ => Token::Ident(ident), - }); - - let single_line = - seq::<_, _, Simple>("//".chars()).then_ignore(take_until(text::newline())); - - let multi_line = - seq::<_, _, Simple>("/*".chars()).then_ignore(take_until(seq("*/".chars()))); - - let comment = single_line.or(multi_line); - - let token = float - .or(int) - .or(str_) - .or(op) - .or(ctrl) - .or(ident) - .recover_with(skip_then_retry_until([])); - - token - .map_with_span(|tok, span| (tok, span)) - .padded() - .padded_by(comment.padded().repeated()) - .repeated() -} - -mod ast { - use super::Span; - - #[derive(Debug)] - pub struct Script { - pub imports: Vec, - pub global_vars: Vec, - pub functions: Vec, - } - - #[derive(Debug)] - pub enum TopLevelItem { - Import(Import), - GlobalVar(GlobalVar), - Function(Function), - } - - #[derive(Debug)] - pub struct Import { - pub span: Span, - pub import: String, - pub type_: ImportType, - } - - #[derive(Debug)] - pub enum ImportType { - Memory(u32), - Variable { - name: String, - type_: Type, - mutable: bool, - }, - // Function { name: String, params: Vec, result: Option } - } - - #[derive(Debug)] - pub struct GlobalVar { - pub span: Span, - pub name: String, - pub type_: Type, - } - - #[derive(Debug)] - pub struct Function { - pub span: Span, - pub export: bool, - pub name: String, - pub params: Vec<(String, Type)>, - pub type_: Option, - pub body: Block, - } - - #[derive(Debug)] - pub struct Block { - pub statements: Vec, - pub final_expression: Option>, - } - - impl Block { - pub fn type_(&self) -> Option { - self.final_expression.as_ref().and_then(|e| e.type_) - } - } - - #[derive(Debug)] - pub struct MemoryLocation { - pub span: Span, - pub size: MemSize, - pub left: Box, - pub right: Box, - } - - #[derive(Debug)] - pub struct LocalVariable { - pub span: Span, - pub name: String, - pub type_: Option, - pub value: Option, - pub defer: bool, - } - - #[derive(Debug)] - pub struct Expression { - pub type_: Option, - pub expr: Expr, - pub span: Span, - } - - #[derive(Debug)] - pub enum Expr { - I32Const(i32), - F32Const(f32), - Variable(String), - Let { - name: String, - type_: Option, - value: Option>, - defer: bool, - }, - Poke { - mem_location: MemoryLocation, - value: Box, - }, - Loop { - label: String, - block: Box, - }, - BranchIf { - condition: Box, - label: String, - }, - UnaryOp { - op: UnaryOp, - value: Box, - }, - BinOp { - op: BinOp, - left: Box, - right: Box, - }, - LocalTee { - name: String, - value: Box, - }, - Cast { - value: Box, - type_: Type, - }, - FuncCall { - name: String, - params: Vec, - }, - Select { - condition: Box, - if_true: Box, - if_false: Box, - }, - Error, - } - - impl Expr { - pub fn with_span(self, span: Span) -> Expression { - Expression { - type_: None, - expr: self, - span: span, - } - } - } - - #[derive(Debug, Clone, Copy)] - pub enum UnaryOp { - Negate, - } - - #[derive(Debug, Clone, Copy)] - pub enum BinOp { - Add, - Sub, - Mul, - Div, - Rem, - And, - Or, - Xor, - Eq, - Ne, - Gt, - Ge, - Lt, - Le, - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub enum MemSize { - Byte, - Word, - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] - pub enum Type { - I32, - I64, - F32, - F64, - } -} - -fn map_token( - f: impl Fn(&Token) -> Option + 'static + Clone, -) -> impl Parser> + Clone { - filter_map(move |span, tok: Token| { - if let Some(output) = f(&tok) { - Ok(output) - } else { - Err(Simple::expected_input_found(span, Vec::new(), Some(tok))) - } - }) -} - -fn block_parser() -> impl Parser> + Clone { - recursive(|block| { - let mut block_expression = None; - let expression = recursive(|expression| { - let val = map_token(|tok| match tok { - Token::Int(v) => Some(ast::Expr::I32Const(*v)), - Token::Float(v) => Some(ast::Expr::F32Const(v.parse().unwrap())), - _ => None, - }) - .labelled("value"); - - let variable = filter_map(|span, tok| match tok { - Token::Ident(id) => Ok(ast::Expr::Variable(id)), - _ => Err(Simple::expected_input_found(span, Vec::new(), Some(tok))), - }) - .labelled("variable"); - - let ident = filter_map(|span, tok| match tok { - Token::Ident(id) => Ok(id), - _ => Err(Simple::expected_input_found(span, Vec::new(), Some(tok))), - }) - .labelled("identifier"); - - let local_tee = ident - .then(just(Token::Op(":=".to_string())).ignore_then(expression.clone())) - .map(|(name, expr)| ast::Expr::LocalTee { - name, - value: Box::new(expr), - }).boxed(); - - let loop_expr = just(Token::Loop) - .ignore_then(ident) - .then( - block - .clone() - .delimited_by(Token::Ctrl('{'), Token::Ctrl('}')), - ) - .map(|(label, block)| ast::Expr::Loop { - label, - block: Box::new(block), - }); - - let block_expr = loop_expr.boxed(); - - block_expression = Some(block_expr.clone()); - - let branch_if = just(Token::BranchIf) - .ignore_then(expression.clone()) - .then_ignore(just(Token::Ctrl(':'))) - .then(ident) - .map(|(condition, label)| ast::Expr::BranchIf { - condition: Box::new(condition), - label, - }).boxed(); - - let let_ = just(Token::Let) - .ignore_then(just(Token::Defer).or_not()) - .then(ident.clone()) - .then(just(Token::Ctrl(':')).ignore_then(type_parser()).or_not()) - .then( - just(Token::Op("=".to_string())) - .ignore_then(expression.clone()) - .or_not(), - ) - .map(|(((defer, name), type_), value)| ast::Expr::Let { - name, - type_, - value: value.map(Box::new), - defer: defer.is_some(), - }).boxed(); - - let tee = ident - .clone() - .then_ignore(just(Token::Op(":=".to_string()))) - .then(expression.clone()) - .map(|(name, value)| ast::Expr::LocalTee { - name, - value: Box::new(value), - }).boxed(); - - let select = just(Token::Select) - .ignore_then( - expression - .clone() - .then_ignore(just(Token::Ctrl(','))) - .then(expression.clone()) - .then_ignore(just(Token::Ctrl(','))) - .then(expression.clone()) - .delimited_by(Token::Ctrl('('), Token::Ctrl(')')), - ) - .map(|((condition, if_true), if_false)| ast::Expr::Select { - condition: Box::new(condition), - if_true: Box::new(if_true), - if_false: Box::new(if_false), - }).boxed(); - - let function_call = ident - .clone() - .then( - expression - .clone() - .separated_by(just(Token::Ctrl(','))) - .delimited_by(Token::Ctrl('('), Token::Ctrl(')')), - ) - .map(|(name, params)| ast::Expr::FuncCall { name, params }).boxed(); - - let atom = val - .or(tee) - .or(function_call) - .or(variable) - .or(local_tee) - .or(block_expr) - .or(branch_if) - .or(let_) - .or(select) - .map_with_span(|expr, span| expr.with_span(span)) - .or(expression - .clone() - .delimited_by(Token::Ctrl('('), Token::Ctrl(')'))) - .recover_with(nested_delimiters( - Token::Ctrl('('), - Token::Ctrl(')'), - [(Token::Ctrl('{'), Token::Ctrl('}'))], - |span| ast::Expr::Error.with_span(span), - )).boxed(); - - let unary_op = just(Token::Op("-".to_string())) - .to(ast::UnaryOp::Negate) - .map_with_span(|op, span| (op, span)) - .repeated() - .then(atom) - .map(|(ops, value)| { - ops.into_iter().rev().fold(value, |acc, (op, span)| { - let span = span.start..acc.span.end; - ast::Expr::UnaryOp { - op, - value: Box::new(acc), - } - .with_span(span) - }) - }).boxed(); - - let op_cast = unary_op - .clone() - .then( - just(Token::As) - .ignore_then(type_parser()) - .map_with_span(|type_, span| (type_, span)) - .repeated(), - ) - .foldl(|value, (type_, span)| { - ast::Expr::Cast { - value: Box::new(value), - type_, - } - .with_span(span) - }).boxed(); - - let mem_size = just(Token::Ctrl('?')) - .to(ast::MemSize::Byte) - .or(just(Token::Ctrl('!')).to(ast::MemSize::Word)); - - let memory_op = op_cast - .clone() - .then( - mem_size - .then(op_cast.clone()) - .then_ignore(just(Token::Op("=".to_string()))) - .then(expression.clone()) - .repeated(), - ) - .foldl(|left, ((size, right), value)| { - let span = left.span.start..value.span.end; - ast::Expr::Poke { - mem_location: ast::MemoryLocation { - span: span.clone(), - left: Box::new(left), - size, - right: Box::new(right), - }, - value: Box::new(value), - } - .with_span(span) - }).boxed(); - - let op_product = memory_op - .clone() - .then( - just(Token::Op("*".to_string())) - .to(ast::BinOp::Mul) - .or(just(Token::Op("/".to_string())).to(ast::BinOp::Div)) - .or(just(Token::Op("%".to_string())).to(ast::BinOp::Rem)) - .then(memory_op.clone()) - .repeated(), - ) - .foldl(|left, (op, right)| { - let span = left.span.start..right.span.end; - ast::Expr::BinOp { - op, - left: Box::new(left), - right: Box::new(right), - } - .with_span(span) - }).boxed(); - - let op_sum = op_product - .clone() - .then( - just(Token::Op("+".to_string())) - .to(ast::BinOp::Add) - .or(just(Token::Op("-".to_string())).to(ast::BinOp::Sub)) - .then(op_product.clone()) - .repeated(), - ) - .foldl(|left, (op, right)| { - let span = left.span.start..right.span.end; - ast::Expr::BinOp { - op, - left: Box::new(left), - right: Box::new(right), - } - .with_span(span) - }).boxed(); - - let op_cmp = op_sum - .clone() - .then( - just(Token::Op("==".to_string())) - .to(ast::BinOp::Eq) - .or(just(Token::Op("!=".to_string())).to(ast::BinOp::Ne)) - .or(just(Token::Op("<".to_string())).to(ast::BinOp::Lt)) - .or(just(Token::Op("<=".to_string())).to(ast::BinOp::Le)) - .or(just(Token::Op(">".to_string())).to(ast::BinOp::Gt)) - .or(just(Token::Op(">=".to_string())).to(ast::BinOp::Ge)) - .then(op_sum.clone()) - .repeated(), - ) - .foldl(|left, (op, right)| { - let span = left.span.start..right.span.end; - ast::Expr::BinOp { - op, - left: Box::new(left), - right: Box::new(right), - } - .with_span(span) - }).boxed(); - - let op_bit = op_cmp - .clone() - .then( - just(Token::Op("&".to_string())) - .to(ast::BinOp::And) - .or(just(Token::Op("|".to_string())).to(ast::BinOp::Or)) - .or(just(Token::Op("^".to_string())).to(ast::BinOp::Xor)) - .then(op_cmp.clone()) - .repeated(), - ) - .foldl(|left, (op, right)| { - let span = left.span.start..right.span.end; - ast::Expr::BinOp { - op, - left: Box::new(left), - right: Box::new(right), - } - .with_span(span) - }).boxed(); - - op_bit - }); - - let block_expression = block_expression.unwrap(); - - expression - .clone() - .then_ignore(just(Token::Ctrl(';'))) - .or(block_expression.map_with_span(|expr, span| expr.with_span(span))) - .repeated() - .then(expression.clone().or_not()) - .map(|(statements, final_expression)| ast::Block { - statements, - final_expression: final_expression.map(|e| Box::new(e)), - }) - }) -} - -fn type_parser() -> impl Parser> + Clone { - filter_map(|span, tok| match tok { - Token::Ident(id) if id == "i32" => Ok(ast::Type::I32), - Token::Ident(id) if id == "i64" => Ok(ast::Type::I64), - Token::Ident(id) if id == "f32" => Ok(ast::Type::F32), - Token::Ident(id) if id == "f64" => Ok(ast::Type::F64), - _ => Err(Simple::expected_input_found( - span, - vec![ - Token::Ident("i32".into()), - Token::Ident("i64".into()), - Token::Ident("f32".into()), - Token::Ident("f64".into()), - ], - Some(tok), - )), - }) -} - -fn top_level_item_parser() -> impl Parser> + Clone { - let integer = map_token(|tok| match tok { - Token::Int(v) => Some(*v), - _ => None, - }); - - let string = map_token(|tok| match tok { - Token::Str(s) => Some(s.clone()), - _ => None, - }); - - let identifier = map_token(|tok| match tok { - Token::Ident(id) => Some(id.clone()), - _ => None, - }); - - let import_memory = just(Token::Memory) - .ignore_then(integer.delimited_by(Token::Ctrl('('), Token::Ctrl(')'))) - .map(|min_size| ast::ImportType::Memory(min_size as u32)); - - let import_global = just(Token::Global) - .ignore_then(just(Token::Mut).or_not()) - .then(identifier.clone()) - .then_ignore(just(Token::Ctrl(':'))) - .then(type_parser()) - .map(|((mut_opt, name), type_)| ast::ImportType::Variable { - mutable: mut_opt.is_some(), - name, - type_, - }); - - let import = just(Token::Import) - .ignore_then(string) - .then(import_memory.or(import_global)) - .then_ignore(just(Token::Ctrl(';'))) - .map_with_span(|(import, type_), span| { - ast::TopLevelItem::Import(ast::Import { - span, - import, - type_, - }) - }); - - let parameter = identifier - .clone() - .then_ignore(just(Token::Ctrl(':'))) - .then(type_parser()); - - let function = just(Token::Export) - .or_not() - .then_ignore(just(Token::Fn)) - .then(identifier.clone()) - .then( - parameter - .separated_by(just(Token::Ctrl(','))) - .delimited_by(Token::Ctrl('('), Token::Ctrl(')')), - ) - .then( - just(Token::Op("->".to_string())) - .ignore_then(type_parser()) - .or_not(), - ) - .then(block_parser().delimited_by(Token::Ctrl('{'), Token::Ctrl('}'))) - .map_with_span(|((((export, name), params), type_), body), span| { - ast::TopLevelItem::Function(ast::Function { - span, - params, - export: export.is_some(), - name, - type_, - body, - }) - }); - - let global = just(Token::Global) - .ignore_then(identifier.clone()) - .then_ignore(just(Token::Ctrl(':'))) - .then(type_parser()) - .then_ignore(just(Token::Ctrl(';'))) - .map_with_span(|(name, type_), span| { - ast::TopLevelItem::GlobalVar(ast::GlobalVar { name, type_, span }) - }); - - import.or(function).or(global) -} - -fn script_parser() -> impl Parser> + Clone { - top_level_item_parser() - .repeated() - .then_ignore(end()) - .map(|items| { - let mut script = ast::Script { - imports: Vec::new(), - global_vars: Vec::new(), - functions: Vec::new(), - }; - for item in items { - match item { - ast::TopLevelItem::Import(i) => script.imports.push(i), - ast::TopLevelItem::GlobalVar(v) => script.global_vars.push(v), - ast::TopLevelItem::Function(f) => script.functions.push(f), - } - } - script - }) -} diff --git a/src/typecheck.rs b/src/typecheck.rs index 7111e4b..e3ec209 100644 --- a/src/typecheck.rs +++ b/src/typecheck.rs @@ -1,34 +1,49 @@ +use ariadne::{Color, Label, Report, ReportKind, Source}; use std::collections::HashMap; use crate::ast; +use crate::Span; use ast::Type::*; -#[derive(Debug)] -pub struct Error { - pub position: ast::Position, - pub message: String, +type Result = std::result::Result; + +struct Var { + span: Span, + type_: ast::Type, } +type Vars = HashMap; -type Result = std::result::Result; - -type Vars<'a> = HashMap<&'a str, ast::Type>; - -pub fn tc_script(script: &mut ast::Script) -> Result<()> { +pub fn tc_script(script: &mut ast::Script, source: &str) -> Result<()> { let mut context = Context { + source, global_vars: HashMap::new(), local_vars: HashMap::new(), + block_stack: Vec::new(), }; + let mut result = Ok(()); + for import in &script.imports { match import.type_ { - ast::ImportType::Variable { name, type_, .. } => { - if context.global_vars.contains_key(name) { - return Err(Error { - position: import.position, - message: "Duplicate global variable".into(), - }); + ast::ImportType::Variable { + ref name, type_, .. + } => { + if let Some(Var { span, .. }) = context.global_vars.get(name) { + result = report_duplicate_definition( + "Global already defined", + &import.span, + span, + source, + ); + } else { + context.global_vars.insert( + name.clone(), + Var { + type_, + span: import.span.clone(), + }, + ); } - context.global_vars.insert(name, type_); } // ast::ImportType::Function { .. } => todo!(), ast::ImportType::Memory(..) => (), @@ -36,120 +51,261 @@ pub fn tc_script(script: &mut ast::Script) -> Result<()> { } for v in &script.global_vars { - if context.global_vars.contains_key(v.name) { - return Err(Error { - position: v.position, - message: "Duplicate global variable".into(), - }); + if let Some(Var { span, .. }) = context.global_vars.get(&v.name) { + result = report_duplicate_definition("Global already defined", &v.span, span, source); + } else { + context.global_vars.insert( + v.name.clone(), + Var { + type_: v.type_, + span: v.span.clone(), + }, + ); } - context.global_vars.insert(v.name, v.type_); } for f in &mut script.functions { context.local_vars.clear(); for (name, type_) in &f.params { - if context.local_vars.contains_key(name) || context.global_vars.contains_key(name) { - return Err(Error { - position: f.position, - message: format!("Variable already defined '{}'", name), - }); + if let Some(Var { span, .. }) = context + .local_vars + .get(name) + .or_else(|| context.global_vars.get(name)) + { + result = + report_duplicate_definition("Variable already defined", &f.span, span, source); + } else { + context.local_vars.insert( + name.clone(), + Var { + type_: *type_, + span: f.span.clone(), + }, + ); } - context.local_vars.insert(name, *type_); } tc_block(&mut context, &mut f.body)?; } - Ok(()) + result } struct Context<'a> { - global_vars: Vars<'a>, - local_vars: Vars<'a>, + source: &'a str, + global_vars: Vars, + local_vars: Vars, + block_stack: Vec, } -fn tc_block<'a>(context: &mut Context<'a>, block: &mut ast::Block<'a>) -> Result<()> { +fn tc_block(context: &mut Context, block: &mut ast::Block) -> Result<()> { + let mut result = Ok(()); for stmt in &mut block.statements { - match *stmt { - ast::Statement::Expression(ref mut expr) => tc_expression(context, expr)?, - ast::Statement::LocalVariable(ref mut lv) => { - if let Some(ref mut value) = lv.value { - tc_expression(context, value)?; - if lv.type_.is_none() { - lv.type_ = value.type_; - } else if lv.type_ != value.type_ { - return Err(Error { - position: lv.position, - message: "Mismatched types".into(), - }); - } - } - if let Some(type_) = lv.type_ { - if context.local_vars.contains_key(lv.name) - || context.global_vars.contains_key(lv.name) - { - return Err(Error { - position: lv.position, - message: format!("Variable '{}' already defined", lv.name), - }); - } - context.local_vars.insert(lv.name, type_); - } else { - return Err(Error { - position: lv.position, - message: "Missing type".into(), - }); - } - } - ast::Statement::Poke { - position, - ref mut mem_location, - ref mut value, - } => { - tc_mem_location(context, mem_location)?; - tc_expression(context, value)?; - if value.type_ != Some(I32) { - return Err(Error { - position, - message: "Type mismatch".into(), - }); - } - } + if tc_expression(context, stmt).is_err() { + result = Err(()); } } if let Some(ref mut expr) = block.final_expression { tc_expression(context, expr)?; } - Ok(()) + result } -fn tc_expression<'a>(context: &mut Context<'a>, expr: &mut ast::Expression<'a>) -> Result<()> { +fn report_duplicate_definition( + msg: &str, + span: &Span, + prev_span: &Span, + source: &str, +) -> Result<()> { + Report::build(ReportKind::Error, (), span.start) + .with_message(msg) + .with_label( + Label::new(span.clone()) + .with_message(msg) + .with_color(Color::Red), + ) + .with_label( + Label::new(prev_span.clone()) + .with_message("Previous definition was here") + .with_color(Color::Yellow), + ) + .finish() + .eprint(Source::from(source)) + .unwrap(); + Err(()) +} + +fn type_mismatch( + type1: ast::Type, + span1: &Span, + type2: Option, + span2: &Span, + source: &str, +) -> Result<()> { + Report::build(ReportKind::Error, (), span2.start) + .with_message("Type mismatch") + .with_label( + Label::new(span1.clone()) + .with_message(format!("Expected type {:?}...", type1)) + .with_color(Color::Yellow), + ) + .with_label( + Label::new(span2.clone()) + .with_message(format!( + "...but found type {}", + type2 + .map(|t| format!("{:?}", t)) + .unwrap_or("void".to_string()) + )) + .with_color(Color::Red), + ) + .finish() + .eprint(Source::from(source)) + .unwrap(); + Err(()) +} + +fn expected_type(span: &Span, source: &str) -> Result<()> { + Report::build(ReportKind::Error, (), span.start) + .with_message("Expected value but found expression of type void") + .with_label( + Label::new(span.clone()) + .with_message("Expected value but found expression of type void") + .with_color(Color::Red), + ) + .finish() + .eprint(Source::from(source)) + .unwrap(); + Err(()) +} + +fn unknown_variable(span: &Span, source: &str) -> Result<()> { + Report::build(ReportKind::Error, (), span.start) + .with_message("Unknown variable") + .with_label( + Label::new(span.clone()) + .with_message("Unknown variable") + .with_color(Color::Red), + ) + .finish() + .eprint(Source::from(source)) + .unwrap(); + Err(()) +} + +fn tc_expression(context: &mut Context, expr: &mut ast::Expression) -> Result<()> { expr.type_ = match expr.expr { + ast::Expr::Let { + ref mut value, + ref mut type_, + ref name, + .. + } => { + if let Some(ref mut value) = value { + tc_expression(context, value)?; + if let Some(type_) = type_ { + if Some(*type_) != value.type_ { + return type_mismatch( + *type_, + &expr.span, + value.type_, + &value.span, + context.source, + ); + } + } else if value.type_.is_none() { + return expected_type(&value.span, context.source); + } else { + *type_ = value.type_; + } + } + if let Some(type_) = type_ { + if let Some(Var { span, .. }) = context + .local_vars + .get(name) + .or_else(|| context.global_vars.get(name)) + { + return report_duplicate_definition( + "Variable already defined", + &expr.span, + span, + context.source, + ); + } + context.local_vars.insert( + name.clone(), + Var { + type_: *type_, + span: expr.span.clone(), + }, + ); + } else { + Report::build(ReportKind::Error, (), expr.span.start) + .with_message("Type missing") + .with_label( + Label::new(expr.span.clone()) + .with_message("Type missing") + .with_color(Color::Red), + ) + .finish() + .eprint(Source::from(context.source)) + .unwrap(); + return Err(()); + } + None + } + ast::Expr::Poke { + ref mut mem_location, + ref mut value, + } => { + tc_mem_location(context, mem_location)?; + tc_expression(context, value)?; + if value.type_ != Some(I32) { + return type_mismatch(I32, &expr.span, value.type_, &value.span, context.source); + } + None + } ast::Expr::I32Const(_) => Some(ast::Type::I32), ast::Expr::F32Const(_) => Some(ast::Type::F32), + ast::Expr::UnaryOp { + op: _, + ref mut value, + } => { + tc_expression(context, value)?; + todo!(); + } ast::Expr::BinOp { - position, op, ref mut left, ref mut right, } => { tc_expression(context, left)?; tc_expression(context, right)?; - if left.type_.is_none() || left.type_ != right.type_ { - return Err(Error { - position, - message: "Type mismatch".into(), - }); + if let Some(type_) = left.type_ { + if left.type_ != right.type_ { + return type_mismatch( + type_, + &left.span, + right.type_, + &right.span, + context.source, + ); + } + } else { + return expected_type(&left.span, context.source); } use ast::BinOp::*; match op { Add | Sub | Mul | Div => left.type_, Rem | And | Or | Xor => { if left.type_ != Some(I32) { - return Err(Error { - position, - message: "Unsupported type".into(), - }); + return type_mismatch( + I32, + &left.span, + left.type_, + &left.span, + context.source, + ); } else { left.type_ } @@ -157,117 +313,136 @@ fn tc_expression<'a>(context: &mut Context<'a>, expr: &mut ast::Expression<'a>) Eq | Ne | Lt | Le | Gt | Ge => Some(I32), } } - ast::Expr::Variable { position, name } => { - if let Some(&type_) = context + ast::Expr::Variable(ref name) => { + if let Some(&Var { type_, .. }) = context .global_vars .get(name) .or_else(|| context.local_vars.get(name)) { Some(type_) } else { - return Err(Error { - position, - message: format!("Variable '{}' not found", name), - }); + return unknown_variable(&expr.span, context.source); } } ast::Expr::LocalTee { - position, - name, + ref name, ref mut value, } => { tc_expression(context, value)?; - if let Some(&type_) = context.local_vars.get(name) { + if let Some(&Var { + type_, ref span, .. + }) = context.local_vars.get(name) + { if value.type_ != Some(type_) { - return Err(Error { - position, - message: "Type mismatch".into(), - }); + return type_mismatch(type_, span, value.type_, &value.span, context.source); } Some(type_) } else { - return Err(Error { - position, - message: format!("No local variable '{}' found", name), - }); + return unknown_variable(&expr.span, context.source); } } ast::Expr::Loop { - position: _, - label: _, + ref label, ref mut block, } => { + context.block_stack.push(label.clone()); tc_block(context, block)?; + context.block_stack.pop(); block.final_expression.as_ref().and_then(|e| e.type_) } ast::Expr::BranchIf { - position, ref mut condition, - label: _, + ref label, } => { tc_expression(context, condition)?; if condition.type_ != Some(I32) { - return Err(Error { - position, - message: "Condition has to be i32".into(), - }); + return type_mismatch( + I32, + &expr.span, + condition.type_, + &condition.span, + context.source, + ); + } + if !context.block_stack.contains(label) { + Report::build(ReportKind::Error, (), expr.span.start) + .with_message("Label not found") + .with_label( + Label::new(expr.span.clone()) + .with_message("Label not found") + .with_color(Color::Red), + ) + .finish() + .eprint(Source::from(context.source)) + .unwrap(); + return Err(()); } None } ast::Expr::Cast { - position, ref mut value, type_, } => { tc_expression(context, value)?; if value.type_.is_none() { - return Err(Error { - position, - message: "Cannot cast void".into(), - }); + return expected_type(&expr.span, context.source); } Some(type_) } ast::Expr::FuncCall { - position, - name, + ref name, ref mut params, } => { if let Some((ptypes, rtype)) = builtin_function_types(name) { if params.len() != ptypes.len() { - return Err(Error { - position, - message: format!( + Report::build(ReportKind::Error, (), expr.span.start) + .with_message(format!( "Expected {} parameters but found {}", ptypes.len(), params.len() - ), - }); + )) + .with_label( + Label::new(expr.span.clone()) + .with_message(format!( + "Expected {} parameters but found {}", + ptypes.len(), + params.len() + )) + .with_color(Color::Red), + ) + .finish() + .eprint(Source::from(context.source)) + .unwrap(); + return Err(()); } - for (index, (ptype, param)) in ptypes.iter().zip(params.iter_mut()).enumerate() { + for (ptype, param) in ptypes.iter().zip(params.iter_mut()) { tc_expression(context, param)?; - if param.type_.is_none() || param.type_.unwrap() != *ptype { - return Err(Error { - position, - message: format!( - "Param {} is {:?} but should be {:?}", - index + 1, - param.type_, - ptype - ), - }); + if param.type_ != Some(*ptype) { + return type_mismatch( + *ptype, + &expr.span, + param.type_, + ¶m.span, + context.source, + ); } } rtype } else { - return Err(Error { - position, - message: format!("Unknown function '{}'", name), - }); + Report::build(ReportKind::Error, (), expr.span.start) + .with_message(format!("Unknown function {}", name)) + .with_label( + Label::new(expr.span.clone()) + .with_message(format!("Unknown function {}", name)) + .with_color(Color::Red), + ) + .finish() + .eprint(Source::from(context.source)) + .unwrap(); + return Err(()); } } ast::Expr::Select { - position, ref mut condition, ref mut if_true, ref mut if_false, @@ -276,40 +451,62 @@ fn tc_expression<'a>(context: &mut Context<'a>, expr: &mut ast::Expression<'a>) tc_expression(context, if_true)?; tc_expression(context, if_false)?; if condition.type_ != Some(ast::Type::I32) { - return Err(Error { - position, - message: "Condition of select has to be of type i32".into(), - }); + return type_mismatch( + I32, + &condition.span, + condition.type_, + &condition.span, + context.source, + ); } - if if_true.type_ != if_false.type_ { - return Err(Error { - position, - message: "Types of select branches differ".into(), - }); - } - if if_true.type_.is_none() { - return Err(Error { - position, - message: "Types of select branches cannot be void".into(), - }); + if let Some(true_type) = if_true.type_ { + if if_true.type_ != if_false.type_ { + return type_mismatch( + true_type, + &if_true.span, + if_false.type_, + &if_false.span, + context.source, + ); + } + } else { + return expected_type(&if_true.span, context.source); } if_true.type_ } + ast::Expr::Error => unreachable!(), }; Ok(()) } fn tc_mem_location<'a>( context: &mut Context<'a>, - mem_location: &mut ast::MemoryLocation<'a>, + mem_location: &mut ast::MemoryLocation, ) -> Result<()> { tc_expression(context, &mut mem_location.left)?; tc_expression(context, &mut mem_location.right)?; - if mem_location.left.type_ != Some(I32) || mem_location.right.type_ != Some(I32) { - return Err(Error { - position: mem_location.position, - message: "Type mismatch".into(), - }); + if mem_location.left.type_ != Some(I32) { + return type_mismatch( + I32, + &mem_location.left.span, + mem_location.left.type_, + &mem_location.left.span, + context.source, + ); + } + if let ast::Expr::I32Const(_) = mem_location.right.expr { + } else { + Report::build(ReportKind::Error, (), mem_location.right.span.start) + .with_message("Expected I32 constant") + .with_label( + Label::new(mem_location.right.span.clone()) + .with_message("Expected I32 constant") + .with_color(Color::Red), + ) + .finish() + .eprint(Source::from(context.source)) + .unwrap(); + return Err(()); } Ok(()) }