mirror of
https://github.com/HigherOrderCO/Bend.git
synced 2024-09-17 14:47:21 +03:00
Merge pull request #276 from HigherOrderCO/feature/sc-625/add-procedural-style-syntax-for-hvm-lang
[sc-625] Add procedural-style syntax for hvm-lang
This commit is contained in:
commit
39ff4ffb7e
13
cspell.json
13
cspell.json
@ -40,6 +40,7 @@
|
||||
"insta",
|
||||
"interner",
|
||||
"itertools",
|
||||
"kwargs",
|
||||
"lcons",
|
||||
"linearization",
|
||||
"linearizes",
|
||||
@ -59,6 +60,7 @@
|
||||
"oref",
|
||||
"peekable",
|
||||
"postcondition",
|
||||
"prec",
|
||||
"readback",
|
||||
"recursively",
|
||||
"redex",
|
||||
@ -91,13 +93,6 @@
|
||||
"walkdir",
|
||||
"wopts"
|
||||
],
|
||||
"files": [
|
||||
"**/*.rs",
|
||||
"**/*.md"
|
||||
],
|
||||
"ignoreRegExpList": [
|
||||
"HexValues",
|
||||
"/λ/g",
|
||||
"/-O/g"
|
||||
]
|
||||
"files": ["**/*.rs", "**/*.md"],
|
||||
"ignoreRegExpList": ["HexValues", "/λ/g", "/-O/g"]
|
||||
}
|
||||
|
134
src/term/flavour_py/mod.rs
Normal file
134
src/term/flavour_py/mod.rs
Normal file
@ -0,0 +1,134 @@
|
||||
mod order_kwargs;
|
||||
pub mod parser;
|
||||
pub mod to_lang;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use interner::global::GlobalString;
|
||||
|
||||
use crate::term::Name;
|
||||
|
||||
use super::Op;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Term {
|
||||
// "None"
|
||||
None,
|
||||
// [a-zA-Z_]+
|
||||
Var { nam: Name },
|
||||
// [0-9_]+
|
||||
Num { val: u32 },
|
||||
// {fun}({args},{kwargs},)
|
||||
Call { fun: Box<Term>, args: Vec<Term>, kwargs: Vec<(Name, Term)> },
|
||||
// "lambda" {names}* ":" {bod}
|
||||
Lam { names: Vec<Name>, bod: Box<Term> },
|
||||
// {lhs} {op} {rhs}
|
||||
Bin { op: Op, lhs: Box<Term>, rhs: Box<Term> },
|
||||
// "\"" ... "\""
|
||||
Str { val: GlobalString },
|
||||
// "[" ... "]"
|
||||
Lst { els: Vec<Term> },
|
||||
// "(" ... ")"
|
||||
Tup { els: Vec<Term> },
|
||||
// "[" {term} "for" {bind} "in" {iter} ("if" {cond})? "]"
|
||||
Comprehension { term: Box<Term>, bind: Name, iter: Box<Term>, cond: Option<Box<Term>> },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MatchArm {
|
||||
pub lft: Option<Name>,
|
||||
pub rgt: Stmt,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum AssignPattern {
|
||||
// [a-zA-Z_]+
|
||||
Var(Name),
|
||||
// "(" ... ")"
|
||||
Tup(Vec<Name>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum InPlaceOp {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Stmt {
|
||||
// {pat} = {val} ";" {nxt}
|
||||
Assign { pat: AssignPattern, val: Box<Term>, nxt: Box<Stmt> },
|
||||
// {var} += {val} ";" {nxt}
|
||||
InPlace { op: InPlaceOp, var: Name, val: Box<Term>, nxt: Box<Stmt> },
|
||||
// "if" {cond} ":"
|
||||
// {then}
|
||||
// "else" ":"
|
||||
// {otherwise}
|
||||
If { cond: Box<Term>, then: Box<Stmt>, otherwise: Box<Stmt> },
|
||||
// "match" {arg} ":" ("as" {bind})?
|
||||
// case {lft} ":" {rgt}
|
||||
Match { arg: Box<Term>, bind: Option<Name>, arms: Vec<MatchArm> },
|
||||
// "switch" {arg} ("as" {bind})?
|
||||
// case 0..wildcard ":" {rgt}
|
||||
Switch { arg: Box<Term>, bind: Option<Name>, arms: Vec<Stmt> },
|
||||
// "fold" {fun} {arg} ("as" {bind})? ":" {arms}
|
||||
// case {lft} ":" {rgt}
|
||||
Fold { fun: Name, arg: Box<Term>, bind: Option<Name>, arms: Vec<MatchArm> },
|
||||
// "do" {fun} ":" {block}
|
||||
Do { fun: Name, block: Vec<MBind> },
|
||||
// "return" {expr} ";"
|
||||
Return { term: Box<Term> },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MBind {
|
||||
Ask { pat: AssignPattern, val: Box<Term> },
|
||||
Stmt { stmt: Box<Stmt> },
|
||||
}
|
||||
|
||||
// Name "(" {fields}* ")"
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Variant {
|
||||
pub name: Name,
|
||||
pub fields: Vec<Name>,
|
||||
}
|
||||
|
||||
// "def" {name} "(" {params} ")" ":" {body}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Definition {
|
||||
pub name: Name,
|
||||
pub params: Vec<Name>,
|
||||
pub body: Stmt,
|
||||
}
|
||||
|
||||
// "enum" ":" {variants}*
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Enum {
|
||||
pub name: Name,
|
||||
pub variants: IndexMap<Name, Variant>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TopLevel {
|
||||
Def(Definition),
|
||||
Enum(Enum),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Program {
|
||||
pub enums: IndexMap<Name, Enum>,
|
||||
pub defs: IndexMap<Name, Definition>,
|
||||
pub variants: IndexMap<Name, Name>,
|
||||
}
|
||||
|
||||
impl InPlaceOp {
|
||||
pub fn to_lang_op(self) -> Op {
|
||||
match self {
|
||||
InPlaceOp::Add => Op::ADD,
|
||||
InPlaceOp::Sub => Op::SUB,
|
||||
InPlaceOp::Mul => Op::MUL,
|
||||
InPlaceOp::Div => Op::DIV,
|
||||
}
|
||||
}
|
||||
}
|
107
src/term/flavour_py/order_kwargs.rs
Normal file
107
src/term/flavour_py/order_kwargs.rs
Normal file
@ -0,0 +1,107 @@
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use crate::term::Name;
|
||||
|
||||
use super::{Definition, Enum, Program, Stmt, Term, Variant};
|
||||
|
||||
struct Ctx<'a> {
|
||||
variants: &'a IndexMap<Name, Name>,
|
||||
enums: &'a IndexMap<Name, Enum>,
|
||||
defs: &'a IndexMap<Name, Definition>,
|
||||
}
|
||||
|
||||
enum Fetch<'a> {
|
||||
Variant(&'a Variant),
|
||||
Definition(&'a Definition),
|
||||
}
|
||||
|
||||
impl<'a> Ctx<'a> {
|
||||
fn fetch(&self, name: &Name) -> Option<Fetch> {
|
||||
match self.variants.get(name) {
|
||||
Some(enum_name) => match self.enums.get(enum_name) {
|
||||
Some(r#enum) => r#enum.variants.get(name).map(Fetch::Variant),
|
||||
None => None,
|
||||
},
|
||||
None => self.defs.get(name).map(Fetch::Definition),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Program {
|
||||
pub fn order_kwargs(&mut self) {
|
||||
let ctx = Ctx { variants: &self.variants, enums: &self.enums, defs: &self.defs.clone() };
|
||||
for def in self.defs.values_mut() {
|
||||
def.body.order_kwargs(&ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stmt {
|
||||
fn order_kwargs(&mut self, ctx: &Ctx) {
|
||||
match self {
|
||||
Stmt::Assign { val, nxt, .. } => {
|
||||
val.order_kwargs(ctx);
|
||||
nxt.order_kwargs(ctx);
|
||||
}
|
||||
Stmt::InPlace { val, nxt, .. } => {
|
||||
val.order_kwargs(ctx);
|
||||
nxt.order_kwargs(ctx);
|
||||
}
|
||||
Stmt::If { cond, then, otherwise } => {
|
||||
cond.order_kwargs(ctx);
|
||||
then.order_kwargs(ctx);
|
||||
otherwise.order_kwargs(ctx);
|
||||
}
|
||||
Stmt::Match { arg, arms, .. } => {
|
||||
arg.order_kwargs(ctx);
|
||||
for arm in arms {
|
||||
arm.rgt.order_kwargs(ctx);
|
||||
}
|
||||
}
|
||||
Stmt::Switch { .. } => unimplemented!(),
|
||||
Stmt::Fold { .. } => unimplemented!(),
|
||||
Stmt::Do { .. } => unimplemented!(),
|
||||
Stmt::Return { term } => term.order_kwargs(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Term {
|
||||
fn order_kwargs(&mut self, ctx: &Ctx) {
|
||||
match self {
|
||||
Term::Call { fun, args, kwargs } => {
|
||||
if let Term::Var { nam } = &**fun {
|
||||
if let Some(fetch) = ctx.fetch(nam) {
|
||||
match fetch {
|
||||
Fetch::Variant(variant) => go_order_kwargs(&variant.fields, kwargs, args),
|
||||
Fetch::Definition(def) => go_order_kwargs(&def.params, kwargs, args),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fun.order_kwargs(ctx);
|
||||
args.iter_mut().for_each(|a| a.order_kwargs(ctx));
|
||||
}
|
||||
}
|
||||
Term::Lam { bod, .. } => bod.order_kwargs(ctx),
|
||||
Term::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 { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn go_order_kwargs(names: &[Name], kwargs: &mut Vec<(Name, Term)>, args: &mut Vec<Term>) {
|
||||
let mut index_map = IndexMap::new();
|
||||
for (index, field) in names.iter().enumerate() {
|
||||
index_map.insert(field, index);
|
||||
}
|
||||
|
||||
let mut kwargs = std::mem::take(kwargs);
|
||||
kwargs.sort_by_key(|i| index_map.get(&i.0).unwrap());
|
||||
let new_args = kwargs.into_iter().map(|i| i.1.clone());
|
||||
args.extend(new_args);
|
||||
}
|
639
src/term/flavour_py/parser.rs
Normal file
639
src/term/flavour_py/parser.rs
Normal file
@ -0,0 +1,639 @@
|
||||
use indexmap::IndexMap;
|
||||
use TSPL::Parser;
|
||||
|
||||
use crate::{
|
||||
maybe_grow,
|
||||
term::{parser::ParserCommons, Name, Op, STRINGS},
|
||||
};
|
||||
|
||||
use super::{
|
||||
AssignPattern, Definition, Enum, InPlaceOp, MBind, MatchArm, Program, Stmt, Term, TopLevel, Variant,
|
||||
};
|
||||
|
||||
const PREC: &[&[Op]] =
|
||||
&[&[Op::EQL, Op::NEQ], &[Op::LTN], &[Op::GTN], &[Op::ADD, Op::SUB], &[Op::MUL, Op::DIV]];
|
||||
|
||||
struct Indent(isize);
|
||||
|
||||
impl Indent {
|
||||
fn enter_level(&mut self) {
|
||||
self.0 += 2;
|
||||
}
|
||||
|
||||
fn exit_level(&mut self) {
|
||||
self.0 -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PyParser<'i> {
|
||||
input: &'i str,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> PyParser<'a> {
|
||||
pub fn new(input: &'a str) -> Self {
|
||||
Self { input, index: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ParserCommons<'a> for PyParser<'a> {}
|
||||
|
||||
impl<'a> Parser<'a> for PyParser<'a> {
|
||||
fn input(&mut self) -> &'a str {
|
||||
self.input
|
||||
}
|
||||
|
||||
fn index(&mut self) -> &mut usize {
|
||||
&mut self.index
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PyParser<'a> {
|
||||
// A primary can be considered as a constant or literal expression.
|
||||
fn parse_primary_py(&mut self) -> Result<Term, String> {
|
||||
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()?;
|
||||
if self.skip_starts_with(",") {
|
||||
let mut els = vec![head];
|
||||
while self.try_consume(",") {
|
||||
els.push(self.parse_term_py()?);
|
||||
}
|
||||
self.consume(")")?;
|
||||
Term::Tup { els }
|
||||
} else {
|
||||
self.consume(")")?;
|
||||
head
|
||||
}
|
||||
}
|
||||
'[' => self.list_or_comprehension()?,
|
||||
'\"' => {
|
||||
let str = self.parse_quoted_string()?;
|
||||
let val = STRINGS.get(str);
|
||||
Term::Str { val }
|
||||
}
|
||||
c if c.is_ascii_digit() => {
|
||||
let val = self.parse_u64()?;
|
||||
Term::Num { val: val as u32 }
|
||||
}
|
||||
_ => {
|
||||
if self.try_consume("True") {
|
||||
return Ok(Term::Num { val: 1 });
|
||||
} else if self.try_consume("False") {
|
||||
return Ok(Term::Num { val: 0 });
|
||||
}
|
||||
Term::Var { nam: self.parse_hvml_name()? }
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn list_or_comprehension(&mut self) -> Result<Term, String> {
|
||||
self.consume("[")?;
|
||||
let head = self.parse_term_py()?;
|
||||
if self.try_consume_keyword("for") {
|
||||
let bind = self.parse_hvml_name()?;
|
||||
self.consume("in")?;
|
||||
let iter = self.parse_term_py()?;
|
||||
let mut cond = None;
|
||||
if self.try_consume_keyword("if") {
|
||||
cond = Some(Box::new(self.parse_term_py()?));
|
||||
}
|
||||
self.consume("]")?;
|
||||
Ok(Term::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)?;
|
||||
head.extend(tail);
|
||||
Ok(Term::Lst { els: head })
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_term_py(&mut self) -> Result<Term, String> {
|
||||
self.skip_trivia();
|
||||
if self.try_consume_keyword("lambda") {
|
||||
let names = self.list_like(|p| p.parse_hvml_name(), "", ":", ",", true, 1)?;
|
||||
let bod = self.parse_term_py()?;
|
||||
Ok(Term::Lam { names, bod: Box::new(bod) })
|
||||
} else {
|
||||
self.parse_infix_py(0)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_call(&mut self) -> Result<Term, String> {
|
||||
self.skip_trivia();
|
||||
let mut args = Vec::new();
|
||||
let mut kwargs = Vec::new();
|
||||
let fun = self.parse_primary_py()?;
|
||||
if self.try_consume("(") {
|
||||
loop {
|
||||
if self.try_consume(",") {
|
||||
continue;
|
||||
}
|
||||
if self.try_consume(")") {
|
||||
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());
|
||||
}
|
||||
} else {
|
||||
args.push(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if args.is_empty() && kwargs.is_empty() {
|
||||
Ok(fun)
|
||||
} else {
|
||||
Ok(Term::Call { fun: Box::new(fun), args, kwargs })
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_infix_py(&mut self, prec: usize) -> Result<Term, String> {
|
||||
maybe_grow(|| {
|
||||
self.skip_trivia();
|
||||
if prec > PREC.len() - 1 {
|
||||
return self.parse_call();
|
||||
}
|
||||
let mut lhs = self.parse_infix_py(prec + 1)?;
|
||||
while let Some(op) = self.get_op() {
|
||||
if PREC[prec].iter().any(|r| *r == op) {
|
||||
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) };
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(lhs)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_op(&mut self) -> Option<Op> {
|
||||
match self.skip_peek_one() {
|
||||
Some('+') => Some(Op::ADD),
|
||||
Some('-') => Some(Op::SUB),
|
||||
Some('*') => Some(Op::MUL),
|
||||
Some('/') => Some(Op::DIV),
|
||||
Some('>') => Some(Op::GTN),
|
||||
Some('<') => Some(Op::LTN),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_newlines(&mut self) -> Result<(), String> {
|
||||
while let Some(c) = self.peek_one() {
|
||||
if c == '\n' {
|
||||
self.advance_one();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn skip_exact_indent(&mut self, Indent(mut count): &Indent, block: bool) -> Result<bool, String> {
|
||||
while let Some(c) = self.peek_one() {
|
||||
if c == '\n' {
|
||||
self.advance_one();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if count <= 0 {
|
||||
self.skip_spaces();
|
||||
return Ok(false);
|
||||
}
|
||||
while let Some(c) = self.peek_one() {
|
||||
if c.is_ascii_whitespace() {
|
||||
count -= 1;
|
||||
self.advance_one();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if block {
|
||||
return Ok(count == 0 && !self.is_eof());
|
||||
}
|
||||
// TODO: add the current line in Err
|
||||
if count == 0 { Ok(true) } else { Err("Indentation error".to_string()) }
|
||||
}
|
||||
|
||||
fn parse_stmt_py(&mut self, indent: &mut Indent) -> Result<Stmt, String> {
|
||||
maybe_grow(|| {
|
||||
self.skip_exact_indent(indent, false)?;
|
||||
if self.try_consume_keyword("return") {
|
||||
self.parse_return_py()
|
||||
} else if self.try_consume_keyword("if") {
|
||||
self.parse_if_py(indent)
|
||||
} else if self.try_consume_keyword("match") {
|
||||
self.parse_match_py(indent)
|
||||
} else if self.try_consume_keyword("switch") {
|
||||
self.parse_switch_py(indent)
|
||||
} else if self.try_consume_keyword("fold") {
|
||||
self.parse_fold_py(indent)
|
||||
} else if self.try_consume_keyword("do") {
|
||||
self.parse_do_py(indent)
|
||||
} else {
|
||||
self.parse_in_place(indent)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_in_place(&mut self, indent: &mut Indent) -> Result<Stmt, String> {
|
||||
if self.starts_with("(") {
|
||||
self.parse_assignment_py(indent)
|
||||
} else {
|
||||
let name = self.parse_hvml_name()?;
|
||||
if self.skip_starts_with("=") {
|
||||
// it's actually an assignment
|
||||
self.consume("=")?;
|
||||
let val = self.parse_term_py()?;
|
||||
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) });
|
||||
}
|
||||
let in_place: InPlaceOp;
|
||||
self.skip_spaces();
|
||||
if let Some(s) = self.peek_many(2) {
|
||||
if s == "+=" {
|
||||
self.consume("+=")?;
|
||||
in_place = InPlaceOp::Add;
|
||||
} else if s == "-=" {
|
||||
self.consume("+=")?;
|
||||
in_place = InPlaceOp::Sub;
|
||||
} else if s == "*=" {
|
||||
self.consume("+=")?;
|
||||
in_place = InPlaceOp::Mul;
|
||||
} else if s == "/=" {
|
||||
self.consume("+=")?;
|
||||
in_place = InPlaceOp::Div;
|
||||
} else {
|
||||
return self.expected("in-place operator");
|
||||
}
|
||||
|
||||
let val = self.parse_term_py()?;
|
||||
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) })
|
||||
} else {
|
||||
self.expected("in-place operator")?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_return_py(&mut self) -> Result<Stmt, String> {
|
||||
let term = self.parse_term_py()?;
|
||||
self.consume(";")?;
|
||||
Ok(Stmt::Return { term: Box::new(term) })
|
||||
}
|
||||
|
||||
fn parse_if_py(&mut self, indent: &mut Indent) -> Result<Stmt, String> {
|
||||
let cond = self.parse_term_py()?;
|
||||
self.consume(":")?;
|
||||
indent.enter_level();
|
||||
let then = self.parse_stmt_py(indent)?;
|
||||
indent.exit_level();
|
||||
self.skip_exact_indent(indent, false)?;
|
||||
self.consume("else")?;
|
||||
self.consume(":")?;
|
||||
indent.enter_level();
|
||||
let otherwise = self.parse_stmt_py(indent)?;
|
||||
indent.exit_level();
|
||||
Ok(Stmt::If { cond: Box::new(cond), then: Box::new(then), otherwise: Box::new(otherwise) })
|
||||
}
|
||||
|
||||
fn parse_as_bind(&mut self) -> Result<Option<Name>, String> {
|
||||
let mut bind = None;
|
||||
if self.try_consume_keyword("as") {
|
||||
bind = Some(self.parse_hvml_name()?);
|
||||
}
|
||||
Ok(bind)
|
||||
}
|
||||
|
||||
fn parse_match_py(&mut self, indent: &mut Indent) -> Result<Stmt, String> {
|
||||
let scrutinee = self.parse_primary_py()?;
|
||||
let bind = self.parse_as_bind()?;
|
||||
self.consume(":")?;
|
||||
let mut arms = Vec::new();
|
||||
indent.enter_level();
|
||||
loop {
|
||||
if !self.skip_exact_indent(indent, true)? {
|
||||
break;
|
||||
}
|
||||
arms.push(self.parse_case_py(indent)?);
|
||||
}
|
||||
indent.exit_level();
|
||||
Ok(Stmt::Match { arg: Box::new(scrutinee), bind, arms })
|
||||
}
|
||||
|
||||
fn name_or_wildcard(&mut self) -> Result<Option<Name>, String> {
|
||||
self.labelled(
|
||||
|p| {
|
||||
if p.try_consume("_") {
|
||||
Ok(None)
|
||||
} else {
|
||||
let nam = p.parse_hvml_name()?;
|
||||
Ok(Some(nam))
|
||||
}
|
||||
},
|
||||
"name or '_'",
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_case_py(&mut self, indent: &mut Indent) -> Result<MatchArm, String> {
|
||||
self.consume("case")?;
|
||||
let pat = self.name_or_wildcard()?;
|
||||
self.consume(":")?;
|
||||
indent.enter_level();
|
||||
let body = self.parse_stmt_py(indent)?;
|
||||
indent.exit_level();
|
||||
Ok(MatchArm { lft: pat, rgt: body })
|
||||
}
|
||||
|
||||
fn parse_switch_py(&mut self, indent: &mut Indent) -> Result<Stmt, String> {
|
||||
let arg = self.parse_term_py()?;
|
||||
let bind = self.parse_as_bind()?;
|
||||
self.consume(":")?;
|
||||
let mut arms = Vec::new();
|
||||
|
||||
indent.enter_level();
|
||||
let mut should_continue = true;
|
||||
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 {
|
||||
'_' => {
|
||||
if expected_num == 0 {
|
||||
return self.expected("0");
|
||||
} else {
|
||||
self.consume("_")?;
|
||||
should_continue = false;
|
||||
}
|
||||
}
|
||||
c if c.is_ascii_digit() => {
|
||||
let value = self.parse_u64()?;
|
||||
if value != expected_num {
|
||||
return self.expected(&expected_num.to_string());
|
||||
}
|
||||
}
|
||||
_ => return self.expected("Number pattern"),
|
||||
}
|
||||
self.consume(":")?;
|
||||
indent.enter_level();
|
||||
arms.push(self.parse_stmt_py(indent)?);
|
||||
indent.exit_level();
|
||||
expected_num += 1;
|
||||
} else {
|
||||
self.expected("Switch pattern")?
|
||||
}
|
||||
}
|
||||
indent.exit_level();
|
||||
Ok(Stmt::Switch { arg: Box::new(arg), bind, arms })
|
||||
}
|
||||
|
||||
fn parse_fold_py(&mut self, indent: &mut Indent) -> Result<Stmt, String> {
|
||||
let fun = self.parse_hvml_name()?;
|
||||
let arg = self.parse_term_py()?;
|
||||
let bind = self.parse_as_bind()?;
|
||||
self.consume(":")?;
|
||||
let mut arms = Vec::new();
|
||||
indent.enter_level();
|
||||
loop {
|
||||
if !self.skip_exact_indent(indent, true)? {
|
||||
break;
|
||||
}
|
||||
arms.push(self.parse_case_py(indent)?);
|
||||
}
|
||||
indent.exit_level();
|
||||
Ok(Stmt::Fold { fun, arg: Box::new(arg), bind, arms })
|
||||
}
|
||||
|
||||
fn parse_do_py(&mut self, indent: &mut Indent) -> Result<Stmt, String> {
|
||||
let fun = self.parse_hvml_name()?;
|
||||
self.consume(":")?;
|
||||
|
||||
let mut block = Vec::new();
|
||||
|
||||
indent.enter_level();
|
||||
loop {
|
||||
if !self.skip_exact_indent(indent, true)? {
|
||||
break;
|
||||
}
|
||||
if self.try_consume("!") {
|
||||
let pat = self.parse_assign_pattern_py()?;
|
||||
self.consume("=")?;
|
||||
let val = self.parse_term_py()?;
|
||||
self.consume(";")?;
|
||||
block.push(MBind::Ask { pat, val: Box::new(val) });
|
||||
} else {
|
||||
block.push(MBind::Stmt { stmt: Box::new(self.parse_stmt_py(&mut Indent(0))?) })
|
||||
}
|
||||
}
|
||||
indent.exit_level();
|
||||
|
||||
Ok(Stmt::Do { fun, block })
|
||||
}
|
||||
|
||||
fn parse_assignment_py(&mut self, indent: &mut Indent) -> Result<Stmt, String> {
|
||||
let pat = self.parse_assign_pattern_py()?;
|
||||
self.consume("=")?;
|
||||
let val = self.parse_term_py()?;
|
||||
self.consume(";")?;
|
||||
let nxt = self.parse_stmt_py(indent)?;
|
||||
Ok(Stmt::Assign { pat, val: Box::new(val), nxt: Box::new(nxt) })
|
||||
}
|
||||
|
||||
fn parse_assign_pattern_py(&mut self) -> Result<AssignPattern, String> {
|
||||
if self.skip_starts_with("(") {
|
||||
let mut binds = self.list_like(|p| p.parse_hvml_name(), "(", ")", ",", true, 1)?;
|
||||
if binds.len() == 1 {
|
||||
Ok(AssignPattern::Var(std::mem::take(&mut binds[0])))
|
||||
} else {
|
||||
Ok(AssignPattern::Tup(binds))
|
||||
}
|
||||
} else {
|
||||
self.parse_hvml_name().map(AssignPattern::Var)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_top_level_py(&mut self, indent: &mut Indent) -> Result<TopLevel, String> {
|
||||
self.skip_exact_indent(indent, false)?;
|
||||
if self.try_consume_keyword("def") {
|
||||
Ok(TopLevel::Def(self.parse_def_py(indent)?))
|
||||
} else if self.try_consume_keyword("enum") {
|
||||
Ok(TopLevel::Enum(self.parse_enum_py(indent)?))
|
||||
} else {
|
||||
self.expected("Enum or Def declaration")?
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_def_py(&mut self, indent: &mut Indent) -> Result<Definition, String> {
|
||||
let name = self.parse_hvml_name()?;
|
||||
let params = self.list_like(|p| p.parse_hvml_name(), "(", ")", ",", true, 0)?;
|
||||
self.consume(":")?;
|
||||
indent.enter_level();
|
||||
let body = self.parse_stmt_py(indent)?;
|
||||
indent.exit_level();
|
||||
Ok(Definition { name, params, body })
|
||||
}
|
||||
|
||||
fn parse_enum_py(&mut self, indent: &mut Indent) -> Result<Enum, String> {
|
||||
let name = self.parse_hvml_name()?;
|
||||
let mut variants = Vec::new();
|
||||
self.consume(":")?;
|
||||
indent.enter_level();
|
||||
loop {
|
||||
if !self.skip_exact_indent(indent, true)? {
|
||||
break;
|
||||
}
|
||||
let name = self.parse_hvml_name()?;
|
||||
let mut fields = Vec::new();
|
||||
if self.skip_starts_with("(") {
|
||||
fields = self.list_like(|p| p.parse_hvml_name(), "(", ")", ",", true, 0)?;
|
||||
}
|
||||
variants.push((name.clone(), Variant { name, fields }));
|
||||
}
|
||||
indent.exit_level();
|
||||
let variants = variants.into_iter().collect();
|
||||
Ok(Enum { name, variants })
|
||||
}
|
||||
|
||||
pub fn parse_program_py(&mut self) -> Result<Program, String> {
|
||||
let mut enums = IndexMap::<Name, Enum>::new();
|
||||
let mut defs = IndexMap::<Name, Definition>::new();
|
||||
let mut variants = IndexMap::<Name, Name>::new();
|
||||
|
||||
while {
|
||||
self.skip_newlines()?;
|
||||
true
|
||||
} && !self.is_eof()
|
||||
{
|
||||
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 })
|
||||
}
|
||||
|
||||
fn add_def_py(defs: &mut IndexMap<Name, Definition>, def: Definition) -> Result<(), String> {
|
||||
match defs.entry(def.name.clone()) {
|
||||
indexmap::map::Entry::Occupied(o) => Err(format!("Repeated definition '{}'.", o.get().name)),
|
||||
indexmap::map::Entry::Vacant(v) => {
|
||||
v.insert(def);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_enum_py(
|
||||
enums: &mut IndexMap<Name, Enum>,
|
||||
variants: &mut IndexMap<Name, Name>,
|
||||
r#enum: Enum,
|
||||
) -> Result<(), String> {
|
||||
match enums.entry(r#enum.name.clone()) {
|
||||
indexmap::map::Entry::Occupied(o) => return Err(format!("Repeated enum '{}'.", o.key())),
|
||||
indexmap::map::Entry::Vacant(v) => {
|
||||
for (name, variant) in r#enum.variants.iter() {
|
||||
match variants.entry(name.clone()) {
|
||||
indexmap::map::Entry::Occupied(_) => {
|
||||
return Err(format!("Repeated variant '{}'.", variant.name));
|
||||
}
|
||||
indexmap::map::Entry::Vacant(v) => _ = v.insert(r#enum.name.clone()),
|
||||
}
|
||||
}
|
||||
v.insert(r#enum);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(dead_code)]
|
||||
mod test {
|
||||
use super::PyParser;
|
||||
|
||||
#[test]
|
||||
fn parse_def() {
|
||||
let src = r#"
|
||||
enum Point:
|
||||
Point(x, y)
|
||||
|
||||
enum Bool:
|
||||
True()
|
||||
False()
|
||||
|
||||
def mk_point():
|
||||
return Point(y = 2, x = 1);
|
||||
|
||||
def identity(x):
|
||||
return x;
|
||||
|
||||
def inc(n):
|
||||
n += 1;
|
||||
return n;
|
||||
|
||||
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:
|
||||
return 1;
|
||||
case False:
|
||||
return 0;
|
||||
|
||||
def true():
|
||||
return True;
|
||||
|
||||
def fib(n):
|
||||
if n < 2:
|
||||
return n;
|
||||
else:
|
||||
return fib(n - 1) + fib(n - 2);
|
||||
|
||||
def swt(n):
|
||||
switch n:
|
||||
case 0:
|
||||
return 42;
|
||||
case _:
|
||||
return 1;
|
||||
|
||||
def fld(list):
|
||||
fold List.fold list:
|
||||
case List.cons:
|
||||
return 1;
|
||||
case List.nil:
|
||||
return 2;
|
||||
|
||||
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::term::Book::default());
|
||||
println!("{out}");
|
||||
}
|
||||
}
|
115
src/term/flavour_py/to_lang.rs
Normal file
115
src/term/flavour_py/to_lang.rs
Normal file
@ -0,0 +1,115 @@
|
||||
use crate::term::{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::Opr {
|
||||
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 { typ: lang::NumType::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::Opr { 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::Lst { 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!(),
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ use std::{borrow::Cow, collections::HashMap, ops::Deref};
|
||||
pub mod builtins;
|
||||
pub mod check;
|
||||
pub mod display;
|
||||
pub mod flavour_py;
|
||||
pub mod load_book;
|
||||
pub mod net_to_term;
|
||||
pub mod parser;
|
||||
|
@ -366,53 +366,6 @@ impl<'a> TermParser<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_oper(&mut self) -> Result<Op, String> {
|
||||
let opr = if self.try_consume("+") {
|
||||
Op::ADD
|
||||
} else if self.try_consume("-") {
|
||||
Op::SUB
|
||||
} else if self.try_consume("*") {
|
||||
Op::MUL
|
||||
} else if self.try_consume("/") {
|
||||
Op::DIV
|
||||
} else if self.try_consume("%") {
|
||||
Op::REM
|
||||
} else if self.try_consume("<") {
|
||||
Op::LTN
|
||||
} else if self.try_consume(">") {
|
||||
Op::GTN
|
||||
} else if self.try_consume("==") {
|
||||
Op::EQL
|
||||
} else if self.try_consume("!=") {
|
||||
Op::NEQ
|
||||
} else if self.try_consume("&") {
|
||||
Op::AND
|
||||
} else if self.try_consume("|") {
|
||||
Op::OR
|
||||
} else if self.try_consume("^") {
|
||||
Op::XOR
|
||||
} else {
|
||||
return self.expected("numeric operator");
|
||||
};
|
||||
Ok(opr)
|
||||
}
|
||||
|
||||
fn try_consume_keyword(&mut self, keyword: &str) -> bool {
|
||||
self.skip_trivia();
|
||||
|
||||
if !self.starts_with(keyword) {
|
||||
return false;
|
||||
}
|
||||
let input = &self.input()[*self.index() + keyword.len() ..];
|
||||
let next_is_name = input.chars().next().map_or(false, is_name_char);
|
||||
if !next_is_name {
|
||||
self.consume(keyword).unwrap();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_top_level_name(&mut self) -> Result<Name, String> {
|
||||
let ini_idx = *self.index();
|
||||
let nam = self.parse_hvml_name()?;
|
||||
@ -425,11 +378,6 @@ impl<'a> TermParser<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_hvml_name(&mut self) -> Result<Name, String> {
|
||||
let nam = self.parse_name()?;
|
||||
Ok(Name::new(nam))
|
||||
}
|
||||
|
||||
fn parse_name_or_era(&mut self) -> Result<Option<Name>, String> {
|
||||
self.labelled(
|
||||
|p| {
|
||||
@ -531,88 +479,6 @@ impl<'a> TermParser<'a> {
|
||||
self.consume("}")?;
|
||||
Ok(Term::Swt { arg: Box::new(arg), bnd: Some(bnd), with, pred, arms })
|
||||
}
|
||||
|
||||
/* 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<char> {
|
||||
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<T>(
|
||||
&mut self,
|
||||
parser: impl Fn(&mut Self) -> Result<T, String>,
|
||||
start: &str,
|
||||
end: &str,
|
||||
sep: &str,
|
||||
hard_sep: bool,
|
||||
min_els: usize,
|
||||
) -> Result<Vec<T>, 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<T>(
|
||||
&mut self,
|
||||
parser: impl Fn(&mut Self) -> Result<T, String>,
|
||||
label: &str,
|
||||
) -> Result<T, String> {
|
||||
match parser(self) {
|
||||
Ok(val) => Ok(val),
|
||||
Err(_) => self.expected(label),
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_spanned<T>(&mut self, exp: &str, ini_idx: usize, end_idx: usize) -> Result<T, String> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> for TermParser<'a> {
|
||||
@ -690,3 +556,139 @@ fn add_ctx_to_msg(msg: &str, ini_idx: usize, end_idx: usize, file: &str) -> Stri
|
||||
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> {
|
||||
fn labelled<T>(
|
||||
&mut self,
|
||||
parser: impl Fn(&mut Self) -> Result<T, String>,
|
||||
label: &str,
|
||||
) -> Result<T, String> {
|
||||
match parser(self) {
|
||||
Ok(val) => Ok(val),
|
||||
Err(_) => self.expected(label),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_hvml_name(&mut self) -> Result<Name, String> {
|
||||
let nam = self.parse_name()?;
|
||||
Ok(Name::new(nam))
|
||||
}
|
||||
|
||||
fn skip_peek_one(&mut self) -> Option<char> {
|
||||
self.skip_trivia();
|
||||
self.peek_one()
|
||||
}
|
||||
|
||||
/// 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 expected_spanned<T>(&mut self, exp: &str, ini_idx: usize, end_idx: usize) -> Result<T, String> {
|
||||
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 try_consume_keyword(&mut self, keyword: &str) -> bool {
|
||||
self.skip_trivia();
|
||||
|
||||
if !self.starts_with(keyword) {
|
||||
return false;
|
||||
}
|
||||
let input = &self.input()[*self.index() + keyword.len() ..];
|
||||
let next_is_name = input.chars().next().map_or(false, is_name_char);
|
||||
if !next_is_name {
|
||||
self.consume(keyword).unwrap();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<T>(
|
||||
&mut self,
|
||||
parser: impl Fn(&mut Self) -> Result<T, String>,
|
||||
start: &str,
|
||||
end: &str,
|
||||
sep: &str,
|
||||
hard_sep: bool,
|
||||
min_els: usize,
|
||||
) -> Result<Vec<T>, 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 parse_oper(&mut self) -> Result<Op, String> {
|
||||
let opr = if self.try_consume("+") {
|
||||
Op::ADD
|
||||
} else if self.try_consume("-") {
|
||||
Op::SUB
|
||||
} else if self.try_consume("*") {
|
||||
Op::MUL
|
||||
} else if self.try_consume("/") {
|
||||
Op::DIV
|
||||
} else if self.try_consume("%") {
|
||||
Op::REM
|
||||
} else if self.try_consume("<") {
|
||||
Op::LTN
|
||||
} else if self.try_consume(">") {
|
||||
Op::GTN
|
||||
} else if self.try_consume("==") {
|
||||
Op::EQL
|
||||
} else if self.try_consume("!=") {
|
||||
Op::NEQ
|
||||
} else if self.try_consume("&") {
|
||||
Op::AND
|
||||
} else if self.try_consume("|") {
|
||||
Op::OR
|
||||
} else if self.try_consume("^") {
|
||||
Op::XOR
|
||||
} else {
|
||||
return self.expected("numeric operator");
|
||||
};
|
||||
Ok(opr)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user