[sc-501] Use TSPL for the parser

This commit is contained in:
Nicolas Abril 2024-04-10 19:27:47 +02:00
parent d1bbc40146
commit 9db85265c4
68 changed files with 1034 additions and 1277 deletions

125
Cargo.lock generated
View File

@ -3,15 +3,12 @@
version = 3
[[package]]
name = "ahash"
version = "0.8.11"
name = "TSPL"
version = "0.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
checksum = "5a9423b1e6e2d6c0bbc03660f58f9c30f55359e13afea29432e6e767c0f7dc25"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
"highlight_error",
]
[[package]]
@ -68,12 +65,6 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "beef"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]]
name = "cc"
version = "1.0.92"
@ -86,16 +77,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chumsky"
version = "1.0.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc3172a80699de358070dd99f80ea8badc6cdf8ac2417cb5a96e6d81bf5fe06d"
dependencies = [
"hashbrown 0.13.2",
"stacker",
]
[[package]]
name = "clap"
version = "4.5.4"
@ -172,21 +153,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
@ -208,8 +174,9 @@ checksum = "809e18805660d7b6b2e2b9f316a5099521b5998d5cba4dda11b5157a21aaef03"
[[package]]
name = "hvm-core"
version = "0.2.24"
source = "git+https://github.com/HigherOrderCO/hvm-core.git#54cf65f53cf15add868bc504c89bbdd5686d7160"
source = "git+https://github.com/HigherOrderCO/hvm-core.git#40febf34ce6c6b09a249553574da9835cb1ca976"
dependencies = [
"TSPL",
"arrayvec",
"clap",
"nohash-hasher",
@ -221,7 +188,7 @@ dependencies = [
name = "hvm-lang"
version = "0.1.0"
dependencies = [
"chumsky",
"TSPL",
"clap",
"highlight_error",
"hvm-core",
@ -229,7 +196,6 @@ dependencies = [
"insta",
"interner",
"itertools",
"logos",
"stacker",
"stdext",
"walkdir",
@ -242,7 +208,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown 0.14.3",
"hashbrown",
]
[[package]]
@ -290,51 +256,12 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "logos"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "161971eb88a0da7ae0c333e1063467c5b5727e7fb6b710b8db4814eade3a42e8"
dependencies = [
"logos-derive",
]
[[package]]
name = "logos-codegen"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e31badd9de5131fdf4921f6473d457e3dd85b11b7f091ceb50e4df7c3eeb12a"
dependencies = [
"beef",
"fnv",
"lazy_static",
"proc-macro2",
"quote",
"regex-syntax",
"syn",
]
[[package]]
name = "logos-derive"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c2a69b3eb68d5bd595107c9ee58d7e07fe2bb5e360cc85b0f084dedac80de0a"
dependencies = [
"logos-codegen",
]
[[package]]
name = "nohash-hasher"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "proc-macro2"
version = "1.0.79"
@ -355,19 +282,13 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.35"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex-syntax"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "same-file"
version = "1.0.6"
@ -451,12 +372,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.5.0"
@ -563,23 +478,3 @@ name = "windows_x86_64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]]
name = "zerocopy"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@ -20,15 +20,13 @@ default = ["cli"]
cli = ["dep:clap"]
[dependencies]
# Later versions of the alpha contains bugs that changes the parser errors
chumsky = { version = "= 1.0.0-alpha.4", features = ["label"] }
TSPL = "0.0.9"
clap = { version = "4.4.1", features = ["derive"], optional = true }
highlight_error = "0.1.1"
hvm-core = { git = "https://github.com/HigherOrderCO/hvm-core.git" }
indexmap = "2.2.3"
interner = "0.2.1"
itertools = "0.11.0"
logos = "0.14.0"
stacker = "0.1"
[dev-dependencies]

View File

@ -138,7 +138,7 @@ StrEx2 = (String.cons 'H' (String.cons 'e', (String.cons 'l' (String.cons 'l', (
Characters are delimited by `'` `'` and support Unicode escape sequences. They have a numeric value associated with them.
```
main = '\u4242'
main = '\u{4242}'
```
Lists are delimited by `[` `]` and elements can be optionally separated by `,`.

View File

@ -11,6 +11,7 @@
"builtins",
"callcc",
"chumsky",
"codepoint",
"combinators",
"concat",
"ctrs",
@ -24,6 +25,7 @@
"effectful",
"foldl",
"hasher",
"hexdigit",
"hvm's",
"hvmc",
"hvml",
@ -80,6 +82,7 @@
"succ",
"supercombinator",
"supercombinators",
"TSPL",
"tunr",
"unbounds",
"vectorize",

9
examples/gen_tree.hvm Normal file
View File

@ -0,0 +1,9 @@
Tree.leaf = λnode λleaf leaf
Tree.node = λval λlft λrgt λnode λleaf (node val lft rgt)
Tree.gen n x = switch n {
0: Tree.leaf
_: (Tree.node x (Tree.gen n-1 (+ (* x 2) 1)) (Tree.gen n-1 (+ (* x 2) 2)))
}
main = (Tree.gen 8 2)

View File

@ -1,17 +0,0 @@
_Char = 0
_List = λ_T 0
_List.cons = λ_head λ_tail λ_P λ_cons λ_nil ((_cons _head) _tail)
_List.nil = λ_P λ_cons λ_nil _nil
_Nat = 0
_Nat.succ = λ_n λ_P λ_succ λ_zero (_succ _n)
_Nat.zero = λ_P λ_succ λ_zero _zero
_String = (_List _Char)
_String.cons = λ_head λ_tail λ_P λ_cons λ_nil ((_cons _head) _tail)
_String.nil = λ_P λ_cons λ_nil _nil
_Tree = λ_A 0
_Tree.gen = λ_n λ_x switch _n = _n { 0: _Tree.leaf _: let _n-1 = _n-1 (((_Tree.node _x) ((_Tree.gen _n-1) (+ (* _x 2) 1))) ((_Tree.gen _n-1) (+ (* _x 2) 2))) }
_Tree.leaf = λ_P λ_node λ_leaf _leaf
_Tree.node = λ_val λ_lft λ_rgt λ_P λ_node λ_leaf (((_node _val) _lft) _rgt)
_main = ((_Tree.gen 8) 2)
main = _main

View File

@ -486,3 +486,10 @@ pub struct RunStats {
pub used: usize,
pub run_time: f64,
}
fn maybe_grow<R, F>(f: F) -> R
where
F: FnOnce() -> R,
{
stacker::maybe_grow(1024 * 32, 1024 * 1024, f)
}

View File

@ -19,7 +19,7 @@ struct Cli {
pub verbose: bool,
#[arg(short = 'e', long, global = true, help = "Use other entrypoint rather than main or Main")]
pub entrypoint: Option<Name>,
pub entrypoint: Option<String>,
}
#[derive(Subcommand, Clone, Debug)]
@ -96,11 +96,7 @@ enum Mode {
#[arg(help = "Path to the input file")]
path: PathBuf,
#[arg(value_parser = |arg: &str| hvml::term::parser::parse_term(arg)
.map_err(|e| match e[0].reason() {
chumsky::error::RichReason::Many(errs) => format!("{}", &errs[0]),
_ => format!("{}", e[0].reason()),
}))]
#[arg(value_parser = |arg: &str| hvml::term::parser::TermParser::new_term(arg))]
arguments: Option<Vec<hvml::term::Term>>,
},
/// Runs the lambda-term level desugaring passes.
@ -292,7 +288,7 @@ fn execute_cli_mode(mut cli: Cli) -> Result<(), Diagnostics> {
let load_book = |path: &Path| -> Result<Book, Diagnostics> {
let mut book = load_file_to_book(path)?;
book.entrypoint = entrypoint;
book.entrypoint = entrypoint.map(Name::new);
if arg_verbose {
println!("{book}");

View File

@ -1,4 +1,5 @@
use super::{parser::parse_book, Book, Name, Pattern, Term};
use super::{parser::TermParser, Book, Name, Pattern, Term};
use crate::maybe_grow;
const BUILTINS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/term/builtins.hvm"));
@ -23,7 +24,8 @@ pub const NAT_ZERO: &str = "Nat.zero";
impl Book {
pub fn builtins() -> Book {
parse_book(BUILTINS, Book::default, true).expect("Error parsing builtin file, this should not happen")
TermParser::new_book(BUILTINS, Book::default(), true)
.expect("Error parsing builtin file, this should not happen")
}
pub fn encode_builtins(&mut self) {
@ -38,7 +40,7 @@ impl Book {
impl Term {
fn encode_builtins(&mut self) {
Term::recursive_call(move || match self {
maybe_grow(|| match self {
Term::Lst { els } => *self = Term::encode_list(std::mem::take(els)),
Term::Str { val } => *self = Term::encode_str(val),
Term::Nat { val } => *self = Term::encode_nat(*val),
@ -91,20 +93,20 @@ impl Pattern {
}
fn encode_list(elements: Vec<Pattern>) -> Pattern {
let lnil = Pattern::Ctr(Name::from(LNIL), vec![]);
let lnil = Pattern::Ctr(Name::new(LNIL), vec![]);
elements.into_iter().rfold(lnil, |acc, mut nxt| {
nxt.encode_builtins();
Pattern::Ctr(Name::from(LCONS), vec![nxt, acc])
Pattern::Ctr(Name::new(LCONS), vec![nxt, acc])
})
}
fn encode_str(str: &str) -> Pattern {
let lnil = Pattern::Ctr(Name::from(SNIL), vec![]);
let lnil = Pattern::Ctr(Name::new(SNIL), vec![]);
str.chars().rfold(lnil, |tail, head| {
let head = Pattern::Num(head as u64);
Pattern::Ctr(Name::from(SCONS), vec![head, tail])
Pattern::Ctr(Name::new(SCONS), vec![head, tail])
})
}
}

View File

@ -43,7 +43,7 @@ impl Ctx<'_> {
}
(None, None, None) => {
let entrypoint = self.book.entrypoint.clone().unwrap_or(Name::from(ENTRY_POINT));
let entrypoint = self.book.entrypoint.clone().unwrap_or(Name::new(ENTRY_POINT));
self.info.add_book_error(EntryErr::NotFound(entrypoint))
}
}
@ -59,8 +59,8 @@ fn validate_entry_point(entry: &Definition) -> Result<Name, EntryErr> {
impl Book {
fn get_possible_entry_points(&self) -> (Option<&Definition>, Option<&Definition>, Option<&Definition>) {
let custom = self.entrypoint.as_ref().and_then(|e| self.defs.get(e));
let main = self.defs.get(&Name::from(ENTRY_POINT));
let hvm1_main = self.defs.get(&Name::from(HVM1_ENTRY_POINT));
let main = self.defs.get(&Name::new(ENTRY_POINT));
let hvm1_main = self.defs.get(&Name::new(HVM1_ENTRY_POINT));
(custom, main, hvm1_main)
}
}

View File

@ -1,5 +1,6 @@
use crate::{
diagnostics::{Diagnostics, ToStringVerbose},
maybe_grow,
term::{Ctx, Name, Term},
};
use std::collections::{hash_map::Entry, HashMap};
@ -62,7 +63,7 @@ pub fn check_uses<'a>(
globals: &mut HashMap<&'a Name, (usize, usize)>,
errs: &mut Vec<UnboundVarErr>,
) {
Term::recursive_call(move || match term {
maybe_grow(move || match term {
Term::Var { nam } => {
if !scope.contains_key(nam) {
errs.push(UnboundVarErr::Local(nam.clone()));

View File

@ -1,4 +1,5 @@
use super::{Book, Definition, Name, NumCtr, Pattern, Rule, Tag, Term};
use crate::maybe_grow;
use std::{fmt, ops::Deref};
/* Some aux structures for things that are not so simple to display */
@ -41,7 +42,7 @@ macro_rules! display {
impl fmt::Display for Term {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Term::recursive_call(move || match self {
maybe_grow(|| match self {
Term::Lam { tag, nam, bod } => {
write!(f, "{}λ{} {}", tag.display_padded(), var_as_str(nam), bod)
}

View File

@ -1,8 +1,4 @@
use super::{
parser::{parse_book, parser::error_to_msg},
Book,
};
use itertools::Itertools;
use super::{parser::TermParser, Book};
use std::path::Path;
/// Reads a file and parses to a definition book.
@ -12,8 +8,5 @@ pub fn load_file_to_book(path: &Path) -> Result<Book, String> {
}
pub fn do_parse_book(code: &str, path: &Path) -> Result<Book, String> {
match parse_book(code, Book::builtins, false) {
Ok(book) => Ok(book),
Err(errs) => Err(errs.iter().map(|e| error_to_msg(e, code, path)).join("\n")),
}
TermParser::new_book(code, Book::builtins(), false).map_err(|e| format!("In {} :\n{}", path.display(), e))
}

View File

@ -1,6 +1,7 @@
use self::parser::lexer::STRINGS;
use self::parser::STRINGS;
use crate::{
diagnostics::{Diagnostics, DiagnosticsConfig},
maybe_grow,
term::builtins::*,
ENTRY_POINT,
};
@ -303,7 +304,7 @@ impl Tag {
impl Clone for Term {
fn clone(&self) -> Self {
Self::recursive_call(move || match self {
maybe_grow(|| match self {
Self::Lam { tag, nam, bod } => Self::Lam { tag: tag.clone(), nam: nam.clone(), bod: bod.clone() },
Self::Var { nam } => Self::Var { nam: nam.clone() },
Self::Chn { tag, nam, bod } => Self::Chn { tag: tag.clone(), nam: nam.clone(), bod: bod.clone() },
@ -416,7 +417,7 @@ impl Term {
}
pub fn r#ref(name: &str) -> Self {
Term::Ref { nam: name.into() }
Term::Ref { nam: Name::new(name) }
}
pub fn str(str: &str) -> Self {
@ -654,12 +655,6 @@ impl Term {
}
/* Common checks and transformations */
pub fn recursive_call<R, F>(f: F) -> R
where
F: FnOnce() -> R,
{
stacker::maybe_grow(1024 * 32, 1024 * 1024, f)
}
/// Substitute the occurrences of a variable in a term with the given term.
///
@ -669,7 +664,7 @@ impl Term {
/// Expects var bind information to be properly stored in match expressions,
/// so it must run AFTER `fix_match_terms`.
pub fn subst(&mut self, from: &Name, to: &Term) {
Term::recursive_call(|| {
maybe_grow(|| {
for (child, binds) in self.children_mut_with_binds() {
if !binds.flat_map(|b| b.as_ref()).contains(from) {
child.subst(from, to);
@ -686,7 +681,7 @@ impl Term {
/// Substitute the occurrence of an unscoped variable with the given term.
pub fn subst_unscoped(&mut self, from: &Name, to: &Term) {
Term::recursive_call(|| {
maybe_grow(|| {
// We don't check the unscoped binds because there can be only one bind of an unscoped var.
// TODO: potentially there could be some situation where this causes an incorrect program to compile?
for child in self.children_mut() {
@ -705,7 +700,7 @@ impl Term {
/// and the number of times each var is used
pub fn free_vars(&self) -> HashMap<Name, u64> {
fn go(term: &Term, free_vars: &mut HashMap<Name, u64>) {
Term::recursive_call(move || {
maybe_grow(|| {
if let Term::Var { nam } = term {
*free_vars.entry(nam.clone()).or_default() += 1;
}
@ -731,7 +726,7 @@ impl Term {
/// Returns the set of declared and the set of used unscoped variables
pub fn unscoped_vars(&self) -> (IndexSet<Name>, IndexSet<Name>) {
fn go(term: &Term, decls: &mut IndexSet<Name>, uses: &mut IndexSet<Name>) {
Term::recursive_call(move || {
maybe_grow(|| {
match term {
Term::Chn { nam: Some(nam), .. } => {
decls.insert(nam.clone());
@ -865,25 +860,19 @@ impl Name {
impl Default for Name {
fn default() -> Self {
Self::from("")
}
}
impl From<&str> for Name {
fn from(value: &str) -> Self {
Name(STRINGS.get(value))
Self::new("")
}
}
impl From<u64> for Name {
fn from(value: u64) -> Self {
num_to_name(value).as_str().into()
Name::new(num_to_name(value).as_str())
}
}
impl From<u32> for Name {
fn from(value: u32) -> Self {
num_to_name(value as u64).as_str().into()
Name::new(num_to_name(value as u64).as_str())
}
}

View File

@ -1,5 +1,6 @@
use crate::{
diagnostics::{DiagnosticOrigin, Diagnostics, Severity},
maybe_grow,
net::{INet, NodeId, NodeKind::*, Port, SlotId, ROOT},
term::{num_to_name, term_to_net::Labels, Book, Name, Tag, Term},
};
@ -68,10 +69,10 @@ pub struct Reader<'a> {
impl Reader<'_> {
fn read_term(&mut self, next: Port) -> Term {
Term::recursive_call(move || {
maybe_grow(|| {
if self.dup_paths.is_none() && !self.seen.insert(next) {
self.error(ReadbackError::Cyclic);
return Term::Var { nam: Name::from("...") };
return Term::Var { nam: Name::new("...") };
}
let node = next.node();
@ -122,7 +123,7 @@ impl Reader<'_> {
Term::Lam { nam, bod, .. } => {
// Extract non-var args so we can refer to the pred.
let (arg, bind) = if let Term::Var { nam } = &mut arg {
(std::mem::replace(nam, Name::from("")), None)
(std::mem::take(nam), None)
} else {
(self.namegen.unique(), Some(arg))
};
@ -341,7 +342,7 @@ impl Term {
/// This has the effect of inserting the split at the lowest common ancestor
/// of all of the uses of `fst` and `snd`.
fn insert_split(&mut self, split: &mut Split, threshold: usize) -> Option<usize> {
Term::recursive_call(move || {
maybe_grow(|| {
let mut n = match self {
Term::Var { nam } => usize::from(split.fst == *nam || split.snd == *nam),
_ => 0,
@ -374,7 +375,7 @@ impl Term {
}
}
Term::recursive_call(move || match self {
maybe_grow(|| match self {
Term::Ref { nam: def_name } => {
if def_name.is_generated() {
let def = book.defs.get(def_name).unwrap();

813
src/term/parser.rs Normal file
View File

@ -0,0 +1,813 @@
use crate::{
maybe_grow,
term::{
display::DisplayFn, Adt, Book, Definition, IntOp, MatchRule, Name, NumCtr, Op, OpType, Pattern, Rule,
SwitchRule, Tag, Term,
},
};
use highlight_error::highlight_error;
use interner::global::GlobalPool;
use TSPL::Parser;
pub static STRINGS: GlobalPool<String> = GlobalPool::new();
// hvml grammar description:
// <Book> ::= (<Data> | <Rule>)*
// <Data> ::= "data" <Name> "=" ( <Name> | "(" <Name> (<Name>)* ")" )+
// <Rule> ::= ("(" <Name> <Pattern>* ")" | <Name> <Pattern>*) "=" <Term>
// <Pattern> ::= "(" <Name> <Pattern>* ")" | <NameEra> | <Number> | "(" <Pattern> ("," <Pattern>)+ ")"
// <Term> ::=
// <Number> | <NumOp> | <Tup> | <App> | <Group> | <Nat> | <Lam> | <UnscopedLam> |
// <Use> | <Dup> | <LetTup> | <Let> | <Match> | <Switch> | <Era> | <UnscopedVar> | <Var>
// <Lam> ::= <Tag>? ("λ"|"@") <NameEra> <Term>
// <UnscopedLam>::= <Tag>? ("λ"|"@") "$" <Name> <Term>
// <NumOp> ::= "(" <Operator> <Term> <Term> ")"
// <Tup> ::= "(" <Term> ("," <Term>)+ ")"
// <App> ::= <Tag>? "(" <Term> (<Term>)+ ")"
// <Group> ::= "(" <Term> ")"
// <Use> ::= "use" <Name> "=" <Term> ";"? <Term>
// <Let> ::= "let" <NameEra> "=" <Term> ";"? <Term>
// <LetTup> ::= "let" "(" <NameEra> ("," <NameEra>)+ ")" "=" <Term> ";"? <Term>
// <Dup> ::= "let" <Tag>? "{" <NameEra> (","? <NameEra>)+ "}" "=" <Term> ";"? <Term>
// <List> ::= "[" (<Term> ","?)* "]"
// <String> ::= "\"" (escape sequence | [^"])* "\""
// <Char> ::= "'" (escape sequence | [^']) "'"
// <Match> ::= "match" (<Term> | <Name> "=" <Term>) ("with" <Var> (","? <Var>)*)? "{" <MatchArm>+ "}"
// <MatchArm> ::= "|"? <Pattern> ":" <Term> ";"?
// <Switch> ::= "match" (<Term> | <Name> "=" <Term>) ("with" <Var> (","? <Var>)*)? "{" <SwitchArm>+ "}"
// <SwitchArm> ::= "|"? (<Num>|"_") ":" <Term> ";"?
// <Var> ::= <Name>
// <UnscopedVar>::= "$" <Name>
// <NameEra> ::= <Name> | "*"
// <Era> ::= "*"
// <Tag> ::= "#" <Name>
// <Name> ::= [_\-./a-zA-Z0-9]+
// <Number> ::= ([0-9]+ | "0x"[0-9a-fA-F]+ | "0b"[01]+)
// <Operator> ::= ( "+" | "-" | "*" | "/" | "%" | "==" | "!=" | "<<" | ">>" | "<=" | ">=" | "<" | ">" | "^" )
TSPL::new_parser!(TermParser);
impl<'a> TermParser<'a> {
// TODO: Since TSPL doesn't expose `new` we need something that creates the parser.
pub fn new_book(input: &'a str, default_book: Book, builtin: bool) -> Result<Book, String> {
Self::new(input).parse_book(default_book, builtin)
}
pub fn new_term(input: &'a str) -> Result<Term, String> {
Self::new(input).parse_term()
}
/* AST parsing functions */
fn parse_book(&mut self, default_book: Book, builtin: bool) -> Result<Book, String> {
let mut book = default_book;
self.skip_trivia();
while !self.is_eof() {
let ini_idx = *self.index();
if self.skip_starts_with("data") {
// adt declaration
let (nam, adt) = self.parse_datatype(builtin)?;
let end_idx = *self.index();
book.add_adt(nam, adt).map_err(|e| add_ctx(&e, ini_idx, end_idx, self.input()))?;
} else {
// function declaration rule
let (name, rule) = self.parse_rule()?;
book.add_rule(name, rule, builtin);
}
self.skip_trivia();
}
Ok(book)
}
fn parse_datatype(&mut self, builtin: bool) -> Result<(Name, Adt), String> {
// data name = ctr (| ctr)*
self.consume("data")?;
let name = self.labelled(|p| p.parse_hvml_name(), "datatype name")?;
self.consume("=")?;
let mut ctrs = vec![self.parse_datatype_ctr()?];
while self.try_consume("|") {
ctrs.push(self.parse_datatype_ctr()?);
}
let ctrs = ctrs.into_iter().collect();
let adt = Adt { ctrs, builtin };
Ok((name, adt))
}
fn parse_datatype_ctr(&mut self) -> Result<(Name, Vec<Name>), String> {
if self.skip_starts_with("(") {
// (name field*)
let field_parser = |p: &mut Self| p.labelled(|p| p.parse_hvml_name(), "datatype constructor field");
let mut els = self.list_like(field_parser, "(", ")", "", false, 1)?;
let fields = els.split_off(1);
let name = els.into_iter().next().unwrap();
Ok((name, fields))
} else {
// name
let name = self.labelled(|p| p.parse_hvml_name(), "datatype constructor name")?;
Ok((name, vec![]))
}
}
fn parse_rule(&mut self) -> Result<(Name, Rule), String> {
let (name, pats) = if self.try_consume("(") {
let name = self.labelled(|p| p.parse_hvml_name(), "function name")?;
let pats = self.list_like(|p| p.parse_rule_pattern(), "", ")", "", false, 0)?;
(name, pats)
} else {
let name = self.labelled(|p| p.parse_hvml_name(), "top-level definition")?;
let mut pats = vec![];
while !self.skip_starts_with("=") {
pats.push(self.parse_rule_pattern()?);
}
(name, pats)
};
self.consume("=")?;
let body = self.parse_term()?;
let rule = Rule { pats, body };
Ok((name, rule))
}
fn parse_rule_pattern(&mut self) -> Result<Pattern, String> {
maybe_grow(|| {
let Some(head) = self.skip_peek_one() else { return self.expected("pattern-matching pattern") };
let pat = match head {
// Ctr or Tup
'(' => {
self.consume("(")?;
let head_ini_idx = *self.index();
let head = self.parse_rule_pattern()?;
let head_end_idx = *self.index();
if self.try_consume(",") {
// Tup
let mut els = self.list_like(|p| p.parse_rule_pattern(), "", ")", ",", true, 1)?;
els.insert(0, head);
Pattern::Tup(els)
} else {
// Ctr
let Pattern::Var(Some(name)) = head else {
return self.expected_spanned("constructor name", head_ini_idx, head_end_idx);
};
let els = self.list_like(|p| p.parse_rule_pattern(), "", ")", "", false, 0)?;
Pattern::Ctr(name, els)
}
}
// List
'[' => {
let els = self.list_like(|p| p.parse_rule_pattern(), "[", "]", ",", false, 0)?;
Pattern::Lst(els)
}
// String
'\"' => {
let str = self.parse_quoted_string()?;
Pattern::Str(STRINGS.get(str))
}
// Char
'\'' => {
let char = self.parse_quoted_char()?;
Pattern::Num(char as u64)
}
// Number
c if c.is_numeric() => {
let num = self.parse_u64()?;
Pattern::Num(num)
}
// Var
_ => {
let name = self.parse_name_or_era()?;
Pattern::Var(name)
}
};
Ok(pat)
})
}
fn parse_term(&mut self) -> Result<Term, String> {
maybe_grow(|| {
let Some(head) = self.skip_peek_one() else { return self.expected("term") };
let term = match head {
// Lambda, unscoped lambda
'λ' | '@' => self.parse_lambda(Tag::Static)?,
// App, Tup, Num Op
'(' => {
self.consume("(")?;
let starts_with_oper = self.skip_peek_one().map_or(false, |c| "+-*/%&|<>^=!".contains(c));
if starts_with_oper {
let opr = self.parse_oper()?;
if self.skip_starts_with(",")
&& let Op { ty: _, op: IntOp::Mul } = opr
{
// jk, actually a tuple
let mut els = vec![Term::Era];
while self.try_consume(",") {
els.push(self.parse_term()?);
}
self.consume(")")?;
Term::Tup { els }
} else {
let fst = self.parse_term()?;
let snd = self.parse_term()?;
self.consume(")")?;
Term::Opx { opr, fst: Box::new(fst), snd: Box::new(snd) }
}
} else {
// Tup or App
let head = self.parse_term()?;
if self.skip_starts_with(",") {
// Tup
let mut els = vec![head];
while self.try_consume(",") {
els.push(self.parse_term()?);
}
self.consume(")")?;
Term::Tup { els }
} else {
// App
let els = self.list_like(|p| p.parse_term(), "", ")", "", false, 0)?;
els.into_iter().fold(head, |fun, arg| Term::App {
tag: Tag::Static,
fun: Box::new(fun),
arg: Box::new(arg),
})
}
}
}
// List
'[' => {
let els = self.list_like(|p| p.parse_term(), "[", "]", ",", false, 0)?;
Term::Lst { els }
}
// Sup
'{' => {
let els = self.list_like(|p| p.parse_term(), "{", "}", ",", false, 2)?;
Term::Sup { tag: Tag::Auto, els }
}
// Unscoped var
'$' => {
self.consume("$")?;
let nam = self.parse_hvml_name()?;
Term::Lnk { nam }
}
// Era
'*' => {
self.consume("*")?;
Term::Era
}
// Nat, tagged lambda, tagged sup, tagged app
'#' => {
let Some(head) = self.peek_many(2) else { return self.expected("tagged term or nat") };
let head = head.chars().collect::<Vec<_>>();
if head[1].is_numeric() {
// Nat
self.consume("#")?;
let val = self.parse_u64()?;
Term::Nat { val }
} else {
// Tagged term
let tag = self.parse_tag()?;
let Some(head) = self.skip_peek_one() else { return self.expected("tagged term") };
match head {
// Tagged app
'(' => {
let els = self.list_like(|p| p.parse_term(), "(", ")", "", false, 2)?;
els
.into_iter()
.reduce(|fun, arg| Term::App { tag: tag.clone(), fun: Box::new(fun), arg: Box::new(arg) })
.unwrap()
}
// Tagged sup
'{' => {
let els = self.list_like(|p| p.parse_term(), "{", "}", ",", false, 2)?;
Term::Sup { tag, els }
}
// Tagged lambda
'λ' | '@' => self.parse_lambda(tag)?,
_ => return self.expected("tagged term"),
}
}
}
// String
'"' => {
let val = self.parse_quoted_string()?;
Term::Str { val: STRINGS.get(val) }
}
// Char
'\'' => {
let chr = self.parse_quoted_char()?;
Term::Num { val: chr as u64 }
}
// Native num
c if c.is_numeric() => {
let val = self.parse_u64()?;
Term::Num { val }
}
_ => {
if self.try_consume("use") {
// Use
let nam = self.parse_hvml_name()?;
self.consume("=")?;
let val = self.parse_term()?;
self.try_consume(";");
let nxt = self.parse_term()?;
Term::Use { nam: Some(nam), val: Box::new(val), nxt: Box::new(nxt) }
} else if self.try_consume("let") {
// Let, let tup, dup, tagged dup
let Some(head) = self.skip_peek_one() else { return self.expected("let bind") };
match head {
// tagged dup
'#' => {
let tag = self.parse_tag()?;
let bnd = self.list_like(|p| p.parse_name_or_era(), "{", "}", ",", false, 2)?;
self.consume("=")?;
let val = self.parse_term()?;
self.try_consume(";");
let nxt = self.parse_term()?;
Term::Dup { tag, bnd, val: Box::new(val), nxt: Box::new(nxt) }
}
// dup
'{' => {
let bnd = self.list_like(|p| p.parse_name_or_era(), "{", "}", ",", false, 2)?;
self.consume("=")?;
let val = self.parse_term()?;
self.try_consume(";");
let nxt = self.parse_term()?;
Term::Dup { tag: Tag::Auto, bnd, val: Box::new(val), nxt: Box::new(nxt) }
}
// Let tup
'(' => {
let bnd = self.list_like(|p| p.parse_name_or_era(), "(", ")", ",", true, 2)?;
self.consume("=")?;
let val = self.parse_term()?;
self.try_consume(";");
let nxt = self.parse_term()?;
Term::Ltp { bnd, val: Box::new(val), nxt: Box::new(nxt) }
}
// let
_ => {
let nam = self.parse_name_or_era()?;
self.consume("=")?;
let val = self.parse_term()?;
self.try_consume(";");
let nxt = self.parse_term()?;
Term::Let { nam, val: Box::new(val), nxt: Box::new(nxt) }
}
}
} else if self.try_consume("match") {
// match
let arg_ini_idx = *self.index();
let mut arg = self.parse_term()?;
let arg_end_idx = *self.index();
let (bnd, arg) = if self.skip_starts_with("=") {
if let Term::Var { nam } = &mut arg {
self.consume("=")?;
let term = self.parse_term()?;
(Some(std::mem::take(nam)), term)
} else {
return self.expected_spanned("var", arg_ini_idx, arg_end_idx);
}
} else {
(None, arg)
};
let with = if self.try_consume("with") {
let mut with = vec![self.parse_hvml_name()?];
while !self.skip_starts_with("{") {
self.try_consume(",");
with.push(self.parse_hvml_name()?);
}
with
} else {
vec![]
};
let rules = self.list_like(|p| p.parse_match_arm(), "{", "}", ";", false, 1)?;
if let Some(bnd) = bnd {
Term::Let {
nam: Some(bnd.clone()),
val: Box::new(arg),
nxt: Box::new(Term::Mat { arg: Box::new(Term::Var { nam: bnd }), with, rules }),
}
} else {
Term::Mat { arg: Box::new(arg), with, rules }
}
} else if self.try_consume("switch") {
// switch
let arg_ini_idx = *self.index();
let mut arg = self.parse_term()?;
let arg_end_idx = *self.index();
let (bnd, arg) = if self.skip_starts_with("=") {
if let Term::Var { nam } = &mut arg {
self.consume("=")?;
let term = self.parse_term()?;
(Some(std::mem::take(nam)), term)
} else {
return self.expected_spanned("var", arg_ini_idx, arg_end_idx);
}
} else {
(None, arg)
};
let with = if self.try_consume("with") {
let mut with = vec![self.parse_hvml_name()?];
while !self.skip_starts_with("{") {
self.try_consume(",");
with.push(self.parse_hvml_name()?);
}
with
} else {
vec![]
};
// TODO: we could enforce correct switches at the parser level to get a spanned error.
let rules = self.list_like(|p| p.parse_switch_arm(), "{", "}", ";", false, 1)?;
if let Some(bnd) = bnd {
Term::Let {
nam: Some(bnd.clone()),
val: Box::new(arg),
nxt: Box::new(Term::Swt { arg: Box::new(Term::Var { nam: bnd }), with, rules }),
}
} else {
Term::Swt { arg: Box::new(arg), with, rules }
}
} else {
// var
let nam = self.labelled(|p| p.parse_hvml_name(), "term")?;
Term::Var { nam }
}
}
};
Ok(term)
})
}
fn parse_oper(&mut self) -> Result<Op, String> {
let opr = if self.try_consume("+") {
Op { ty: OpType::U60, op: IntOp::Add }
} else if self.try_consume("-") {
Op { ty: OpType::U60, op: IntOp::Sub }
} else if self.try_consume("*") {
Op { ty: OpType::U60, op: IntOp::Mul }
} else if self.try_consume("/") {
Op { ty: OpType::U60, op: IntOp::Div }
} else if self.try_consume("%") {
Op { ty: OpType::U60, op: IntOp::Rem }
} else if self.try_consume("<<") {
Op { ty: OpType::U60, op: IntOp::Shl }
} else if self.try_consume(">>") {
Op { ty: OpType::U60, op: IntOp::Shr }
} else if self.try_consume("<=") {
Op { ty: OpType::U60, op: IntOp::Le }
} else if self.try_consume(">=") {
Op { ty: OpType::U60, op: IntOp::Ge }
} else if self.try_consume("<") {
Op { ty: OpType::U60, op: IntOp::Lt }
} else if self.try_consume(">") {
Op { ty: OpType::U60, op: IntOp::Gt }
} else if self.try_consume("==") {
Op { ty: OpType::U60, op: IntOp::Eq }
} else if self.try_consume("!=") {
Op { ty: OpType::U60, op: IntOp::Ne }
} else if self.try_consume("&") {
Op { ty: OpType::U60, op: IntOp::And }
} else if self.try_consume("|") {
Op { ty: OpType::U60, op: IntOp::Or }
} else if self.try_consume("^") {
Op { ty: OpType::U60, op: IntOp::Xor }
} else {
return self.expected("numeric operator");
};
Ok(opr)
}
fn parse_lambda(&mut self, tag: Tag) -> Result<Term, String> {
self.advance_one().unwrap();
let term = if self.try_consume("$") {
// unscoped lambda
let nam = self.parse_hvml_name()?;
let bod = self.parse_term()?;
Term::Chn { tag, nam: Some(nam), bod: Box::new(bod) }
} else {
// normal lambda
let nam = self.parse_name_or_era()?;
let bod = self.parse_term()?;
Term::Lam { tag, nam, bod: Box::new(bod) }
};
Ok(term)
}
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| {
if p.try_consume("*") {
Ok(None)
} else {
let nam = p.parse_hvml_name()?;
Ok(Some(nam))
}
},
"name or '*'",
)
}
fn parse_tag(&mut self) -> Result<Tag, String> {
self.consume("#")?;
let nam = self.labelled(|p| p.parse_hvml_name(), "tag name")?;
Ok(Tag::Named(nam))
}
fn parse_match_arm(&mut self) -> Result<MatchRule, String> {
self.try_consume("|");
let nam = self.parse_name_or_era()?;
self.consume(":")?;
let bod = self.parse_term()?;
Ok((nam, vec![], bod))
}
fn parse_switch_arm(&mut self) -> Result<SwitchRule, String> {
self.try_consume("|");
let Some(head) = self.skip_peek_one() else { return self.expected("switch pattern") };
let ctr = match head {
'_' => {
self.consume("_")?;
NumCtr::Succ(None)
}
c if c.is_numeric() => {
let val = self.parse_u64()?;
NumCtr::Num(val)
}
_ => return self.expected("switch pattern"),
};
self.consume(":")?;
let bod = self.parse_term()?;
Ok((ctr, bod))
}
/* 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),
}
}
/* Overrides */
/// Generates an error message for parsing failures, including the highlighted context.
///
/// Override to have our own error message.
fn expected<T>(&mut self, exp: &str) -> Result<T, String> {
let ini_idx = *self.index();
let end_idx = *self.index() + 1;
self.expected_spanned(exp, ini_idx, end_idx)
}
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 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())
}
}
/// 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
}
}
/// Parses a name from the input, supporting alphanumeric characters, underscores, periods, and hyphens.
///
/// Override to call our own `expected`.
fn parse_name(&mut self) -> Result<String, String> {
self.skip_trivia();
let name = self.take_while(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.' || c == '-' || c == '/');
if name.is_empty() { self.expected("name") } else { Ok(name.to_owned()) }
}
// TODO: Override because the lib has a bug where it will error on '_' .
/// Parses a u64 from the input, supporting dec, hex (0xNUM), and bin (0bNUM).
fn parse_u64(&mut self) -> Result<u64, String> {
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::<String>();
if num_str.is_empty() {
self.expected("numeric digit")
} else {
u64::from_str_radix(&num_str, radix).map_err(|e| e.to_string())
}
}
// TODO: Override to accept more escape sequences
/// Parses a single unicode character, supporting scape sequences.
fn parse_char(&mut self) -> Result<char, String> {
match self.advance_one() {
Some('\\') => match self.advance_one() {
Some('u' | 'U') => {
self.consume("{")?;
let codepoint_str = self.take_while(|c| c.is_ascii_hexdigit());
self.consume("}")?;
u32::from_str_radix(codepoint_str, 16)
.ok()
.and_then(std::char::from_u32)
.ok_or_else(|| self.expected::<char>("unicode-codepoint").unwrap_err())
}
Some('n') => Ok('\n'),
Some('r') => Ok('\r'),
Some('t') => Ok('\t'),
Some('\'') => Ok('\''),
Some('\"') => Ok('\"'),
Some('\\') => Ok('\\'),
Some('0') => Ok('\0'),
Some(chr) => self.expected(&format!("\\{}", chr)),
None => self.expected("escaped-char"),
},
Some(other) => Ok(other),
None => self.expected("char"),
}
}
// TODO: Override to accept more escape sequences
/// Parses a quoted character, like 'x'.
fn parse_quoted_char(&mut self) -> Result<char, String> {
self.skip_trivia();
self.consume("'")?;
let chr = self.parse_char()?;
self.consume("'")?;
Ok(chr)
}
// TODO: Override to accept more escape sequences
/// Parses a quoted string, like "foobar".
fn parse_quoted_string(&mut self) -> Result<String, String> {
self.skip_trivia();
self.consume("\"")?;
let mut result = String::new();
while let Some(chr) = self.peek_one() {
if chr == '"' {
break;
} else {
result.push(self.parse_char()?);
}
}
self.consume("\"")?;
Ok(result)
}
// TODO: override to avoid looping on ending in comment without \n
/// Consumes the next character in the text.
fn skip_trivia(&mut self) {
while let Some(c) = self.peek_one() {
if c.is_ascii_whitespace() {
self.advance_one();
continue;
}
if c == '/' && self.input().get(*self.index() ..).unwrap_or_default().starts_with("//") {
loop {
if let Some(c) = self.peek_one() {
if c != '\n' {
self.advance_one();
} else {
break;
}
} else {
break;
}
}
self.advance_one(); // Skip the newline character as well
continue;
}
break;
}
}
}
impl Book {
fn add_adt(&mut self, nam: Name, adt: Adt) -> Result<(), String> {
if let Some(adt) = self.adts.get(&nam) {
if adt.builtin {
return Err(format!("{} is a built-in datatype and should not be overridden.", nam));
} else {
return Err(format!("Repeated datatype '{}'", nam));
}
} else {
for ctr in adt.ctrs.keys() {
match self.ctrs.entry(ctr.clone()) {
indexmap::map::Entry::Vacant(e) => _ = e.insert(nam.clone()),
indexmap::map::Entry::Occupied(e) => {
if self.adts.get(e.get()).is_some_and(|adt| adt.builtin) {
return Err(format!("{} is a built-in constructor and should not be overridden.", e.key()));
} else {
return Err(format!("Repeated constructor '{}'", e.key()));
}
}
}
}
self.adts.insert(nam.clone(), adt);
}
Ok(())
}
fn add_rule(&mut self, name: Name, rule: Rule, builtin: bool) {
if let Some(def) = self.defs.get_mut(&name) {
def.rules.push(rule);
} else {
self.defs.insert(name.clone(), Definition { name, rules: vec![rule], builtin });
}
}
}
fn add_ctx(msg: &str, ini_idx: usize, end_idx: usize, file: &str) -> String {
let ctx = highlight_error(ini_idx, end_idx, file);
format!("{msg}\n{ctx}")
}

View File

@ -1,315 +0,0 @@
use interner::global::{GlobalPool, GlobalString};
use logos::{FilterResult, Lexer, Logos};
use std::{fmt, num::ParseIntError};
pub static STRINGS: GlobalPool<String> = GlobalPool::new();
#[derive(Logos, Debug, PartialEq, Clone)]
#[logos(error=LexingError)]
#[logos(skip r"[ \t\r\n\f]+")]
pub enum Token {
#[regex("[_.a-zA-Z][_.a-zA-Z0-9-]*", |lex| lex.slice().parse().ok().map(|s: String| STRINGS.get(s)))]
Name(GlobalString),
#[regex("@|λ")]
Lambda,
#[token("$")]
Dollar,
#[token("let")]
Let,
#[token("use")]
Use,
#[token("match")]
Match,
#[token("switch")]
Switch,
#[token("=")]
Equals,
#[regex("0[bB][0-9a-zA-Z_]+", |lex| from_radix(2, lex))]
#[regex("0[xX][0-9a-zA-Z_]+", |lex| from_radix(16, lex))]
#[regex("[0-9][0-9a-zA-Z_]*", |lex| from_radix(10, lex))]
Num(u64),
#[regex(r#""([^"\\]|\\[0tunr'"\\])*""#, |lex| normalized_string(lex).ok())]
#[regex(r#"`([^`\\]|\\[0tunr`'"\\])*`"#, |lex| normalized_string(lex).ok())]
Str(GlobalString),
#[regex(r#"'\\U[0-9a-fA-F]{1,8}'"#, normalized_char, priority = 2)]
// Since '.' is just covering any ascii char, we need to make the
// regex match any possible character of the unicode general category
#[regex(
r#"'(\p{L}|\p{M}|\p{N}|\p{P}|\p{S}|\p{Z}|\p{C}|\p{Emoji}|\\u[0-9a-fA-F]{1,4}|\\[0tunr`'"\\])'"#,
normalized_char
)]
Char(u64),
#[token("#")]
Hash,
#[token("+")]
Add,
#[token("-")]
Sub,
#[token("*")]
Asterisk,
#[token("/")]
Div,
#[token("%")]
Mod,
#[token("~")]
Tilde,
#[token("&")]
And,
#[token("|")]
Or,
#[token("^")]
Xor,
#[token("<<")]
Shl,
#[token(">>")]
Shr,
#[token("<")]
Ltn,
#[token(">")]
Gtn,
#[token("<=")]
Lte,
#[token(">=")]
Gte,
#[token("==")]
EqualsEquals,
#[token("!=")]
NotEquals,
#[token(";")]
Semicolon,
#[token(":")]
Colon,
#[token(",")]
Comma,
#[token("(")]
LParen,
#[token(")")]
RParen,
#[token("{")]
LBracket,
#[token("}")]
RBracket,
#[token("[")]
LBrace,
#[token("]")]
RBrace,
#[regex("//.*", logos::skip)]
SingleLineComment,
#[token("/*", comment)]
MultiLineComment,
Error(LexingError),
}
fn from_radix(radix: u32, lexer: &mut Lexer<Token>) -> Result<u64, ParseIntError> {
let slice = if radix == 10 { lexer.slice() } else { &lexer.slice()[2 ..] };
let slice = &slice.replace('_', "");
u64::from_str_radix(slice, radix)
}
#[derive(Default, Debug, PartialEq, Clone)]
pub enum LexingError {
UnclosedComment,
#[default]
InvalidCharacter,
InvalidNumberLiteral,
}
impl From<ParseIntError> for LexingError {
fn from(_: ParseIntError) -> Self {
LexingError::InvalidNumberLiteral
}
}
// Lexer for nested multi-line comments
#[derive(Logos)]
pub enum MultiLineComment {
#[token("/*")]
Open,
#[token("*/")]
Close,
#[regex("(?s).")]
Other,
}
fn comment(lexer: &mut Lexer<'_, Token>) -> FilterResult<(), LexingError> {
let start = lexer.remainder();
let mut comment = MultiLineComment::lexer(start);
let mut depth = 1; // Already matched an Open token, so count it
loop {
if let Some(token) = comment.next() {
match token {
Ok(MultiLineComment::Open) => depth += 1,
Ok(MultiLineComment::Close) => depth -= 1,
Ok(MultiLineComment::Other) => {}
Err(()) => unreachable!(),
}
} else {
// Unclosed comment
return FilterResult::Error(LexingError::UnclosedComment);
}
if depth <= 0 {
break;
}
}
let end = comment.remainder();
let span = (end as *const str as *const () as usize) - (start as *const str as *const () as usize);
lexer.bump(span);
FilterResult::Skip
}
fn normalized_string(lexer: &mut Lexer<Token>) -> Result<GlobalString, ParseIntError> {
let slice = lexer.slice();
let slice = &slice[1 .. slice.len() - 1];
let mut s = String::new();
let chars = &mut slice.chars();
while let Some(char) = chars.next() {
match char {
'\\' => match chars.next() {
Some('\\') => s.push('\\'),
Some('`') => s.push('`'),
Some('\'') => s.push('\''),
Some('\"') => s.push('\"'),
Some('n') => s.push('\n'),
Some('r') => s.push('\r'),
Some('t') => s.push('\t'),
Some('u') | Some('U') => {
let hex = chars.take(8).collect::<String>();
let hex_val = u32::from_str_radix(&hex, 16)?;
let char = char::from_u32(hex_val).unwrap_or(char::REPLACEMENT_CHARACTER);
s.push(char);
}
Some('0') => s.push('\0'),
Some(other) => {
s.push('\\');
s.push(other);
}
None => s.push('\\'),
},
other => s.push(other),
}
}
Ok(STRINGS.get(s))
}
fn normalized_char(lexer: &mut Lexer<Token>) -> Option<u64> {
let slice = lexer.slice();
let slice = &slice[1 .. slice.len() - 1];
let chars = &mut slice.chars();
let c = match chars.next()? {
'\\' => match chars.next() {
Some('\\') => '\\',
Some('`') => '`',
Some('\"') => '\"',
Some('\'') => '\'',
Some('n') => '\n',
Some('r') => '\r',
Some('t') => '\t',
Some('u') | Some('U') => {
let hex = chars.take(8).collect::<String>();
let hex_val = u32::from_str_radix(&hex, 16).unwrap();
char::from_u32(hex_val).unwrap_or(char::REPLACEMENT_CHARACTER)
}
Some('0') => '\0',
Some(..) => return None,
None => '\\',
},
other => other,
};
Some(u64::from(c))
}
impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Name(s) => write!(f, "{}", s),
Self::Lambda => write!(f, "λ"),
Self::Dollar => write!(f, "$"),
Self::Let => write!(f, "let"),
Self::Use => write!(f, "use"),
Self::Match => write!(f, "match"),
Self::Switch => write!(f, "switch"),
Self::Equals => write!(f, "="),
Self::Num(num) => write!(f, "{num}"),
Self::Str(s) => write!(f, "\"{s}\""),
Self::Char(c) => write!(f, "'{c}'"),
Self::Hash => write!(f, "#"),
Self::Add => write!(f, "+"),
Self::Sub => write!(f, "-"),
Self::Asterisk => write!(f, "*"),
Self::Div => write!(f, "/"),
Self::Mod => write!(f, "%"),
Self::Tilde => write!(f, "~"),
Self::And => write!(f, "&"),
Self::Or => write!(f, "|"),
Self::Xor => write!(f, "^"),
Self::Shl => write!(f, "<<"),
Self::Shr => write!(f, ">>"),
Self::Ltn => write!(f, "<"),
Self::Gtn => write!(f, ">"),
Self::Lte => write!(f, "<="),
Self::Gte => write!(f, ">="),
Self::NotEquals => write!(f, "!="),
Self::EqualsEquals => write!(f, "=="),
Self::Colon => write!(f, ":"),
Self::Comma => write!(f, ","),
Self::Semicolon => write!(f, ";"),
Self::LParen => write!(f, "("),
Self::RParen => write!(f, ")"),
Self::LBracket => write!(f, "{{"),
Self::RBracket => write!(f, "}}"),
Self::LBrace => write!(f, "["),
Self::RBrace => write!(f, "]"),
Self::SingleLineComment => write!(f, "<SingleLineComment>"),
Self::MultiLineComment => write!(f, "<MultiLineComment>"),
Self::Error(LexingError::InvalidNumberLiteral) => write!(f, "<InvalidNumberLiteral>"),
Self::Error(LexingError::InvalidCharacter) => write!(f, "<InvalidCharacter>"),
Self::Error(LexingError::UnclosedComment) => write!(f, "<UnclosedComment>"),
}
}
}

View File

@ -1,5 +0,0 @@
pub mod lexer;
#[allow(clippy::module_inception)]
pub mod parser;
pub use parser::{parse_book, parse_term};

View File

@ -1,612 +0,0 @@
use crate::term::{
parser::lexer::{LexingError, Token},
Adt, Book, Definition, IntOp, Name, NumCtr, Op, OpType, Pattern, Rule, Tag, Term, LNIL, SNIL,
};
use chumsky::{
error::{Error, RichReason},
extra,
input::{Emitter, SpannedInput, Stream, ValueInput},
prelude::{Input, Rich},
primitive::{any, choice, just},
recursive::recursive,
select,
span::SimpleSpan,
util::MaybeRef,
IterParser, Parser,
};
use indexmap::map::Entry;
use logos::{Logos, SpannedIter};
use std::{iter::Map, ops::Range, path::Path};
use super::lexer::STRINGS;
// hvml grammar description:
// <Book> ::= <TopLevel>*
// <TopLevel> ::= (<Def> | <Data>)
// <Def> ::= <Rule> (<Rule>)*
// <Data> ::= "data" <Name> "=" (<Name> | "(" <Name> (<Name>)* ")")+
// <Rule> ::= ("(" <Name> <Pattern>* ")" | <Name> <Pattern>*) "=" (<InlineNumOp> | <InlineApp>)
// <Pattern> ::= "(" <Name> <Pattern>* ")" | <NameEra> | <Number>
// <Term> ::= <Var> | <GlobalVar> | <Number> | <Lam> | <GlobalLam> | <Dup> | <Tup> | <Let> | <Match> | <NumOp> | <App>
// <Lam> ::= ("λ"|"@") <NameEra> <Term>
// <GlobalLam> ::= ("λ"|"@") "$" <Name> <Term>
// <Dup> ::= "dup" <Tag>? <NameEra> <NameEra> "=" <Term> ";" <Term>
// <Tup> ::= "(" <Term> "," <Term> ")"
// <Let> ::= "let" <LetPat> "=" <Term> ";" <Term>
// <LetPat> ::= <Name> | "(" <NameEra> "," <NameEra> ")"
// <Match> ::= "match" (<Term> | <Name> "=" <Term>) "{" <match_arm>+ "}"
// <match_arm> ::= "|"? <Pattern> ":" <Term> ";"?
// <NumOp> ::= "(" <numop_token> <Term> <Term> ")"
// <App> ::= "(" <Term> (<Term>)* ")"
// <Var> ::= <Name>
// <GlobalVar> ::= "$" <Name>
// <NameEra> ::= <Name> | "*"
// <Name> ::= <name_token> // [_a-zA-Z][_a-zA-Z0-9]{0..7}
// <Number> ::= <number_token> // [0-9]+
// <Tag> ::= "#" <Name>
pub fn parse_book(
code: &str,
default_book: impl Fn() -> Book,
builtin: bool,
) -> Result<Book, Vec<Rich<Token>>> {
book(default_book, builtin).parse(token_stream(code)).into_result()
}
pub fn parse_term(code: &str) -> Result<Term, Vec<Rich<Token>>> {
// TODO: Make a function that calls a parser. I couldn't figure out how to type it correctly.
term().parse(token_stream(code)).into_result()
}
/// Converts a Chumsky parser error into a message.
pub fn error_to_msg(err: &Rich<'_, Token>, code: &str, path: &Path) -> String {
let Range { start, end } = err.span().into_range();
let (lin, col) = line_and_col_of_byte(start, code);
let reason = match err.reason() {
// When many reasons, the first one is the most relevant.
// Otherwise we just get 'multiple errors'.
RichReason::Many(errs) => &errs[0],
_ => err.reason(),
};
let path = format!("{}:{lin}:{col}", path.display());
format!("At {}: {}\n{}", path, reason, highlight_error::highlight_error(usize::min(start, end), end, code))
}
fn line_and_col_of_byte(until: usize, src: &str) -> (usize, usize) {
// Line and column numbers starts at 1.
let mut line = 1;
let mut col = 1;
let mut gone = 0;
for char in src.chars() {
if gone >= until {
break;
}
let char_len = char.len_utf8();
gone += char_len;
if char == '\n' {
line += 1;
col = 1;
} else {
col += char_len;
}
}
(line, col)
}
fn token_stream(
code: &str,
) -> SpannedInput<
Token,
SimpleSpan,
Stream<
Map<SpannedIter<Token>, impl FnMut((Result<Token, LexingError>, Range<usize>)) -> (Token, SimpleSpan)>,
>,
> {
// TODO: Maybe change to just using chumsky.
let token_iter = Token::lexer(code).spanned().map(|(token, span)| match token {
Ok(t) => (t, SimpleSpan::from(span)),
Err(e) => (Token::Error(e), SimpleSpan::from(span)),
});
Stream::from_iter(token_iter).spanned(SimpleSpan::from(code.len() .. code.len()))
}
// Parsers
fn soft_keyword<'a, I>(keyword: &'static str) -> impl Parser<'a, I, (), extra::Err<Rich<'a, Token>>>
where
I: ValueInput<'a, Token = Token, Span = SimpleSpan>,
{
any().filter(move |t| matches!(t, Token::Name(n) if n == keyword)).to(()).labelled(keyword)
}
fn name<'a, I>() -> impl Parser<'a, I, Name, extra::Err<Rich<'a, Token>>>
where
I: ValueInput<'a, Token = Token, Span = SimpleSpan>,
{
// FIXME: bug with chumsky when using with `.repeated`
// select!(Token::Name(name) => Name::from(name)).labelled("<Name>")
any()
.filter(|t| matches!(t, Token::Name(_)))
.map(|t| {
let Token::Name(n) = t else { unreachable!() };
Name(n)
})
.labelled("<Name>")
}
/// A top level name that not accepts `-`.
fn tl_name<'a, I>() -> impl Parser<'a, I, Name, extra::Err<Rich<'a, Token>>>
where
I: ValueInput<'a, Token = Token, Span = SimpleSpan>,
{
any()
.filter(|t| matches!(t, Token::Name(n) if n != "data"))
.map(|t| {
let Token::Name(name) = t else { unreachable!() };
name
})
.validate(|out, span, emitter| {
if out.contains('-') {
emitter.emit(Rich::custom(span, "Names with '-' are not supported at top level."));
}
Name(out)
})
.labelled("<Name>")
}
fn tag<'a, I>(default: Tag) -> impl Parser<'a, I, Tag, extra::Err<Rich<'a, Token>>>
where
I: ValueInput<'a, Token = Token, Span = SimpleSpan>,
{
just(Token::Hash).ignore_then(name()).or_not().map(move |x| x.map_or_else(|| default.clone(), Tag::Named))
}
fn name_or_era<'a, I>() -> impl Parser<'a, I, Option<Name>, extra::Err<Rich<'a, Token>>>
where
I: ValueInput<'a, Token = Token, Span = SimpleSpan>,
{
choice((any().filter(|a| matches!(a, Token::Asterisk)).to(None), name().map(Some)))
}
fn num_oper<'a, I>() -> impl Parser<'a, I, Op, extra::Err<Rich<'a, Token>>>
where
I: ValueInput<'a, Token = Token, Span = SimpleSpan>,
{
select! {
Token::Add => Op{ ty: OpType::U60, op: IntOp::Add },
Token::Sub => Op{ ty: OpType::U60, op: IntOp::Sub },
Token::Asterisk => Op{ ty: OpType::U60, op: IntOp::Mul },
Token::Div => Op{ ty: OpType::U60, op: IntOp::Div },
Token::Mod => Op{ ty: OpType::U60, op: IntOp::Rem },
Token::EqualsEquals => Op{ ty: OpType::U60, op: IntOp::Eq },
Token::NotEquals => Op{ ty: OpType::U60, op: IntOp::Ne },
Token::Ltn => Op{ ty: OpType::U60, op: IntOp::Lt },
Token::Gtn => Op{ ty: OpType::U60, op: IntOp::Gt },
Token::Lte => Op{ ty: OpType::U60, op: IntOp::Le },
Token::Gte => Op{ ty: OpType::U60, op: IntOp::Ge },
Token::And => Op{ ty: OpType::U60, op: IntOp::And },
Token::Or => Op{ ty: OpType::U60, op: IntOp::Or },
Token::Xor => Op{ ty: OpType::U60, op: IntOp::Xor },
Token::Shl => Op{ ty: OpType::U60, op: IntOp::Shl },
Token::Shr => Op{ ty: OpType::U60, op: IntOp::Shr },
}
}
fn term<'a, I>() -> impl Parser<'a, I, Term, extra::Err<Rich<'a, Token>>>
where
I: ValueInput<'a, Token = Token, Span = SimpleSpan>,
{
let var = name().map(|name| Term::Var { nam: name }).boxed();
let unscoped_var = just(Token::Dollar).ignore_then(name()).map(|name| Term::Lnk { nam: name }).boxed();
let number = select!(Token::Num(num) => num).or(
select!(Token::Error(LexingError::InvalidNumberLiteral) => ()).validate(|_, span, emit| {
emit.emit(Rich::custom(span, "found invalid number literal expected number"));
0
}),
);
let nat: chumsky::Boxed<I, Term, extra::Err<Rich<_>>> = just(Token::Hash)
.ignore_then(select!(Token::Num(num) => Term::Nat { val: num }).or(
select!(Token::Error(LexingError::InvalidNumberLiteral) => ()).validate(|_, span, emit| {
emit.emit(Rich::custom(span, "found invalid nat literal expected number"));
Term::Nat { val: 0 }
}),
))
.boxed();
let num_term = number.map(|val| Term::Num { val });
let term_sep = just(Token::Semicolon).or_not();
let list_sep = just(Token::Comma).or_not();
recursive(|term| {
// *
let era = just(Token::Asterisk).to(Term::Era).boxed();
// #tag? λx body
let lam = tag(Tag::Static)
.then_ignore(just(Token::Lambda))
.then(name_or_era())
.then(term.clone())
.map(|((tag, nam), bod)| Term::Lam { tag, nam, bod: Box::new(bod) })
.boxed();
// #tag? λ$x body
let unscoped_lam = tag(Tag::Static)
.then_ignore(just(Token::Lambda))
.then(just(Token::Dollar).ignore_then(name_or_era()))
.then(term.clone())
.map(|((tag, nam), bod)| Term::Chn { tag, nam, bod: Box::new(bod) })
.boxed();
// #tag {fst snd}
let sup = tag(Tag::Auto)
.then_ignore(just(Token::LBracket))
.then(term.clone().separated_by(list_sep.clone()).at_least(2).allow_trailing().collect())
.then_ignore(just(Token::RBracket))
.map(|(tag, els)| Term::Sup { tag, els })
.boxed();
// let #tag? {x1 x2} = body; next
let dup = just(Token::Let)
.ignore_then(tag(Tag::Auto))
.then_ignore(just(Token::LBracket))
.then(name_or_era().separated_by(list_sep.clone()).at_least(2).allow_trailing().collect())
.then_ignore(just(Token::RBracket))
.then_ignore(just(Token::Equals))
.then(term.clone())
.then_ignore(term_sep.clone())
.then(term.clone())
.map(|(((tag, bnd), val), next)| Term::Dup { tag, bnd, val: Box::new(val), nxt: Box::new(next) })
.boxed();
// let nam = term; term
let let_ = just(Token::Let)
.ignore_then(name_or_era())
.then_ignore(just(Token::Equals))
.then(term.clone())
.then_ignore(term_sep.clone())
.then(term.clone())
.map(|((nam, val), nxt)| Term::Let { nam, val: Box::new(val), nxt: Box::new(nxt) })
.boxed();
// use a = val ';'? nxt
let use_ = just(Token::Use)
.ignore_then(name())
.then_ignore(just(Token::Equals))
.then(term.clone())
.then_ignore(term_sep.clone())
.then(term.clone())
.map(|((nam, val), nxt)| Term::Use { nam: Some(nam), val: Box::new(val), nxt: Box::new(nxt) })
.boxed();
// (name '=')? term
let match_arg = name().then_ignore(just(Token::Equals)).or_not().then(term.clone()).boxed();
let with = soft_keyword("with")
.ignore_then(name().separated_by(list_sep.clone()).at_least(1).allow_trailing().collect())
.boxed();
let lnil = just(Token::LBrace)
.ignore_then(just(Token::RBrace))
.ignored()
.map(|_| Some(Name::from(LNIL)))
.labelled("List.nil");
let snil =
select!(Token::Str(s) if s.is_empty() => ()).map(|_| Some(Name::from(SNIL))).labelled("String.nil");
let match_pat = choice((name_or_era(), lnil, snil));
// '|'? name: term
let match_rule = just(Token::Or)
.or_not()
.ignore_then(match_pat)
.labelled("<Match pattern>")
.then_ignore(just(Token::Colon))
.then(term.clone())
.map(|(nam, body)| (nam, vec![], body));
let match_rules = match_rule.separated_by(term_sep.clone()).at_least(1).allow_trailing().collect();
// match ((scrutinee | <name> = value),?)+ { pat+: term;... }
let match_ = just(Token::Match)
.ignore_then(match_arg.clone())
.then(with.clone().or_not())
.then_ignore(just(Token::LBracket))
.then(match_rules)
.then_ignore(just(Token::RBracket))
.map(|(((bind, arg), with), rules)| {
let with = with.unwrap_or_default();
if let Some(bind) = bind {
Term::Let {
nam: Some(bind.clone()),
val: Box::new(arg),
nxt: Box::new(Term::Mat { arg: Box::new(Term::Var { nam: bind }), with, rules }),
}
} else {
Term::Mat { arg: Box::new(arg), with, rules }
}
})
.boxed();
let switch_ctr = choice((number.map(NumCtr::Num), soft_keyword("_").map(|_| NumCtr::Succ(None))))
.labelled("<Switch pattern>");
let switch_rule =
just(Token::Or).or_not().ignore_then(switch_ctr).then_ignore(just(Token::Colon)).then(term.clone());
let switch_rules = switch_rule.separated_by(term_sep.clone()).at_least(1).allow_trailing().collect();
let switch = just(Token::Switch)
.ignore_then(match_arg)
.then(with.clone().or_not())
.then_ignore(just(Token::LBracket))
.then(switch_rules)
.then_ignore(just(Token::RBracket))
.map(|(((bind, arg), with), rules)| {
let with = with.unwrap_or_default();
if let Some(bind) = bind {
Term::Let {
nam: Some(bind.clone()),
val: Box::new(arg),
nxt: Box::new(Term::Swt { arg: Box::new(Term::Var { nam: bind }), with, rules }),
}
} else {
Term::Swt { arg: Box::new(arg), with, rules }
}
})
.boxed();
// #tag? (f arg1 arg2 ...)
let app = tag(Tag::Static)
.then_ignore(just(Token::LParen))
.then(term.clone())
.foldl(term.clone().repeated(), |(tag, fun), arg| {
(tag.clone(), Term::App { tag, fun: Box::new(fun), arg: Box::new(arg) })
})
.then_ignore(just(Token::RParen))
.map(|(_, app)| app)
.boxed();
let num_op = num_oper()
.then(term.clone())
.then(term.clone())
.delimited_by(just(Token::LParen), just(Token::RParen))
.map(|((opr, fst), snd)| Term::Opx { opr, fst: Box::new(fst), snd: Box::new(snd) })
.boxed();
// (x, ..n)
let tup = term
.clone()
.separated_by(just(Token::Comma))
.at_least(2)
.collect::<Vec<Term>>()
.delimited_by(just(Token::LParen), just(Token::RParen))
.map(|els| Term::Tup { els })
.boxed();
// let (x, ..n) = term; term
let let_tup = just(Token::Let)
.ignore_then(just(Token::LParen))
.ignore_then(name_or_era().separated_by(just(Token::Comma)).at_least(2).collect())
.then_ignore(just(Token::RParen))
.then_ignore(just(Token::Equals))
.then(term.clone())
.then_ignore(term_sep.clone())
.then(term.clone())
.map(|((bnd, val), next)| Term::Ltp { bnd, val: Box::new(val), nxt: Box::new(next) })
.boxed();
let str = select!(Token::Str(s) => Term::Str { val: s }).boxed();
let chr = select!(Token::Char(c) => Term::Num { val: c }).boxed();
let list = term
.clone()
.separated_by(just(Token::Comma).or_not())
.collect()
.delimited_by(just(Token::LBrace), just(Token::RBrace))
.map(|els| Term::Lst { els })
.boxed();
choice((
num_op,
app,
tup,
unscoped_var,
var,
nat,
num_term,
list,
str,
chr,
sup,
unscoped_lam,
lam,
dup,
use_,
let_tup,
let_,
match_,
switch,
era,
))
.labelled("term")
})
}
fn rule_pattern<'a, I>() -> impl Parser<'a, I, Pattern, extra::Err<Rich<'a, Token>>>
where
I: ValueInput<'a, Token = Token, Span = SimpleSpan>,
{
recursive(|pattern| {
let var = name_or_era().map(Pattern::Var).boxed();
let ctr = tl_name()
.then(pattern.clone().repeated().collect())
.map(|(nam, xs)| Pattern::Ctr(nam, xs))
.delimited_by(just(Token::LParen), just(Token::RParen))
.boxed();
let tup = pattern
.clone()
.separated_by(just(Token::Comma))
.at_least(2)
.collect::<Vec<Pattern>>()
.delimited_by(just(Token::LParen), just(Token::RParen))
.map(Pattern::Tup)
.boxed();
let list = pattern
.clone()
.separated_by(just(Token::Comma).or_not())
.collect()
.delimited_by(just(Token::LBrace), just(Token::RBrace))
.map(Pattern::Lst)
.boxed();
let num_val = any().filter(|t| matches!(t, Token::Num(_))).map(|t| {
let Token::Num(n) = t else { unreachable!() };
n
});
let num = num_val.map(Pattern::Num).labelled("<Num>");
let chr = select!(Token::Char(c) => Pattern::Num(c)).labelled("<Char>").boxed();
let str = select!(Token::Str(s) => Pattern::Str(s)).labelled("<String>").boxed();
choice((num, chr, str, var, ctr, list, tup))
})
.labelled("<Rule pattern>")
}
fn rule_lhs<'a, I>() -> impl Parser<'a, I, (Name, Vec<Pattern>), extra::Err<Rich<'a, Token>>>
where
I: ValueInput<'a, Token = Token, Span = SimpleSpan>,
{
let lhs = tl_name().then(rule_pattern().repeated().collect()).boxed();
let just_lhs = lhs.clone().then_ignore(just(Token::Equals).map_err(|err: Rich<'a, Token>| {
Error::<I>::expected_found(
[
Some(MaybeRef::Val(Token::Add)),
Some(MaybeRef::Val(Token::LParen)),
Some(MaybeRef::Val(Token::LBrace)),
Some(MaybeRef::Val(Token::Equals)),
],
None,
*err.span(),
)
}));
let paren_lhs = just(Token::LParen)
.ignore_then(lhs.clone().map_err(|err| map_unexpected_eof(err, Token::Name(STRINGS.get("<Name>")))))
.then_ignore(just(Token::RParen))
.then_ignore(just(Token::Equals).map_err(|err| map_unexpected_eof(err, Token::Equals)));
choice((just_lhs, paren_lhs))
}
fn rule<'a, I>() -> impl Parser<'a, I, TopLevel, extra::Err<Rich<'a, Token>>>
where
I: ValueInput<'a, Token = Token, Span = SimpleSpan>,
{
rule_lhs().then(term()).map(move |((name, pats), body)| TopLevel::Rule((name, Rule { pats, body })))
}
fn datatype<'a, I>() -> impl Parser<'a, I, TopLevel, extra::Err<Rich<'a, Token>>>
where
I: ValueInput<'a, Token = Token, Span = SimpleSpan>,
{
let arity_0 = tl_name().map_with_span(|nam, span| ((nam, vec![]), span));
let arity_n = tl_name()
.then(name().repeated().collect::<Vec<_>>())
.delimited_by(just(Token::LParen), just(Token::RParen))
.map_with_span(|(nam, args), span| ((nam, args), span));
let ctrs = arity_0.or(arity_n).separated_by(just(Token::Or)).at_least(1).collect();
let data_name = tl_name().map_with_span(|name, span| (name, span));
soft_keyword("data")
.ignore_then(data_name.map_err(|err| map_unexpected_eof(err, Token::Name(STRINGS.get("<Name>")))))
.then_ignore(just(Token::Equals))
.then(ctrs.map_err(|err| map_unexpected_eof(err, Token::Name(STRINGS.get("constructor")))))
.map(move |(name, ctrs)| TopLevel::Adt(name, ctrs))
}
fn map_unexpected_eof(err: Rich<Token>, expected_token: Token) -> Rich<Token> {
if err.found().is_none() {
// Not using Error::expected_found here to not merge with other expected_found errors
Rich::custom(*err.span(), format!("found end of input expected {}", expected_token))
} else {
err
}
}
fn book<'a, I>(
default_book: impl Fn() -> Book,
builtin: bool,
) -> impl Parser<'a, I, Book, extra::Err<Rich<'a, Token>>>
where
I: ValueInput<'a, Token = Token, Span = SimpleSpan>,
{
choice((datatype(), rule()))
.repeated()
.collect()
.validate(move |program, _, emit| collect_book(default_book(), program, builtin, emit))
}
/// Collect rules and adts into a book
fn collect_book(
mut book: Book,
program: Vec<TopLevel>,
builtin: bool,
emit: &mut Emitter<Rich<'_, Token>>,
) -> Book {
for top_level in program {
match top_level {
TopLevel::Rule((name, rule)) => {
if let Some(def) = book.defs.get_mut(&name) {
def.rules.push(rule);
} else {
book.defs.insert(name.clone(), Definition { name, rules: vec![rule], builtin });
}
}
TopLevel::Adt((nam, nam_span), adt) => match book.adts.get(&nam) {
None => {
let (ctrs, spans): (Vec<(_, _)>, Vec<_>) = adt.into_iter().unzip();
for ((ctr, _), span) in ctrs.iter().zip(spans.into_iter()) {
match book.ctrs.entry(ctr.clone()) {
Entry::Vacant(e) => _ = e.insert(nam.clone()),
Entry::Occupied(e) => emit.emit(Rich::custom(
span,
if book.adts.get(e.get()).is_some_and(|adt| adt.builtin) {
format!("{} is a built-in constructor and should not be overridden.", e.key())
} else {
format!("Repeated constructor '{}'", e.key())
},
)),
}
}
let adt = Adt { ctrs: ctrs.into_iter().collect(), builtin };
book.adts.insert(nam.clone(), adt);
}
Some(adt) => emit.emit(Rich::custom(
nam_span,
if adt.builtin {
format!("{} is a built-in datatype and should not be overridden.", nam)
} else {
format!("Repeated datatype '{}'", nam)
},
)),
},
}
}
book
}
enum TopLevel {
Rule((Name, Rule)),
Adt((Name, SimpleSpan), Vec<((Name, Vec<Name>), SimpleSpan)>),
}

View File

@ -1,4 +1,5 @@
use crate::{
maybe_grow,
net::{
INet,
NodeKind::{self, *},
@ -68,7 +69,7 @@ impl EncodeTermState<'_> {
/// `global_vars` has the same information for global lambdas. Must be linked outside this function.
/// Expects variables to be affine, refs to be stored as Refs and all names to be bound.
fn encode_term(&mut self, term: &Term, up: Port) -> Option<Port> {
Term::recursive_call(move || {
maybe_grow(|| {
match term {
// A lambda becomes to a con node. Ports:
// - 0: points to where the lambda occurs.

View File

@ -1,4 +1,7 @@
use crate::term::{Book, Term};
use crate::{
maybe_grow,
term::{Book, Term},
};
impl Book {
/// Inline copies of the declared bind in the `use` expression.
@ -22,7 +25,7 @@ impl Book {
impl Term {
pub fn apply_use(&mut self) {
Term::recursive_call(|| {
maybe_grow(|| {
for children in self.children_mut() {
children.apply_use();
}

View File

@ -1,4 +1,7 @@
use crate::term::{Book, Definition, Name, Rule, Term};
use crate::{
maybe_grow,
term::{Book, Definition, Name, Rule, Term},
};
use indexmap::{IndexMap, IndexSet};
use itertools::Itertools;
use std::collections::BTreeMap;
@ -86,7 +89,7 @@ impl Term {
/// Performs reference substitution within a term replacing any references found in
/// `ref_map` with their corresponding targets.
pub fn subst_ref_to_ref(term: &mut Term, ref_map: &BTreeMap<Name, Name>) -> bool {
Term::recursive_call(move || match term {
maybe_grow(|| match term {
Term::Ref { nam: def_name } => {
if let Some(target_name) = ref_map.get(def_name) {
*def_name = target_name.clone();

View File

@ -107,10 +107,10 @@ impl Book {
None => self.insert_used(def_name, used, uses, adt_encoding),
},
Term::Lst { .. } => {
self.insert_ctrs_used(&Name::from(LIST), uses, adt_encoding);
self.insert_ctrs_used(&Name::new(LIST), uses, adt_encoding);
}
Term::Str { .. } => {
self.insert_ctrs_used(&Name::from(STRING), uses, adt_encoding);
self.insert_ctrs_used(&Name::new(STRING), uses, adt_encoding);
}
_ => {}
}

View File

@ -499,8 +499,8 @@ impl Pattern {
}
Pattern::Tup(args) => Type::Tup(args.len()),
Pattern::Num(_) => Type::Num,
Pattern::Lst(..) => Type::Adt(builtins::LIST.into()),
Pattern::Str(..) => Type::Adt(builtins::STRING.into()),
Pattern::Lst(..) => Type::Adt(Name::new(builtins::LIST)),
Pattern::Str(..) => Type::Adt(Name::new(builtins::STRING)),
}
}
}

View File

@ -1,4 +1,7 @@
use crate::term::{AdtEncoding, Book, Constructors, MatchRule, Name, NumCtr, SwitchRule, Tag, Term};
use crate::{
maybe_grow,
term::{AdtEncoding, Book, Constructors, MatchRule, Name, NumCtr, SwitchRule, Tag, Term},
};
impl Book {
/// Encodes pattern matching expressions in the book into their
@ -20,7 +23,7 @@ impl Book {
impl Term {
pub fn encode_matches(&mut self, ctrs: &Constructors, adt_encoding: AdtEncoding) {
Term::recursive_call(move || {
maybe_grow(|| {
for child in self.children_mut() {
child.encode_matches(ctrs, adt_encoding)
}
@ -73,7 +76,7 @@ fn encode_match(arg: Term, rules: Vec<MatchRule>, ctrs: &Constructors, adt_encod
fn encode_switch(arg: Term, mut rules: Vec<SwitchRule>) -> Term {
let last_rule = rules.pop().unwrap();
let match_var = Name::from("%x");
let match_var = Name::new("%x");
// @n-2 (C n-2)
let NumCtr::Succ(last_var) = last_rule.0 else { unreachable!() };

View File

@ -1,5 +1,6 @@
use crate::{
diagnostics::{Diagnostics, ToStringVerbose, WarningType},
maybe_grow,
term::{Adts, Constructors, Ctx, MatchRule, Name, NumCtr, Term},
};
use std::collections::HashMap;
@ -76,7 +77,7 @@ impl Ctx<'_> {
impl Term {
fn fix_match_terms(&mut self, ctrs: &Constructors, adts: &Adts) -> Vec<FixMatchErr> {
Term::recursive_call(move || {
maybe_grow(|| {
let mut errs = Vec::new();
for child in self.children_mut() {
@ -182,7 +183,7 @@ fn extract_match_arg(arg: &mut Term) -> (Name, Option<Term>) {
if let Term::Var { nam } = arg {
(nam.clone(), None)
} else {
let nam = Name::from("%matched");
let nam = Name::new("%matched");
let arg = std::mem::replace(arg, Term::Var { nam: nam.clone() });
(nam, Some(arg))
}

View File

@ -1,7 +1,7 @@
use indexmap::IndexSet;
use crate::{
multi_iterator,
maybe_grow, multi_iterator,
term::{Book, Definition, Name, Rule, Term},
};
use std::collections::BTreeMap;
@ -85,7 +85,7 @@ impl Term {
/// - A safe Lambda, e.g. a nullary constructor or a lambda with safe body.
/// - A Reference with safe body.
pub fn is_safe(&self, book: &Book, seen: &mut IndexSet<Name>) -> bool {
Term::recursive_call(move || match self {
maybe_grow(|| match self {
Term::Num { .. } | Term::Era => true,
Term::Tup { els } | Term::Sup { els, .. } => els.iter().all(|e| Term::is_safe(e, book, seen)),

View File

@ -1,4 +1,7 @@
use crate::term::{Book, Name, NumCtr, Tag, Term};
use crate::{
maybe_grow,
term::{Book, Name, NumCtr, Tag, Term},
};
use std::collections::{BTreeSet, HashSet};
impl Book {
@ -63,7 +66,7 @@ impl Term {
/// }
/// ```
pub fn linearize_match_lambdas(&mut self) {
Term::recursive_call(move || match self {
maybe_grow(|| match self {
Term::Lam { .. } => {
let mut term_owned = std::mem::take(self);
let mut term = &mut term_owned;
@ -139,7 +142,7 @@ impl Term {
}
fn linearize_matches(&mut self) {
Term::recursive_call(move || {
maybe_grow(|| {
for child in self.children_mut() {
child.linearize_matches();
}
@ -151,7 +154,7 @@ impl Term {
}
fn linearize_match_with(&mut self) {
Term::recursive_call(|| {
maybe_grow(|| {
for child in self.children_mut() {
child.linearize_match_with();
}

View File

@ -1,4 +1,7 @@
use crate::term::{Book, Name, Tag, Term};
use crate::{
maybe_grow,
term::{Book, Name, Tag, Term},
};
use std::collections::HashMap;
/// Erases variables that weren't used, dups the ones that were used more than once.
@ -35,7 +38,7 @@ impl Term {
}
fn term_to_affine(term: &mut Term, var_uses: &mut HashMap<Name, u64>) {
Term::recursive_call(move || match term {
maybe_grow(|| match term {
Term::Let { nam: Some(nam), val, nxt } => {
// TODO: This is swapping the order of how the bindings are
// used, since it's not following the usual AST order (first

View File

@ -1,6 +1,7 @@
use crate::{
builtins::CORE_BUILTINS,
diagnostics::{Diagnostics, ToStringVerbose},
maybe_grow,
term::{Ctx, Name, Pattern, Term},
};
use std::collections::{HashMap, HashSet};
@ -45,7 +46,7 @@ impl Term {
main: Option<&Name>,
scope: &mut HashMap<&'a Name, usize>,
) -> Result<(), ReferencedMainErr> {
Term::recursive_call(move || {
maybe_grow(move || {
if let Term::Var { nam } = self
&& is_var_in_scope(nam, scope)
{

View File

@ -2,6 +2,7 @@ use std::collections::VecDeque;
use crate::{
diagnostics::ToStringVerbose,
maybe_grow,
term::{Adt, AdtEncoding, Book, Name, Tag, Term},
};
@ -23,7 +24,7 @@ impl Term {
}
fn resugar_tagged_scott(&mut self, book: &Book, errs: &mut Vec<AdtReadbackError>) {
Term::recursive_call(move || match self {
maybe_grow(|| match self {
Term::Lam { tag: Tag::Named(adt_name), bod, .. } | Term::Chn { tag: Tag::Named(adt_name), bod, .. } => {
if let Some((adt_name, adt)) = book.adts.get_key_value(adt_name) {
self.resugar_ctr_tagged_scott(book, adt, adt_name, errs);
@ -245,7 +246,7 @@ impl Term {
let (arg, bind) = if let Term::Var { nam } = cur {
(nam.clone(), None)
} else {
(Name::from("%matched"), Some(std::mem::take(cur)))
(Name::new("%matched"), Some(std::mem::take(cur)))
};
// Subst the unique readback names for the field names.

View File

@ -1,4 +1,7 @@
use crate::term::{Term, LCONS, LNIL, NAT_SUCC, NAT_ZERO, SCONS, SNIL};
use crate::{
maybe_grow,
term::{Term, LCONS, LNIL, NAT_SUCC, NAT_ZERO, SCONS, SNIL},
};
impl Term {
pub fn resugar_builtins(&mut self) {
@ -8,7 +11,7 @@ impl Term {
}
pub fn resugar_nats(&mut self) {
Term::recursive_call(move || match self {
maybe_grow(|| match self {
// (Nat.succ pred)
Term::App { fun: box Term::Ref { nam: ctr }, arg: box pred, .. } => {
pred.resugar_nats();
@ -35,7 +38,7 @@ impl Term {
/// Rebuilds the String syntax sugar, converting `(Cons 97 Nil)` into `"a"`.
pub fn resugar_strings(&mut self) {
Term::recursive_call(move || match self {
maybe_grow(|| match self {
// (String.cons Num tail)
Term::App {
fun: box Term::App { fun: box Term::Ref { nam: ctr }, arg: box head, .. },
@ -80,7 +83,7 @@ impl Term {
/// Rebuilds the List syntax sugar, converting `(Cons head Nil)` into `[head]`.
pub fn resugar_lists(&mut self) {
Term::recursive_call(move || match self {
maybe_grow(|| match self {
// (List.cons el tail)
Term::App {
fun: box Term::App { fun: box Term::Ref { nam: ctr }, arg: box head, .. },

View File

@ -1,6 +1,9 @@
// Pass to give all variables in a definition unique names.
use crate::term::{Book, Name, Term};
use crate::{
maybe_grow,
term::{Book, Name, Term},
};
use std::collections::HashMap;
impl Book {
@ -31,7 +34,7 @@ pub struct UniqueNameGenerator {
impl UniqueNameGenerator {
// Recursively assign an id to each variable in the term, then convert each id into a unique name.
pub fn unique_names_in_term(&mut self, term: &mut Term) {
Term::recursive_call(move || match term {
maybe_grow(|| match term {
Term::Var { nam } => *nam = self.use_var(nam),
_ => {
for (child, binds) in term.children_mut_with_binds_mut() {

View File

@ -4,8 +4,8 @@ use hvml::{
net::{hvmc_to_net::hvmc_to_net, net_to_hvmc::net_to_hvmc},
run_book,
term::{
load_book::do_parse_book, net_to_term::net_to_term, parser::parse_term, term_to_compat_net,
term_to_net::Labels, AdtEncoding, Book, Ctx, Name, Term,
load_book::do_parse_book, net_to_term::net_to_term, parser::TermParser, term_to_compat_net,
term_to_net::Labels, AdtEncoding, Book, Ctx, Name,
},
CompileOpts, RunOpts,
};
@ -25,14 +25,6 @@ fn format_output(output: std::process::Output) -> String {
format!("{}{}", String::from_utf8_lossy(&output.stderr), String::from_utf8_lossy(&output.stdout))
}
fn do_parse_term(code: &str) -> Result<Term, String> {
parse_term(code).map_err(|errs| errs.into_iter().map(|e| e.to_string()).join("\n"))
}
fn do_parse_net(code: &str) -> Result<hvmc::ast::Net, String> {
hvmc::ast::Net::from_str(code)
}
const TESTS_PATH: &str = "/tests/golden_tests/";
type RunFn = dyn Fn(&str, &Path) -> Result<String, Diagnostics>;
@ -103,7 +95,7 @@ fn run_golden_test_dir_multiple(test_name: &str, run: &[&RunFn]) {
#[test]
fn compile_term() {
run_golden_test_dir(function_name!(), &|code, _| {
let mut term = do_parse_term(code)?;
let mut term = TermParser::new_term(code)?;
let mut vec = Vec::new();
term.check_unbound_vars(&mut HashMap::new(), &mut vec);
@ -226,7 +218,7 @@ fn run_lazy() {
#[test]
fn readback_lnet() {
run_golden_test_dir(function_name!(), &|code, _| {
let net = do_parse_net(code)?;
let net = hvmc::ast::Net::from_str(code)?;
let book = Book::default();
let compat_net = hvmc_to_net(&net);
let mut diags = Diagnostics::default();
@ -349,7 +341,7 @@ fn hangs() {
fn compile_entrypoint() {
run_golden_test_dir(function_name!(), &|code, path| {
let mut book = do_parse_book(code, path)?;
book.entrypoint = Some(Name::from("foo"));
book.entrypoint = Some(Name::new("foo"));
let diagnostics_cfg = DiagnosticsConfig::new(Severity::Error, true);
let res = compile_book(&mut book, CompileOpts::default_strict(), diagnostics_cfg, None)?;
Ok(format!("{}{}", res.diagnostics, res.core_book))
@ -360,7 +352,7 @@ fn compile_entrypoint() {
fn run_entrypoint() {
run_golden_test_dir(function_name!(), &|code, path| {
let mut book = do_parse_book(code, path)?;
book.entrypoint = Some(Name::from("foo"));
book.entrypoint = Some(Name::new("foo"));
let compile_opts = CompileOpts::default_strict().set_all();
let diagnostics_cfg = DiagnosticsConfig::new(Severity::Error, true);
let (res, info) = run_book(book, None, RunOpts::default(), compile_opts, diagnostics_cfg, None)?;

View File

@ -1,5 +0,0 @@
main =
match List.nil {
| []: 0
| *: 1
}

View File

@ -1 +1 @@
main = (String.cons '\u1234' (String.cons '!' (String.cons '7' String.nil)))
main = (String.cons '\u{1234}' (String.cons '!' (String.cons '7' String.nil)))

View File

@ -1,4 +1,4 @@
String.from_list [] = ``
String.from_list [] = ""
String.from_list (List.cons x xs) = (String.cons x (String.from_list xs))
(Concat String.nil ys) = ys
@ -8,5 +8,5 @@ String.from_list (List.cons x xs) = (String.cons x (String.from_list xs))
(Join (List.cons x xs)) = (Concat x (Join xs))
main =
((String.from_list ['\n', '\r', '\t', '\0', '\"', '\'', '\uAFE', '\`'])
, (Join ["\n", "\r", "\t", "\0", "\"", "\'", "\uAFE", `\``]))
((String.from_list ['\n', '\r', '\t', '\0', '\"', '\'', '\u{AFE}', '\\'])
, (Join ["\n", "\r", "\t", "\0", "\"", "\'", "\u{AFE}", "\\"]))

View File

@ -1,5 +1,5 @@
this-is-not-allowed = 1
data Foo-Bar = (Baz-Qux field-hyph)
data Foo-Bar = Baz-Qux
fun-with-hyphen = 1
main = (this-is-not-allowed Baz-Qux)
main = (Baz-Qux fun-with-hyphen)

View File

@ -1,4 +0,0 @@
(Concat String.nil ys) = ys
(Concat (String.cons x xs) ys) = (String.cons x (Concat xs ys))
Main = (Concat "abc369*`" `asdf"asdf`)

View File

@ -1 +1 @@
main = (String.cons '\U1F30E' String.nil)
main = (String.cons '\U{1F30E}' String.nil)

View File

@ -1 +1 @@
main = (String.cons '\u1234' (String.cons '!' (String.cons '7' String.nil)))
main = (String.cons '\u{1234}' (String.cons '!' (String.cons '7' String.nil)))

View File

@ -1,7 +1 @@
/*
main = λx (+ (* x x) (+ (+ 2 x) 3))
FIXME: panicked at 'not yet implemented' on hvmc::run::NetFields `if next.is_op1() { todo!(); } // FIXME`
*/
main = *
main = λx (+ (* x x) (+ (+ 2 x) 3))

View File

@ -1 +1 @@
main = (String.cons '\U1F30E' String.nil)
main = (String.cons '\U{1F30E}' String.nil)

View File

@ -3,5 +3,8 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/error_messages.hvm
---
Errors:
At tests/golden_tests/compile_file/error_messages.hvm:3:10: Repeated constructor 'B'
 3 | data C = (B)
In tests/golden_tests/compile_file/error_messages.hvm :
Repeated constructor 'B'
 3 | data C = (B)
 4 | 
 5 | Foo (C) = *

View File

@ -3,5 +3,6 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/just_a_name.hvm
---
Errors:
At tests/golden_tests/compile_file/just_a_name.hvm:1:1: found end of input expected '+', '(', '[', or '='
 1 | asdf
In tests/golden_tests/compile_file/just_a_name.hvm :
- expected: pattern-matching pattern
- detected: end of input

View File

@ -3,5 +3,6 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/just_data.hvm
---
Errors:
At tests/golden_tests/compile_file/just_data.hvm:1:5: found end of input expected <Name>
 1 | data
In tests/golden_tests/compile_file/just_data.hvm :
- expected: datatype name
- detected: end of input

View File

@ -3,5 +3,6 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/just_paren.hvm
---
Errors:
At tests/golden_tests/compile_file/just_paren.hvm:2:1: found end of input expected <Name>
 2 | (
In tests/golden_tests/compile_file/just_paren.hvm :
- expected: function name
- detected: end of input

View File

@ -3,5 +3,6 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/just_rule_paren.hvm
---
Errors:
At tests/golden_tests/compile_file/just_rule_paren.hvm:1:6: found end of input expected =
 1 | (rule)
In tests/golden_tests/compile_file/just_rule_paren.hvm :
- expected: '='
- detected: end of input

View File

@ -3,5 +3,6 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/missing_adt_eq.hvm
---
Errors:
At tests/golden_tests/compile_file/missing_adt_eq.hvm:1:9: found end of input expected '='
 1 | data Adt
In tests/golden_tests/compile_file/missing_adt_eq.hvm :
- expected: '='
- detected: end of input

View File

@ -3,5 +3,6 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/missing_ctrs.hvm
---
Errors:
At tests/golden_tests/compile_file/missing_ctrs.hvm:1:12: found end of input expected constructor
 1 | data Adt = 
In tests/golden_tests/compile_file/missing_ctrs.hvm :
- expected: datatype constructor name
- detected: end of input

View File

@ -3,5 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/missing_pat.hvm
---
Errors:
At tests/golden_tests/compile_file/missing_pat.hvm:2:3: found ':' expected <Match pattern>
In tests/golden_tests/compile_file/missing_pat.hvm :
- expected: name or '*'
- detected:
 2 | : *

View File

@ -3,5 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file/unexpected_top_char.hvm
---
Errors:
At tests/golden_tests/compile_file/unexpected_top_char.hvm:1:1: found end of input expected data, <Name>, or '('
In tests/golden_tests/compile_file/unexpected_top_char.hvm :
- expected: top-level definition
- detected:
 1 | *

View File

@ -3,5 +3,8 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_file_o_all/adt_string.hvm
---
Errors:
At tests/golden_tests/compile_file_o_all/adt_string.hvm:1:6: String is a built-in datatype and should not be overridden.
 1 | data String = S
In tests/golden_tests/compile_file_o_all/adt_string.hvm :
String is a built-in datatype and should not be overridden.
 1 | data String = S
 2 | 
 3 | main = S

View File

@ -3,5 +3,6 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/compile_term/wrong_nums.hvm
---
Errors:
found invalid number literal expected number
found invalid number literal expected number
- expected: ')'
- detected:
 1 | (+ 0b0123456789 0FA)

View File

@ -1,17 +0,0 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/encode_pattern_match/match_list_sugar.hvm
---
TaggedScott:
(main) = #List (List.nil #List λ* #List λ* 1 0)
(List.cons) = λa λb #List λc #List λ* #List (c a b)
(List.nil) = #List λ* #List λb b
Scott:
(main) = (List.nil λ* λ* 1 0)
(List.cons) = λa λb λc λ* (c a b)
(List.nil) = λ* λb b

View File

@ -1,5 +1,5 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/examples/all_tree.hvm
input_file: examples/all_tree.hvm
---
True

View File

@ -1,5 +1,5 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/examples/alloc_small_tree.hvm
input_file: examples/alloc_small_tree.hvm
---
λa λ* a

View File

@ -1,5 +1,5 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/examples/fib.hvm
input_file: examples/fib.hvm
---
1346269

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/examples/neg_fusion.hvm
input_file: examples/neg_fusion.hvm
---
λa λ* a

View File

@ -3,5 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/parse_file/repeated_adt_name.hvm
---
Errors:
At tests/golden_tests/parse_file/repeated_adt_name.hvm:2:6: Repeated datatype 'Foo'
 2 | data Foo = B
In tests/golden_tests/parse_file/repeated_adt_name.hvm :
Repeated datatype 'Foo'
 2 | data Foo = B


View File

@ -3,7 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/escape_sequences.hvm
---
Lazy mode:
("\n\r\t\0\"'\u{afe}`", "\n\r\t\0\"'\u{afe}`")
("\n\r\t\0\"'\u{afe}\\", "\n\r\t\0\"'\u{afe}\\")
Strict mode:
("\n\r\t\0\"'\u{afe}`", "\n\r\t\0\"'\u{afe}`")
("\n\r\t\0\"'\u{afe}\\", "\n\r\t\0\"'\u{afe}\\")

View File

@ -3,20 +3,7 @@ source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/names_hyphen_toplevel.hvm
---
Lazy mode:
Errors:
At tests/golden_tests/run_file/names_hyphen_toplevel.hvm:1:1: Names with '-' are not supported at top level.
 1 | this-is-not-allowed = 1
At tests/golden_tests/run_file/names_hyphen_toplevel.hvm:3:6: Names with '-' are not supported at top level.
 3 | data Foo-Bar = Baz-Qux
At tests/golden_tests/run_file/names_hyphen_toplevel.hvm:3:16: Names with '-' are not supported at top level.
 3 | data Foo-Bar = Baz-Qux
(Baz-Qux 1)
Strict mode:
Errors:
At tests/golden_tests/run_file/names_hyphen_toplevel.hvm:1:1: Names with '-' are not supported at top level.
 1 | this-is-not-allowed = 1
At tests/golden_tests/run_file/names_hyphen_toplevel.hvm:3:6: Names with '-' are not supported at top level.
 3 | data Foo-Bar = Baz-Qux
At tests/golden_tests/run_file/names_hyphen_toplevel.hvm:3:16: Names with '-' are not supported at top level.
 3 | data Foo-Bar = Baz-Qux
(Baz-Qux 1)

View File

@ -4,11 +4,19 @@ input_file: tests/golden_tests/run_file/override_list_ctr.hvm
---
Lazy mode:
Errors:
At tests/golden_tests/run_file/override_list_ctr.hvm:2:5: List.nil is a built-in constructor and should not be overridden.
 2 | = List.nil
In tests/golden_tests/run_file/override_list_ctr.hvm :
List.nil is a built-in constructor and should not be overridden.
 1 | data Override
 2 |  = List.nil
 3 | 
 4 | main = [λz λk z]
Strict mode:
Errors:
At tests/golden_tests/run_file/override_list_ctr.hvm:2:5: List.nil is a built-in constructor and should not be overridden.
 2 | = List.nil
In tests/golden_tests/run_file/override_list_ctr.hvm :
List.nil is a built-in constructor and should not be overridden.
 1 | data Override
 2 |  = List.nil
 3 | 
 4 | main = [λz λk z]

View File

@ -4,11 +4,19 @@ input_file: tests/golden_tests/run_file/override_str_ctr.hvm
---
Lazy mode:
Errors:
At tests/golden_tests/run_file/override_str_ctr.hvm:2:5: String.cons is a built-in constructor and should not be overridden.
 2 | = (String.cons any)
In tests/golden_tests/run_file/override_str_ctr.hvm :
String.cons is a built-in constructor and should not be overridden.
 1 | data Override
 2 |  = (String.cons any)
 3 | 
 4 | main = (String.cons "any")
Strict mode:
Errors:
At tests/golden_tests/run_file/override_str_ctr.hvm:2:5: String.cons is a built-in constructor and should not be overridden.
 2 | = (String.cons any)
In tests/golden_tests/run_file/override_str_ctr.hvm :
String.cons is a built-in constructor and should not be overridden.
 1 | data Override
 2 |  = (String.cons any)
 3 | 
 4 | main = (String.cons "any")

View File

@ -1,9 +0,0 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/str_backtick.hvm
---
Lazy mode:
"abc369*`asdf\"asdf"
Strict mode:
"abc369*`asdf\"asdf"

View File

@ -2,4 +2,4 @@
source: tests/golden_tests.rs
input_file: tests/golden_tests/run_lazy/lam_op2_nested.hvm
---
*
λa (+ (* a a) (+ (+ a 2) 3))