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:
Nicolas Abril 2024-05-06 14:25:20 +02:00 committed by GitHub
commit 39ff4ffb7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1136 additions and 143 deletions

View File

@ -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
View 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,
}
}
}

View 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);
}

View 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}");
}
}

View 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!(),
}
}
}

View File

@ -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;

View File

@ -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)
}
}