diff --git a/src/fun/parser.rs b/src/fun/parser.rs index 721739cd..58164a5c 100644 --- a/src/fun/parser.rs +++ b/src/fun/parser.rs @@ -67,7 +67,7 @@ impl<'a> TermParser<'a> { // adt declaration let (nam, adt) = self.parse_datatype(builtin)?; let end_idx = *self.index(); - book.add_adt(nam, adt).map_err(|e| add_ctx_to_msg(&e, ini_idx, end_idx, self.input()))?; + self.with_ctx(book.add_adt(nam, adt), ini_idx, end_idx)?; } else { // function declaration rule let (name, rule) = self.parse_rule()?; @@ -499,8 +499,8 @@ impl<'a> TermParser<'a> { let nam = self.parse_bend_name()?; let end_idx = *self.index(); if nam.contains("__") { - let ctx = highlight_error(ini_idx, end_idx, self.input()); - Err(format!("Top-level names are not allowed to contain \"__\".\n{ctx}")) + let msg = "Top-level names are not allowed to contain \"__\".".to_string(); + self.with_ctx(Err(msg), ini_idx, end_idx) } else { Ok(nam) } @@ -523,22 +523,22 @@ impl<'a> TermParser<'a> { /// Parses a tag where it may or may not be valid. /// /// If it is not valid, the returned callback can be used to issue an error. - fn parse_tag(&mut self) -> Result<(Option, impl FnOnce(&Self) -> Result<(), String>), String> { + fn parse_tag(&mut self) -> Result<(Option, impl FnOnce(&mut Self) -> Result<(), String>), String> { let index = self.index; let tag = if self.skip_peek_one() == Some('#') && !self.peek_many(2).is_some_and(|x| x.chars().nth(1).unwrap().is_ascii_digit()) { - let ctx = highlight_error(index, index + 1, self.input); - return Err(format!("Tagged terms not supported for hvm32.\n{ctx}")); + let msg = "Tagged terms not supported for hvm32.".to_string(); + return self.with_ctx(Err(msg), index, index + 1); } else { None }; let end_index = self.index; let has_tag = tag.is_some(); - Ok((tag, move |slf: &Self| { + Ok((tag, move |slf: &mut Self| { if has_tag { - let ctx = highlight_error(index, end_index, slf.input); - Err(format!("\x1b[1m- unexpected tag:\x1b[0m{}", ctx)) + let msg = "\x1b[1m- unexpected tag:\x1b[0m".to_string(); + slf.with_ctx(Err(msg), index, end_index) } else { Ok(()) } @@ -606,112 +606,9 @@ impl<'a> TermParser<'a> { } fn num_range_err(&mut self, ini_idx: usize, typ: &str) -> Result { - let ctx = highlight_error(ini_idx, *self.index(), self.input()); - Err(format!("\x1b[1mNumber literal outside of range for {}.\x1b[0m\n{}", typ, ctx)) - } - - /* Utils */ - - /// Checks if the next characters in the input start with the given string. - /// Skips trivia. - fn skip_starts_with(&mut self, text: &str) -> bool { - self.skip_trivia(); - self.starts_with(text) - } - - fn skip_peek_one(&mut self) -> Option { - self.skip_trivia(); - self.peek_one() - } - - /// Parses a list-like structure like "[x1, x2, x3,]". - /// - /// `parser` is a function that parses an element of the list. - /// - /// If `hard_sep` the separator between elements is mandatory. - /// Always accepts trailing separators. - /// - /// `min_els` determines how many elements must be parsed at minimum. - fn list_like( - &mut self, - parser: impl Fn(&mut Self) -> Result, - start: &str, - end: &str, - sep: &str, - hard_sep: bool, - min_els: usize, - ) -> Result, String> { - self.consume(start)?; - let mut els = vec![]; - for i in 0 .. min_els { - els.push(parser(self)?); - if hard_sep && !(i == min_els - 1 && self.skip_starts_with(end)) { - self.consume(sep)?; - } else { - self.try_consume(sep); - } - } - - while !self.try_consume(end) { - els.push(parser(self)?); - if hard_sep && !self.skip_starts_with(end) { - self.consume(sep)?; - } else { - self.try_consume(sep); - } - } - Ok(els) - } - - fn labelled( - &mut self, - parser: impl Fn(&mut Self) -> Result, - label: &str, - ) -> Result { - match parser(self) { - Ok(val) => Ok(val), - Err(_) => self.expected(label), - } - } - - fn expected_spanned(&mut self, exp: &str, ini_idx: usize, end_idx: usize) -> Result { - let ctx = highlight_error(ini_idx, end_idx, self.input()); - let is_eof = self.is_eof(); - let detected = DisplayFn(|f| if is_eof { write!(f, " end of input") } else { write!(f, "\n{ctx}") }); - Err(format!("\x1b[1m- expected:\x1b[0m {}\n\x1b[1m- detected:\x1b[0m{}", exp, detected)) - } - - /// Consumes text if the input starts with it. Otherwise, do nothing. - fn try_consume(&mut self, text: &str) -> bool { - self.skip_trivia(); - if self.starts_with(text) { - self.consume(text).unwrap(); - true - } else { - false - } - } - - fn parse_u32(&mut self) -> Result { - self.skip_trivia(); - let radix = match self.peek_many(2) { - Some("0x") => { - self.advance_many(2); - 16 - } - Some("0b") => { - self.advance_many(2); - 2 - } - _ => 10, - }; - let num_str = self.take_while(move |c| c.is_digit(radix) || c == '_'); - let num_str = num_str.chars().filter(|c| *c != '_').collect::(); - if num_str.is_empty() { - self.expected("numeric digit") - } else { - u32::from_str_radix(&num_str, radix).map_err(|e| e.to_string()) - } + let msg = format!("\x1b[1mNumber literal outside of range for {}.\x1b[0m", typ); + let end_idx = *self.index(); + self.with_ctx(Err(msg), ini_idx, end_idx) } } @@ -786,11 +683,6 @@ impl Book { } } -fn add_ctx_to_msg(msg: &str, ini_idx: usize, end_idx: usize, file: &str) -> String { - let ctx = highlight_error(ini_idx, end_idx, file); - format!("{msg}\n{ctx}") -} - impl<'a> ParserCommons<'a> for TermParser<'a> {} pub trait ParserCommons<'a>: Parser<'a> { @@ -823,10 +715,22 @@ pub trait ParserCommons<'a>: Parser<'a> { } fn expected_spanned(&mut self, exp: &str, ini_idx: usize, end_idx: usize) -> Result { - let ctx = highlight_error(ini_idx, end_idx, self.input()); let is_eof = self.is_eof(); - let detected = DisplayFn(|f| if is_eof { write!(f, " end of input") } else { write!(f, "\n{ctx}") }); - Err(format!("\x1b[1m- expected:\x1b[0m {}\n\x1b[1m- detected:\x1b[0m{}", exp, detected)) + let detected = DisplayFn(|f| if is_eof { write!(f, " end of input") } else { Ok(()) }); + let msg = format!("\x1b[1m- expected:\x1b[0m {}\n\x1b[1m- detected:\x1b[0m{}", exp, detected); + self.with_ctx(Err(msg), ini_idx, end_idx) + } + + fn with_ctx( + &mut self, + res: Result, + ini_idx: usize, + end_idx: usize, + ) -> Result { + res.map_err(|msg| { + let ctx = highlight_error(ini_idx, end_idx, self.input()); + format!("{msg}\n{ctx}") + }) } /// Consumes text if the input starts with it. Otherwise, do nothing. @@ -925,4 +829,26 @@ pub trait ParserCommons<'a>: Parser<'a> { }; Ok(opr) } + + fn parse_u32(&mut self) -> Result { + self.skip_trivia(); + let radix = match self.peek_many(2) { + Some("0x") => { + self.advance_many(2); + 16 + } + Some("0b") => { + self.advance_many(2); + 2 + } + _ => 10, + }; + let num_str = self.take_while(move |c| c.is_digit(radix) || c == '_'); + let num_str = num_str.chars().filter(|c| *c != '_').collect::(); + if num_str.is_empty() { + self.expected("numeric digit") + } else { + u32::from_str_radix(&num_str, radix).map_err(|e| e.to_string()) + } + } } diff --git a/src/imp/mod.rs b/src/imp/mod.rs index cc4d8f0c..4c533b2d 100644 --- a/src/imp/mod.rs +++ b/src/imp/mod.rs @@ -1,6 +1,6 @@ mod order_kwargs; pub mod parser; -pub mod to_lang; +pub mod to_fun; use indexmap::IndexMap; use interner::global::GlobalString; @@ -8,7 +8,7 @@ use interner::global::GlobalString; use crate::fun::{CtrField, Name, Op}; #[derive(Clone, Debug)] -pub enum Term { +pub enum Expr { // "None" None, // [a-zA-Z_]+ @@ -16,19 +16,19 @@ pub enum Term { // [0-9_]+ Num { val: u32 }, // {fun}({args},{kwargs},) - Call { fun: Box, args: Vec, kwargs: Vec<(Name, Term)> }, + Call { fun: Box, args: Vec, kwargs: Vec<(Name, Expr)> }, // "lambda" {names}* ":" {bod} - Lam { names: Vec, bod: Box }, + Lam { names: Vec, bod: Box }, // {lhs} {op} {rhs} - Bin { op: Op, lhs: Box, rhs: Box }, + Bin { op: Op, lhs: Box, rhs: Box }, // "\"" ... "\"" Str { val: GlobalString }, // "[" ... "]" - Lst { els: Vec }, + Lst { els: Vec }, // "(" ... ")" - Tup { els: Vec }, + Tup { els: Vec }, // "[" {term} "for" {bind} "in" {iter} ("if" {cond})? "]" - Comprehension { term: Box, bind: Name, iter: Box, cond: Option> }, + Comprehension { term: Box, bind: Name, iter: Box, cond: Option> }, } #[derive(Clone, Debug)] @@ -56,32 +56,37 @@ pub enum InPlaceOp { #[derive(Clone, Debug)] pub enum Stmt { // {pat} = {val} ";" {nxt} - Assign { pat: AssignPattern, val: Box, nxt: Box }, + Assign { pat: AssignPattern, val: Box, nxt: Box }, // {var} += {val} ";" {nxt} - InPlace { op: InPlaceOp, var: Name, val: Box, nxt: Box }, + InPlace { op: InPlaceOp, var: Name, val: Box, nxt: Box }, // "if" {cond} ":" // {then} // "else" ":" // {otherwise} - If { cond: Box, then: Box, otherwise: Box }, + If { cond: Box, then: Box, otherwise: Box }, // "match" {arg} ":" ("as" {bind})? // case {lft} ":" {rgt} - Match { arg: Box, bind: Option, arms: Vec }, + Match { arg: Box, bind: Option, arms: Vec }, // "switch" {arg} ("as" {bind})? // case 0..wildcard ":" {rgt} - Switch { arg: Box, bind: Option, arms: Vec }, - // "fold" {fun} {arg} ("as" {bind})? ":" {arms} + Switch { arg: Box, bind: Option, arms: Vec }, + // "bend" ({bind} ("="" {init})?)* "while" {cond} ":" + // {step} + // "then" ":" + // {base} + Bend { bind: Vec>, init: Vec, cond: Box, step: Box, base: Box }, + // "fold" {arg} ("as" {bind})? ":" {arms} // case {lft} ":" {rgt} - Fold { fun: Name, arg: Box, bind: Option, arms: Vec }, + Fold { arg: Box, bind: Option, arms: Vec }, // "do" {fun} ":" {block} Do { fun: Name, block: Vec }, // "return" {expr} ";" - Return { term: Box }, + Return { term: Box }, } #[derive(Clone, Debug)] pub enum MBind { - Ask { pat: AssignPattern, val: Box }, + Ask { pat: AssignPattern, val: Box }, Stmt { stmt: Box }, } diff --git a/src/imp/order_kwargs.rs b/src/imp/order_kwargs.rs index 25d12129..581ce8b4 100644 --- a/src/imp/order_kwargs.rs +++ b/src/imp/order_kwargs.rs @@ -2,7 +2,7 @@ use indexmap::IndexMap; use crate::fun::Name; -use super::{Definition, Enum, MBind, Program, Stmt, Term, Variant}; +use super::{Definition, Enum, Expr, MBind, Program, Stmt, Variant}; struct Ctx<'a> { variants: &'a IndexMap, @@ -70,6 +70,14 @@ impl Stmt { arm.rgt.order_kwargs(ctx); } } + Stmt::Bend { bind: _, init, cond, step, base } => { + for init in init { + init.order_kwargs(ctx); + } + cond.order_kwargs(ctx); + step.order_kwargs(ctx); + base.order_kwargs(ctx); + } Stmt::Do { block, .. } => { for bind in block { match bind { @@ -83,11 +91,11 @@ impl Stmt { } } -impl Term { +impl Expr { fn order_kwargs(&mut self, ctx: &Ctx) { match self { - Term::Call { fun, args, kwargs } => { - if let Term::Var { nam } = &**fun { + Expr::Call { fun, args, kwargs } => { + if let Expr::Var { nam } = &**fun { if let Some(fetch) = ctx.fetch(nam) { match fetch { Fetch::Variant(variant) => go_order_kwargs(variant.fields.iter().map(|f| &f.nam), kwargs, args), @@ -99,22 +107,22 @@ impl Term { args.iter_mut().for_each(|a| a.order_kwargs(ctx)); } } - Term::Lam { bod, .. } => bod.order_kwargs(ctx), - Term::Bin { lhs, rhs, .. } => { + Expr::Lam { bod, .. } => bod.order_kwargs(ctx), + Expr::Bin { lhs, rhs, .. } => { lhs.order_kwargs(ctx); rhs.order_kwargs(ctx); } - Term::Lst { els } | Term::Tup { els } => els.iter_mut().for_each(|e| e.order_kwargs(ctx)), - Term::Comprehension { .. } => {} - Term::None | Term::Var { .. } | Term::Num { .. } | Term::Str { .. } => {} + Expr::Lst { els } | Expr::Tup { els } => els.iter_mut().for_each(|e| e.order_kwargs(ctx)), + Expr::Comprehension { .. } => {} + Expr::None | Expr::Var { .. } | Expr::Num { .. } | Expr::Str { .. } => {} } } } fn go_order_kwargs<'a>( names: impl Iterator, - kwargs: &mut Vec<(Name, Term)>, - args: &mut Vec, + kwargs: &mut Vec<(Name, Expr)>, + args: &mut Vec, ) { let mut index_map = IndexMap::new(); for (index, field) in names.enumerate() { diff --git a/src/imp/parser.rs b/src/imp/parser.rs index cf2ce53c..cf4587c0 100644 --- a/src/imp/parser.rs +++ b/src/imp/parser.rs @@ -7,7 +7,7 @@ use crate::{ }; use super::{ - AssignPattern, Definition, Enum, InPlaceOp, MBind, MatchArm, Program, Stmt, Term, TopLevel, Variant, + AssignPattern, Definition, Enum, Expr, InPlaceOp, MBind, MatchArm, Program, Stmt, TopLevel, Variant, }; const PREC: &[&[Op]] = @@ -46,24 +46,46 @@ impl<'a> Parser<'a> for PyParser<'a> { fn index(&mut self) -> &mut usize { &mut self.index } + + /// Generates an error message for parsing failures, including the highlighted context. + /// + /// Override to have our own error message. + fn expected(&mut self, exp: &str) -> Result { + let ini_idx = *self.index(); + let end_idx = *self.index() + 1; + self.expected_spanned(exp, ini_idx, end_idx) + } + + /// Consumes an instance of the given string, erroring if it is not found. + /// + /// Override to have our own error message. + fn consume(&mut self, text: &str) -> Result<(), String> { + self.skip_trivia(); + if self.input().get(*self.index() ..).unwrap_or_default().starts_with(text) { + *self.index() += text.len(); + Ok(()) + } else { + self.expected(format!("'{text}'").as_str()) + } + } } impl<'a> PyParser<'a> { // A primary can be considered as a constant or literal expression. - fn parse_primary_py(&mut self) -> Result { + fn parse_expression(&mut self) -> Result { self.skip_trivia(); let Some(head) = self.skip_peek_one() else { return self.expected("primary term")? }; let res = match head { '(' => { self.consume("(")?; - let head = self.parse_term_py()?; + let head = self.parse_infix_or_lambda()?; if self.skip_starts_with(",") { let mut els = vec![head]; while self.try_consume(",") { - els.push(self.parse_term_py()?); + els.push(self.parse_infix_or_lambda()?); } self.consume(")")?; - Term::Tup { els } + Expr::Tup { els } } else { self.consume(")")?; head @@ -73,62 +95,62 @@ impl<'a> PyParser<'a> { '\"' => { let str = self.parse_quoted_string()?; let val = STRINGS.get(str); - Term::Str { val } + Expr::Str { val } } c if c.is_ascii_digit() => { let val = self.parse_u64()?; - Term::Num { val: val as u32 } + Expr::Num { val: val as u32 } } _ => { if self.try_consume("True") { - return Ok(Term::Num { val: 1 }); + return Ok(Expr::Num { val: 1 }); } else if self.try_consume("False") { - return Ok(Term::Num { val: 0 }); + return Ok(Expr::Num { val: 0 }); } - Term::Var { nam: self.parse_bend_name()? } + Expr::Var { nam: self.parse_bend_name()? } } }; Ok(res) } - fn list_or_comprehension(&mut self) -> Result { + fn list_or_comprehension(&mut self) -> Result { self.consume("[")?; - let head = self.parse_term_py()?; + let head = self.parse_infix_or_lambda()?; if self.try_consume_keyword("for") { let bind = self.parse_bend_name()?; self.consume("in")?; - let iter = self.parse_term_py()?; + let iter = self.parse_infix_or_lambda()?; let mut cond = None; if self.try_consume_keyword("if") { - cond = Some(Box::new(self.parse_term_py()?)); + cond = Some(Box::new(self.parse_infix_or_lambda()?)); } self.consume("]")?; - Ok(Term::Comprehension { term: Box::new(head), bind, iter: Box::new(iter), cond }) + Ok(Expr::Comprehension { term: Box::new(head), bind, iter: Box::new(iter), cond }) } else { let mut head = vec![head]; self.try_consume(","); - let tail = self.list_like(|p| p.parse_term_py(), "", "]", ",", true, 0)?; + let tail = self.list_like(|p| p.parse_infix_or_lambda(), "", "]", ",", true, 0)?; head.extend(tail); - Ok(Term::Lst { els: head }) + Ok(Expr::Lst { els: head }) } } - fn parse_term_py(&mut self) -> Result { + fn parse_infix_or_lambda(&mut self) -> Result { self.skip_trivia(); if self.try_consume_keyword("lambda") { let names = self.list_like(|p| p.parse_bend_name(), "", ":", ",", true, 1)?; - let bod = self.parse_term_py()?; - Ok(Term::Lam { names, bod: Box::new(bod) }) + let bod = self.parse_infix_or_lambda()?; + Ok(Expr::Lam { names, bod: Box::new(bod) }) } else { self.parse_infix_py(0) } } - fn parse_call(&mut self) -> Result { + fn parse_call(&mut self) -> Result { self.skip_trivia(); let mut args = Vec::new(); let mut kwargs = Vec::new(); - let fun = self.parse_primary_py()?; + let fun = self.parse_expression()?; if self.try_consume("(") { loop { if self.try_consume(",") { @@ -138,14 +160,9 @@ impl<'a> PyParser<'a> { break; } - let arg = self.parse_term_py()?; - if self.try_consume("=") { - if let Term::Var { nam } = arg { - let value = self.parse_term_py()?; - kwargs.push((nam, value)); - } else { - return Err("Unexpected '='".to_string()); - } + let (bind, arg) = self.parse_named_arg()?; + if let Some(bind) = bind { + kwargs.push((bind, arg)); } else { args.push(arg); } @@ -154,11 +171,28 @@ impl<'a> PyParser<'a> { if args.is_empty() && kwargs.is_empty() { Ok(fun) } else { - Ok(Term::Call { fun: Box::new(fun), args, kwargs }) + Ok(Expr::Call { fun: Box::new(fun), args, kwargs }) } } - fn parse_infix_py(&mut self, prec: usize) -> Result { + fn parse_named_arg(&mut self) -> Result<(Option, Expr), String> { + let arg = self.parse_infix_or_lambda()?; + if self.try_consume("=") { + if let Expr::Var { nam } = arg { + let bind = Some(nam); + let arg = self.parse_infix_or_lambda()?; + Ok((bind, arg)) + } else { + let msg = "Unexpected '=' in unnamed argument.".to_string(); + let idx = *self.index(); + self.with_ctx(Err(msg), idx, idx + 1) + } + } else { + Ok((None, arg)) + } + } + + fn parse_infix_py(&mut self, prec: usize) -> Result { maybe_grow(|| { self.skip_trivia(); if prec > PREC.len() - 1 { @@ -170,7 +204,7 @@ impl<'a> PyParser<'a> { let op = self.parse_oper()?; let rhs = self.parse_infix_py(prec + 1)?; self.skip_trivia(); - lhs = Term::Bin { op, lhs: Box::new(lhs), rhs: Box::new(rhs) }; + lhs = Expr::Bin { op, lhs: Box::new(lhs), rhs: Box::new(rhs) }; } else { break; } @@ -191,15 +225,42 @@ impl<'a> PyParser<'a> { } } - fn skip_newlines(&mut self) -> Result<(), String> { - while let Some(c) = self.peek_one() { - if c == '\n' { + fn skip_newlines(&mut self) -> usize { + loop { + let num_spaces = self.advance_inline_trivia(); + if self.peek_one() == Some('\r') { + self.advance_one(); + } + if self.peek_one() == Some('\n') { self.advance_one(); } else { - break; + return num_spaces; } } - Ok(()) + } + + fn advance_inline_trivia(&mut self) -> usize { + let mut char_count = 0; + while let Some(c) = self.peek_one() { + if " \t".contains(c) { + self.advance_one(); + char_count += 1; + continue; + } + if c == '/' && self.input().get(*self.index() ..).unwrap_or_default().starts_with("//") { + while let Some(c) = self.peek_one() { + if c != '\n' { + self.advance_one(); + char_count += 1; + } else { + break; + } + } + continue; + } + break; + } + char_count } fn skip_exact_indent(&mut self, Indent(mut count): &Indent, block: bool) -> Result { @@ -242,6 +303,8 @@ impl<'a> PyParser<'a> { self.parse_switch_py(indent) } else if self.try_consume_keyword("fold") { self.parse_fold_py(indent) + } else if self.try_consume_keyword("bend") { + self.parse_bend(indent) } else if self.try_consume_keyword("do") { self.parse_do_py(indent) } else { @@ -258,7 +321,7 @@ impl<'a> PyParser<'a> { if self.skip_starts_with("=") { // it's actually an assignment self.consume("=")?; - let val = self.parse_term_py()?; + let val = self.parse_infix_or_lambda()?; self.consume(";")?; let nxt = self.parse_stmt_py(indent)?; return Ok(Stmt::Assign { pat: AssignPattern::Var(name), val: Box::new(val), nxt: Box::new(nxt) }); @@ -282,7 +345,7 @@ impl<'a> PyParser<'a> { return self.expected("in-place operator"); } - let val = self.parse_term_py()?; + let val = self.parse_infix_or_lambda()?; self.consume(";")?; let nxt = self.parse_stmt_py(indent)?; Ok(Stmt::InPlace { op: in_place, var: name, val: Box::new(val), nxt: Box::new(nxt) }) @@ -293,13 +356,13 @@ impl<'a> PyParser<'a> { } fn parse_return_py(&mut self) -> Result { - let term = self.parse_term_py()?; + let term = self.parse_infix_or_lambda()?; self.consume(";")?; Ok(Stmt::Return { term: Box::new(term) }) } fn parse_if_py(&mut self, indent: &mut Indent) -> Result { - let cond = self.parse_term_py()?; + let cond = self.parse_infix_or_lambda()?; self.consume(":")?; indent.enter_level(); let then = self.parse_stmt_py(indent)?; @@ -313,17 +376,8 @@ impl<'a> PyParser<'a> { Ok(Stmt::If { cond: Box::new(cond), then: Box::new(then), otherwise: Box::new(otherwise) }) } - fn parse_as_bind(&mut self) -> Result, String> { - let mut bind = None; - if self.try_consume_keyword("as") { - bind = Some(self.parse_bend_name()?); - } - Ok(bind) - } - fn parse_match_py(&mut self, indent: &mut Indent) -> Result { - let scrutinee = self.parse_primary_py()?; - let bind = self.parse_as_bind()?; + let (bind, arg) = self.parse_named_arg()?; self.consume(":")?; let mut arms = Vec::new(); indent.enter_level(); @@ -334,7 +388,7 @@ impl<'a> PyParser<'a> { arms.push(self.parse_case_py(indent)?); } indent.exit_level(); - Ok(Stmt::Match { arg: Box::new(scrutinee), bind, arms }) + Ok(Stmt::Match { arg: Box::new(arg), bind, arms }) } fn name_or_wildcard(&mut self) -> Result, String> { @@ -352,7 +406,6 @@ impl<'a> PyParser<'a> { } fn parse_case_py(&mut self, indent: &mut Indent) -> Result { - self.consume("case")?; let pat = self.name_or_wildcard()?; self.consume(":")?; indent.enter_level(); @@ -362,8 +415,7 @@ impl<'a> PyParser<'a> { } fn parse_switch_py(&mut self, indent: &mut Indent) -> Result { - let arg = self.parse_term_py()?; - let bind = self.parse_as_bind()?; + let (bind, arg) = self.parse_named_arg()?; self.consume(":")?; let mut arms = Vec::new(); @@ -372,7 +424,6 @@ impl<'a> PyParser<'a> { let mut expected_num = 0; while should_continue && self.skip_exact_indent(indent, true)? { - self.consume("case")?; if let Some(c) = self.skip_peek_one() { match c { '_' => { @@ -405,9 +456,7 @@ impl<'a> PyParser<'a> { } fn parse_fold_py(&mut self, indent: &mut Indent) -> Result { - let fun = self.parse_bend_name()?; - let arg = self.parse_term_py()?; - let bind = self.parse_as_bind()?; + let (bind, arg) = self.parse_named_arg()?; self.consume(":")?; let mut arms = Vec::new(); indent.enter_level(); @@ -418,7 +467,23 @@ impl<'a> PyParser<'a> { arms.push(self.parse_case_py(indent)?); } indent.exit_level(); - Ok(Stmt::Fold { fun, arg: Box::new(arg), bind, arms }) + Ok(Stmt::Fold { arg: Box::new(arg), bind, arms }) + } + + fn parse_bend(&mut self, indent: &mut Indent) -> Result { + let args = self.list_like(|p| p.parse_named_arg(), "", "while", ",", true, 0)?; + let (bind, init) = args.into_iter().unzip(); + let cond = self.parse_infix_or_lambda()?; + self.consume(":")?; + indent.enter_level(); + let step = self.parse_stmt_py(indent)?; + indent.exit_level(); + self.consume("then")?; + self.consume(":")?; + indent.enter_level(); + let base = self.parse_stmt_py(indent)?; + indent.exit_level(); + Ok(Stmt::Bend { bind, init, cond: Box::new(cond), step: Box::new(step), base: Box::new(base) }) } fn parse_do_py(&mut self, indent: &mut Indent) -> Result { @@ -435,7 +500,7 @@ impl<'a> PyParser<'a> { if self.try_consume("!") { let pat = self.parse_assign_pattern_py()?; self.consume("=")?; - let val = self.parse_term_py()?; + let val = self.parse_infix_or_lambda()?; self.consume(";")?; block.push(MBind::Ask { pat, val: Box::new(val) }); } else { @@ -450,7 +515,7 @@ impl<'a> PyParser<'a> { fn parse_assignment_py(&mut self, indent: &mut Indent) -> Result { let pat = self.parse_assign_pattern_py()?; self.consume("=")?; - let val = self.parse_term_py()?; + let val = self.parse_infix_or_lambda()?; self.consume(";")?; let nxt = self.parse_stmt_py(indent)?; Ok(Stmt::Assign { pat, val: Box::new(val), nxt: Box::new(nxt) }) @@ -522,16 +587,18 @@ impl<'a> PyParser<'a> { let mut defs = IndexMap::::new(); let mut variants = IndexMap::::new(); - while { - self.skip_newlines()?; - true - } && !self.is_eof() - { + loop { + let spaces = self.skip_newlines(); + if self.is_eof() { + break; + } + if spaces != 0 { + self.expected("Indentation error")?; + } match self.parse_top_level_py(&mut Indent(0))? { TopLevel::Def(def) => Self::add_def_py(&mut defs, def)?, TopLevel::Enum(r#enum) => Self::add_enum_py(&mut enums, &mut variants, r#enum)?, } - self.skip_spaces(); } Ok(Program { enums, defs, variants }) @@ -576,7 +643,6 @@ mod test { use super::PyParser; #[test] - #[ignore] fn parse_def() { let src = r#" enum Point: @@ -596,17 +662,17 @@ def inc(n): n += 1; return n; -def inc_list(list): - return [x+1 for x in list]; +//def inc_list(list): +// return [x+1 for x in list]; def lam(): return lambda x, y: x; def do_match(b): - match b as bool: - case True: + match b: + True: return 1; - case False: + False: return 0; def true(): @@ -620,27 +686,33 @@ def fib(n): def swt(n): switch n: - case 0: + 0: return 42; - case _: + _: return 1; def fld(list): - fold List.fold list: - case List.cons: + fold list: + List.cons: return 1; - case List.nil: + List.nil: return 2; -def main(): - do IO.bind: - !x = IO.read(); - return x; +def bnd(): + bend x = 0 while x < 10: + return List.cons(x go(x + 1)); + then: + return List.nil(); + +//def main(): +// do IO.bind: +// !x = IO.read(); +// return x; "#; let mut parser = PyParser::new(src); let mut program = parser.parse_program_py().inspect_err(|e| println!("{e}")).unwrap(); program.order_kwargs(); - let out = program.to_lang(crate::fun::Book::default()); + let out = program.to_fun(crate::fun::Book::default()); println!("{out}"); } } diff --git a/src/imp/to_fun.rs b/src/imp/to_fun.rs new file mode 100644 index 00000000..ca5c13cc --- /dev/null +++ b/src/imp/to_fun.rs @@ -0,0 +1,126 @@ +use super::{AssignPattern, Definition, Expr, Program, Stmt}; +use crate::fun; + +impl Program { + pub fn to_fun(self, mut book: fun::Book) -> fun::Book { + for (def_name, def) in self.defs { + book.defs.insert(def_name, def.to_fun()); + } + + for (enum_name, r#enum) in self.enums { + for variant in r#enum.variants.iter() { + book.ctrs.insert(variant.0.clone(), enum_name.clone()); + } + let ctrs = r#enum.variants.into_iter().map(|(k, v)| (k, v.fields)).collect(); + let adt = fun::Adt { ctrs, builtin: false }; + book.adts.insert(enum_name, adt); + } + + book + } +} + +impl Definition { + pub fn to_fun(self) -> fun::Definition { + let rule = fun::Rule { + pats: self.params.into_iter().map(|param| fun::Pattern::Var(Some(param))).collect(), + body: self.body.to_fun(), + }; + + fun::Definition { name: self.name, rules: vec![rule], builtin: false } + } +} + +impl AssignPattern { + pub fn to_fun(self) -> fun::Pattern { + match self { + AssignPattern::Var(name) => fun::Pattern::Var(Some(name)), + AssignPattern::Tup(names) => fun::Pattern::Fan( + fun::FanKind::Tup, + fun::Tag::Static, + names.into_iter().map(|name| fun::Pattern::Var(Some(name))).collect(), + ), + } + } +} + +impl Stmt { + pub fn to_fun(self) -> fun::Term { + match self { + Stmt::Assign { pat, val, nxt } => { + let pat = pat.to_fun(); + let val = val.to_fun(); + let nxt = nxt.to_fun(); + fun::Term::Let { pat: Box::new(pat), val: Box::new(val), nxt: Box::new(nxt) } + } + Stmt::InPlace { op, var: nam, val, nxt } => fun::Term::Let { + pat: Box::new(fun::Pattern::Var(Some(nam.clone()))), + val: Box::new(fun::Term::Oper { + opr: op.to_lang_op(), + fst: Box::new(fun::Term::Var { nam }), + snd: Box::new(val.to_fun()), + }), + nxt: Box::new(nxt.to_fun()), + }, + Stmt::If { cond, then, otherwise } => { + let arms = vec![otherwise.to_fun(), then.to_fun()]; + fun::Term::Swt { arg: Box::new(cond.to_fun()), bnd: None, with: Vec::new(), pred: None, arms } + } + Stmt::Match { arg, bind, arms } => { + let arg = arg.to_fun(); + let arms = arms.into_iter().map(|arm| (arm.lft, Vec::new(), arm.rgt.to_fun())).collect(); + fun::Term::Mat { arg: Box::new(arg), bnd: bind, with: Vec::new(), arms } + } + Stmt::Switch { arg, bind, arms } => { + let arg = arg.to_fun(); + let arms = arms.into_iter().map(Stmt::to_fun).collect(); + fun::Term::Swt { arg: Box::new(arg), bnd: bind, with: Vec::new(), pred: None, arms } + } + Stmt::Fold { arg, bind, arms } => { + let arg = arg.to_fun(); + let arms = arms.into_iter().map(|arm| (arm.lft, Vec::new(), arm.rgt.to_fun())).collect(); + fun::Term::Fold { arg: Box::new(arg), bnd: bind, with: Vec::new(), arms } + } + Stmt::Bend { bind, init, cond, step, base } => { + let init = init.into_iter().map(Expr::to_fun).collect(); + let cond = cond.to_fun(); + let step = step.to_fun(); + let base = base.to_fun(); + fun::Term::Bend { bind, init, cond: Box::new(cond), step: Box::new(step), base: Box::new(base) } + } + Stmt::Do { .. } => todo!(), + Stmt::Return { term } => term.to_fun(), + } + } +} + +impl Expr { + pub fn to_fun(self) -> fun::Term { + match self { + Expr::None => fun::Term::Era, + Expr::Var { nam } => fun::Term::Var { nam }, + Expr::Num { val } => fun::Term::Num { val: fun::Num::U24(val) }, + Expr::Call { fun, args, kwargs } => { + assert!(kwargs.is_empty()); + let args = args.into_iter().map(Self::to_fun); + fun::Term::call(fun.to_fun(), args) + } + Expr::Lam { names, bod } => names.into_iter().rfold(bod.to_fun(), |acc, nxt| fun::Term::Lam { + tag: fun::Tag::Static, + pat: Box::new(fun::Pattern::Var(Some(nxt))), + bod: Box::new(acc), + }), + Expr::Bin { op, lhs, rhs } => { + fun::Term::Oper { opr: op, fst: Box::new(lhs.to_fun()), snd: Box::new(rhs.to_fun()) } + } + Expr::Str { val } => fun::Term::Str { val }, + Expr::Lst { els } => fun::Term::List { els: els.into_iter().map(Self::to_fun).collect() }, + Expr::Tup { els } => fun::Term::Fan { + fan: fun::FanKind::Tup, + tag: fun::Tag::Static, + els: els.into_iter().map(Self::to_fun).collect(), + }, + Expr::Comprehension { .. } => todo!(), + } + } +} diff --git a/src/imp/to_lang.rs b/src/imp/to_lang.rs deleted file mode 100644 index 675f5558..00000000 --- a/src/imp/to_lang.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::fun::{self as lang}; - -use super::{AssignPattern, Definition, Program, Stmt, Term}; - -impl Program { - pub fn to_lang(self, mut book: lang::Book) -> lang::Book { - for (def_name, def) in self.defs { - book.defs.insert(def_name, def.to_lang()); - } - - for (enum_name, r#enum) in self.enums { - for variant in r#enum.variants.iter() { - book.ctrs.insert(variant.0.clone(), enum_name.clone()); - } - let ctrs = r#enum.variants.into_iter().map(|(k, v)| (k, v.fields)).collect(); - let adt = lang::Adt { ctrs, builtin: false }; - book.adts.insert(enum_name, adt); - } - - book - } -} - -impl Definition { - pub fn to_lang(self) -> lang::Definition { - let rule = lang::Rule { - pats: self.params.into_iter().map(|param| lang::Pattern::Var(Some(param))).collect(), - body: self.body.to_lang(), - }; - - lang::Definition { name: self.name, rules: vec![rule], builtin: false } - } -} - -impl AssignPattern { - pub fn to_lang(self) -> lang::Pattern { - match self { - AssignPattern::Var(name) => lang::Pattern::Var(Some(name)), - AssignPattern::Tup(names) => lang::Pattern::Fan( - lang::FanKind::Tup, - lang::Tag::Static, - names.into_iter().map(|name| lang::Pattern::Var(Some(name))).collect(), - ), - } - } -} - -impl Stmt { - pub fn to_lang(self) -> lang::Term { - match self { - Stmt::Assign { pat, val, nxt } => { - let pat = pat.to_lang(); - let val = val.to_lang(); - let nxt = nxt.to_lang(); - lang::Term::Let { pat: Box::new(pat), val: Box::new(val), nxt: Box::new(nxt) } - } - Stmt::InPlace { op, var: nam, val, nxt } => lang::Term::Let { - pat: Box::new(lang::Pattern::Var(Some(nam.clone()))), - val: Box::new(lang::Term::Oper { - opr: op.to_lang_op(), - fst: Box::new(lang::Term::Var { nam }), - snd: Box::new(val.to_lang()), - }), - nxt: Box::new(nxt.to_lang()), - }, - Stmt::If { cond, then, otherwise } => { - let arms = vec![otherwise.to_lang(), then.to_lang()]; - lang::Term::Swt { arg: Box::new(cond.to_lang()), bnd: None, with: Vec::new(), pred: None, arms } - } - Stmt::Match { arg, bind, arms: old_arms } => { - let arg = arg.to_lang(); - let mut arms = Vec::with_capacity(old_arms.len()); - for arm in old_arms { - arms.push((arm.lft, Vec::new(), arm.rgt.to_lang())) - } - lang::Term::Mat { arg: Box::new(arg), bnd: bind, with: Vec::new(), arms } - } - Stmt::Switch { .. } => unimplemented!(), - Stmt::Fold { .. } => unimplemented!(), - Stmt::Do { .. } => unimplemented!(), - Stmt::Return { term } => term.to_lang(), - } - } -} - -impl Term { - pub fn to_lang(self) -> lang::Term { - match self { - Term::None => lang::Term::Era, - Term::Var { nam } => lang::Term::Var { nam }, - Term::Num { val } => lang::Term::Num { val: lang::Num::U24(val) }, - Term::Call { fun, args, kwargs } => { - assert!(kwargs.is_empty()); - let args = args.into_iter().map(Self::to_lang); - lang::Term::call(fun.to_lang(), args) - } - Term::Lam { names, bod } => names.into_iter().rfold(bod.to_lang(), |acc, nxt| lang::Term::Lam { - tag: lang::Tag::Static, - pat: Box::new(lang::Pattern::Var(Some(nxt))), - bod: Box::new(acc), - }), - Term::Bin { op, lhs, rhs } => { - lang::Term::Oper { opr: op, fst: Box::new(lhs.to_lang()), snd: Box::new(rhs.to_lang()) } - } - Term::Str { val } => lang::Term::Str { val }, - Term::Lst { els } => lang::Term::List { els: els.into_iter().map(Self::to_lang).collect() }, - Term::Tup { els } => lang::Term::Fan { - fan: lang::FanKind::Tup, - tag: lang::Tag::Static, - els: els.into_iter().map(Self::to_lang).collect(), - }, - Term::Comprehension { .. } => todo!(), - } - } -} diff --git a/tests/snapshots/compile_file__just_a_name.hvm.snap b/tests/snapshots/compile_file__just_a_name.hvm.snap index 2270b6bf..3b6ab37f 100644 --- a/tests/snapshots/compile_file__just_a_name.hvm.snap +++ b/tests/snapshots/compile_file__just_a_name.hvm.snap @@ -6,3 +6,4 @@ Errors: In tests/golden_tests/compile_file/just_a_name.hvm : - expected: pattern-matching pattern - detected: end of input + 1 | asdf  diff --git a/tests/snapshots/compile_file__just_data.hvm.snap b/tests/snapshots/compile_file__just_data.hvm.snap index 7dd33ad7..6accfcb7 100644 --- a/tests/snapshots/compile_file__just_data.hvm.snap +++ b/tests/snapshots/compile_file__just_data.hvm.snap @@ -6,3 +6,4 @@ Errors: In tests/golden_tests/compile_file/just_data.hvm : - expected: datatype name - detected: end of input + 1 | data  diff --git a/tests/snapshots/compile_file__just_paren.hvm.snap b/tests/snapshots/compile_file__just_paren.hvm.snap index 4bfa9cc6..65e52a2e 100644 --- a/tests/snapshots/compile_file__just_paren.hvm.snap +++ b/tests/snapshots/compile_file__just_paren.hvm.snap @@ -6,3 +6,4 @@ Errors: In tests/golden_tests/compile_file/just_paren.hvm : - expected: function name - detected: end of input + 2 | (  diff --git a/tests/snapshots/compile_file__just_rule_paren.hvm.snap b/tests/snapshots/compile_file__just_rule_paren.hvm.snap index ea464fb1..b95d019f 100644 --- a/tests/snapshots/compile_file__just_rule_paren.hvm.snap +++ b/tests/snapshots/compile_file__just_rule_paren.hvm.snap @@ -6,3 +6,4 @@ Errors: In tests/golden_tests/compile_file/just_rule_paren.hvm : - expected: '=' - detected: end of input + 1 | (rule)  diff --git a/tests/snapshots/compile_file__missing_adt_eq.hvm.snap b/tests/snapshots/compile_file__missing_adt_eq.hvm.snap index 136d09c0..3af463c5 100644 --- a/tests/snapshots/compile_file__missing_adt_eq.hvm.snap +++ b/tests/snapshots/compile_file__missing_adt_eq.hvm.snap @@ -6,3 +6,4 @@ Errors: In tests/golden_tests/compile_file/missing_adt_eq.hvm : - expected: '=' - detected: end of input + 1 | data Adt  diff --git a/tests/snapshots/compile_file__missing_ctrs.hvm.snap b/tests/snapshots/compile_file__missing_ctrs.hvm.snap index 7d665d5d..f145ebcc 100644 --- a/tests/snapshots/compile_file__missing_ctrs.hvm.snap +++ b/tests/snapshots/compile_file__missing_ctrs.hvm.snap @@ -6,3 +6,4 @@ Errors: In tests/golden_tests/compile_file/missing_ctrs.hvm : - expected: datatype constructor name - detected: end of input + 1 | data Adt =