Compare commits
No commits in common. "56e67121548fcefe47444152b80d5052035f8d02" and "3957e9c14a9355d5fe5ae9bf7e0b84398254c815" have entirely different histories.
56e6712154
...
3957e9c14a
32
src/main.rs
32
src/main.rs
|
@ -21,9 +21,9 @@
|
||||||
// - [x] with stack mechanics and refcounting
|
// - [x] with stack mechanics and refcounting
|
||||||
// - [ ] with tail-call optimization (nb: this may not be possible w/ a TW-VM)
|
// - [ ] with tail-call optimization (nb: this may not be possible w/ a TW-VM)
|
||||||
// - [ ] with all the necessary forms for current Ludus
|
// - [ ] with all the necessary forms for current Ludus
|
||||||
// * [x] guards in match clauses
|
// * [ ] guards in match clauses
|
||||||
// * [x] `as` patterns
|
// * [x] `as` patterns
|
||||||
// * [x] splat patterns in tuples, lists, dicts
|
// * [ ] splat patterns in tuples, lists, dicts
|
||||||
// * [ ] splats in list and dict literals
|
// * [ ] splats in list and dict literals
|
||||||
// * [ ] `loop` and `recur`
|
// * [ ] `loop` and `recur`
|
||||||
// * [ ] string patterns
|
// * [ ] string patterns
|
||||||
|
@ -55,14 +55,28 @@ use crate::base::*;
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let src = "
|
let src = "
|
||||||
fn t () -> true
|
fn fancy_add {
|
||||||
fn f () -> false
|
() -> 0
|
||||||
|
(n as :number) -> n
|
||||||
fn id {
|
(x as :number, y as :number) -> add (x, y)
|
||||||
(x) if f () -> x
|
|
||||||
(x) -> :whoops
|
|
||||||
}
|
}
|
||||||
id (:foo)
|
|
||||||
|
fn fancy_sub {
|
||||||
|
() -> 0
|
||||||
|
(n as :number) -> n
|
||||||
|
(x as :number, y as :number) -> sub (x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fib {
|
||||||
|
(1) -> 1
|
||||||
|
(2) -> 1
|
||||||
|
(n) -> fancy_add(
|
||||||
|
fib (fancy_sub (n, 1))
|
||||||
|
fib (fancy_sub (n, 2))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fib (30)
|
||||||
";
|
";
|
||||||
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
|
let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
|
||||||
if lex_errs.len() > 0 {
|
if lex_errs.len() > 0 {
|
||||||
|
|
|
@ -211,12 +211,12 @@ pub enum Pattern<'src> {
|
||||||
Keyword(&'src str),
|
Keyword(&'src str),
|
||||||
Word(&'src str),
|
Word(&'src str),
|
||||||
As(&'src str, &'src str),
|
As(&'src str, &'src str),
|
||||||
Splattern(Box<Spanned<Self>>),
|
|
||||||
Placeholder,
|
Placeholder,
|
||||||
Tuple(Vec<Spanned<Self>>),
|
Tuple(Vec<Spanned<Self>>),
|
||||||
List(Vec<Spanned<Self>>),
|
List(Vec<Spanned<Self>>),
|
||||||
Pair(&'src str, Box<Spanned<Self>>),
|
// is this the right representation for Dicts?
|
||||||
Dict(Vec<Spanned<Self>>),
|
// Could/should this also be a Vec?
|
||||||
|
Dict(Vec<Spanned<PairPattern<'src>>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src> fmt::Display for Pattern<'src> {
|
impl<'src> fmt::Display for Pattern<'src> {
|
||||||
|
@ -229,7 +229,6 @@ impl<'src> fmt::Display for Pattern<'src> {
|
||||||
Pattern::Keyword(k) => write!(f, ":{}", k),
|
Pattern::Keyword(k) => write!(f, ":{}", k),
|
||||||
Pattern::Word(w) => write!(f, "{}", w),
|
Pattern::Word(w) => write!(f, "{}", w),
|
||||||
Pattern::As(w, t) => write!(f, "{} as {}", w, t),
|
Pattern::As(w, t) => write!(f, "{} as {}", w, t),
|
||||||
Pattern::Splattern(p) => write!(f, "...{}", (*p).0.to_string()),
|
|
||||||
Pattern::Placeholder => write!(f, "_"),
|
Pattern::Placeholder => write!(f, "_"),
|
||||||
Pattern::Tuple(t) => write!(
|
Pattern::Tuple(t) => write!(
|
||||||
f,
|
f,
|
||||||
|
@ -256,7 +255,6 @@ impl<'src> fmt::Display for Pattern<'src> {
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", ")
|
.join(", ")
|
||||||
),
|
),
|
||||||
Pattern::Pair(key, value) => write!(f, ":{} {}", key, value.0),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -302,24 +300,8 @@ where
|
||||||
}
|
}
|
||||||
.map_with(|a, e| (a, e.span()));
|
.map_with(|a, e| (a, e.span()));
|
||||||
|
|
||||||
let bare_splat = just(Token::Punctuation("...")).map_with(|_, e| {
|
|
||||||
(
|
|
||||||
Pattern::Splattern(Box::new((Pattern::Placeholder, e.span()))),
|
|
||||||
e.span(),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let splattable = word_pattern.clone().or(placeholder_pattern.clone());
|
|
||||||
|
|
||||||
let patt_splat = just(Token::Punctuation("..."))
|
|
||||||
.ignore_then(splattable)
|
|
||||||
.map_with(|x, e| (Pattern::Splattern(Box::new(x)), e.span()));
|
|
||||||
|
|
||||||
let splattern = patt_splat.or(bare_splat);
|
|
||||||
|
|
||||||
let tuple_pattern = pattern
|
let tuple_pattern = pattern
|
||||||
.clone()
|
.clone()
|
||||||
.or(splattern.clone())
|
|
||||||
.separated_by(separators.clone())
|
.separated_by(separators.clone())
|
||||||
.allow_leading()
|
.allow_leading()
|
||||||
.allow_trailing()
|
.allow_trailing()
|
||||||
|
@ -330,7 +312,6 @@ where
|
||||||
|
|
||||||
let list_pattern = pattern
|
let list_pattern = pattern
|
||||||
.clone()
|
.clone()
|
||||||
.or(splattern.clone())
|
|
||||||
.separated_by(separators.clone())
|
.separated_by(separators.clone())
|
||||||
.allow_leading()
|
.allow_leading()
|
||||||
.allow_trailing()
|
.allow_trailing()
|
||||||
|
@ -340,18 +321,20 @@ where
|
||||||
|
|
||||||
let pair_pattern = select! {Token::Keyword(k) => k}
|
let pair_pattern = select! {Token::Keyword(k) => k}
|
||||||
.then(pattern.clone())
|
.then(pattern.clone())
|
||||||
.map_with(|(key, patt), e| (Pattern::Pair(key, Box::new(patt)), e.span()));
|
.map_with(|(key, patt), e| (PairPattern { key, patt }, e.span()));
|
||||||
|
|
||||||
let shorthand_pattern = select! {Token::Word(w) => w}.map_with(|w, e| {
|
let shorthand_pattern = select! {Token::Word(w) => w}.map_with(|w, e| {
|
||||||
(
|
(
|
||||||
Pattern::Pair(w, Box::new((Pattern::Word(w), e.span()))),
|
PairPattern {
|
||||||
|
key: w,
|
||||||
|
patt: ((Pattern::Word(w), e.span())),
|
||||||
|
},
|
||||||
e.span(),
|
e.span(),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let dict_pattern = pair_pattern
|
let dict_pattern = pair_pattern
|
||||||
.or(shorthand_pattern)
|
.or(shorthand_pattern)
|
||||||
.or(splattern.clone())
|
|
||||||
.separated_by(separators.clone())
|
.separated_by(separators.clone())
|
||||||
.allow_leading()
|
.allow_leading()
|
||||||
.allow_trailing()
|
.allow_trailing()
|
||||||
|
@ -521,18 +504,6 @@ where
|
||||||
)
|
)
|
||||||
.map_with(|clauses, e| (Ast::When(clauses), e.span()));
|
.map_with(|clauses, e| (Ast::When(clauses), e.span()));
|
||||||
|
|
||||||
let guarded_clause = pattern
|
|
||||||
.clone()
|
|
||||||
.then_ignore(just(Token::Reserved("if")))
|
|
||||||
.then(simple.clone())
|
|
||||||
.then_ignore(just(Token::Punctuation("->")))
|
|
||||||
.then(expr.clone())
|
|
||||||
.map_with(|((patt, guard), body), _| MatchClause {
|
|
||||||
patt,
|
|
||||||
guard: Some(guard),
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
|
|
||||||
let match_clause = pattern
|
let match_clause = pattern
|
||||||
.clone()
|
.clone()
|
||||||
.then_ignore(just(Token::Punctuation("->")))
|
.then_ignore(just(Token::Punctuation("->")))
|
||||||
|
@ -549,7 +520,6 @@ where
|
||||||
.then(
|
.then(
|
||||||
match_clause
|
match_clause
|
||||||
.clone()
|
.clone()
|
||||||
.or(guarded_clause)
|
|
||||||
.separated_by(terminators.clone())
|
.separated_by(terminators.clone())
|
||||||
.allow_leading()
|
.allow_leading()
|
||||||
.allow_trailing()
|
.allow_trailing()
|
||||||
|
@ -586,19 +556,6 @@ where
|
||||||
.then(block.clone())
|
.then(block.clone())
|
||||||
.map_with(|(count, body), e| (Ast::Repeat(Box::new(count), Box::new(body)), e.span()));
|
.map_with(|(count, body), e| (Ast::Repeat(Box::new(count), Box::new(body)), e.span()));
|
||||||
|
|
||||||
let fn_guarded = tuple_pattern
|
|
||||||
.clone()
|
|
||||||
.then_ignore(just(Token::Reserved("if")))
|
|
||||||
.then(simple.clone())
|
|
||||||
.then_ignore(just(Token::Punctuation("->")))
|
|
||||||
.then(nonbinding.clone())
|
|
||||||
.map_with(|((patt, guard), body), _| MatchClause {
|
|
||||||
patt,
|
|
||||||
body,
|
|
||||||
guard: Some(guard),
|
|
||||||
})
|
|
||||||
.labelled("function clause");
|
|
||||||
|
|
||||||
let fn_clause = tuple_pattern
|
let fn_clause = tuple_pattern
|
||||||
.clone()
|
.clone()
|
||||||
.then_ignore(just(Token::Punctuation("->")))
|
.then_ignore(just(Token::Punctuation("->")))
|
||||||
|
@ -669,7 +626,7 @@ where
|
||||||
|
|
||||||
let fn_named = just(Token::Reserved("fn"))
|
let fn_named = just(Token::Reserved("fn"))
|
||||||
.ignore_then(word.clone())
|
.ignore_then(word.clone())
|
||||||
.then(fn_clause.clone().or(fn_guarded.clone()))
|
.then(fn_clause.clone())
|
||||||
.map_with(|(word, clause), e| {
|
.map_with(|(word, clause), e| {
|
||||||
let name = if let Ast::Word(word) = word.0 {
|
let name = if let Ast::Word(word) = word.0 {
|
||||||
word
|
word
|
||||||
|
@ -684,7 +641,6 @@ where
|
||||||
.then(
|
.then(
|
||||||
fn_clause
|
fn_clause
|
||||||
.clone()
|
.clone()
|
||||||
.or(fn_guarded.clone())
|
|
||||||
.separated_by(terminators.clone())
|
.separated_by(terminators.clone())
|
||||||
.allow_leading()
|
.allow_leading()
|
||||||
.allow_trailing()
|
.allow_trailing()
|
||||||
|
|
123
src/vm.rs
123
src/vm.rs
|
@ -79,112 +79,51 @@ pub fn match_pattern<'src, 'a>(
|
||||||
}
|
}
|
||||||
// todo: add splats to these match clauses
|
// todo: add splats to these match clauses
|
||||||
(Pattern::Tuple(x), Value::Tuple(y)) => {
|
(Pattern::Tuple(x), Value::Tuple(y)) => {
|
||||||
let has_splat = x.iter().any(|patt| {
|
if x.len() != y.len() {
|
||||||
if let (Pattern::Splattern(_), _) = patt {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if x.len() > y.len() || (!has_splat && x.len() != y.len()) {
|
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let to = ctx.len();
|
let to = ctx.len();
|
||||||
for i in 0..x.len() {
|
for i in 0..x.len() {
|
||||||
if let Pattern::Splattern(patt) = &x[i].0 {
|
if let None = match_pattern(&x[i].0, &y[i], ctx) {
|
||||||
let mut list = Vector::new();
|
while ctx.len() > to {
|
||||||
for i in i..y.len() {
|
ctx.pop();
|
||||||
list.push_back(y[i].clone())
|
|
||||||
}
|
|
||||||
let list = Value::List(list);
|
|
||||||
match_pattern(&(*patt).0, &list, ctx);
|
|
||||||
} else {
|
|
||||||
if let None = match_pattern(&x[i].0, &y[i], ctx) {
|
|
||||||
while ctx.len() > to {
|
|
||||||
ctx.pop();
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(ctx)
|
Some(ctx)
|
||||||
}
|
}
|
||||||
(Pattern::List(x), Value::List(y)) => {
|
(Pattern::List(x), Value::List(y)) => {
|
||||||
let has_splat = x.iter().any(|patt| {
|
if x.len() != y.len() {
|
||||||
if let (Pattern::Splattern(_), _) = patt {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if x.len() > y.len() || (!has_splat && x.len() != y.len()) {
|
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let to = ctx.len();
|
let to = ctx.len();
|
||||||
for i in 0..x.len() {
|
for i in 0..x.len() {
|
||||||
if let Pattern::Splattern(patt) = &x[i].0 {
|
if let None = match_pattern(&x[i].0, y.get(i).unwrap(), ctx) {
|
||||||
let list = Value::List(y.skip(i));
|
while ctx.len() > to {
|
||||||
match_pattern(&(*patt).0, &list, ctx);
|
ctx.pop();
|
||||||
} else {
|
|
||||||
if let None = match_pattern(&x[i].0, &y.get(i).unwrap(), ctx) {
|
|
||||||
while ctx.len() > to {
|
|
||||||
ctx.pop();
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(ctx)
|
Some(ctx)
|
||||||
}
|
}
|
||||||
// TODO: optimize this on several levels
|
|
||||||
// - [ ] opportunistic mutation
|
|
||||||
// - [ ] get rid of all the pointer indirection in word splats
|
|
||||||
(Pattern::Dict(x), Value::Dict(y)) => {
|
(Pattern::Dict(x), Value::Dict(y)) => {
|
||||||
let has_splat = x.iter().any(|patt| {
|
if x.len() != y.len() {
|
||||||
if let (Pattern::Splattern(_), _) = patt {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if x.len() > y.len() || (!has_splat && x.len() != y.len()) {
|
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let to = ctx.len();
|
let to = ctx.len();
|
||||||
let mut matched = vec![];
|
for (PairPattern { key, patt }, _) in x {
|
||||||
for i in 0..x.len() {
|
if let Some(val) = y.get(key) {
|
||||||
let (pattern, _) = &x[i];
|
if let None = match_pattern(&patt.0, val, ctx) {
|
||||||
match pattern {
|
while ctx.len() > to {
|
||||||
Pattern::Pair(key, patt) => {
|
ctx.pop();
|
||||||
if let Some(val) = y.get(key) {
|
|
||||||
if let None = match_pattern(&patt.0, val, ctx) {
|
|
||||||
while ctx.len() > to {
|
|
||||||
ctx.pop();
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
matched.push(key);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return None;
|
return None;
|
||||||
};
|
|
||||||
}
|
|
||||||
Pattern::Splattern(pattern) => match &(*pattern).0 {
|
|
||||||
Pattern::Word(w) => {
|
|
||||||
// TODO: find a way to take ownership
|
|
||||||
// this will ALWAYS make structural changes, because of this clone
|
|
||||||
// we want opportunistic mutation if possible
|
|
||||||
let mut unmatched = y.clone();
|
|
||||||
for key in matched.iter() {
|
|
||||||
unmatched.remove(*key);
|
|
||||||
}
|
|
||||||
ctx.push((*w, Value::Dict(unmatched)));
|
|
||||||
}
|
}
|
||||||
Pattern::Placeholder => (),
|
}
|
||||||
_ => unreachable!(),
|
} else {
|
||||||
},
|
return None;
|
||||||
_ => unreachable!(),
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(ctx)
|
Some(ctx)
|
||||||
}
|
}
|
||||||
|
@ -198,24 +137,8 @@ pub fn match_clauses<'src, 'a>(
|
||||||
ctx: &'a mut Vec<(&'src str, Value<'src>)>,
|
ctx: &'a mut Vec<(&'src str, Value<'src>)>,
|
||||||
) -> Result<Value<'src>, LudusError> {
|
) -> Result<Value<'src>, LudusError> {
|
||||||
let to = ctx.len();
|
let to = ctx.len();
|
||||||
for MatchClause { patt, body, guard } in clauses.iter() {
|
for MatchClause { patt, body, .. } in clauses.iter() {
|
||||||
if let Some(ctx) = match_pattern(&patt.0, value, ctx) {
|
if let Some(ctx) = match_pattern(&patt.0, value, ctx) {
|
||||||
let pass_guard = match guard {
|
|
||||||
None => true,
|
|
||||||
Some((ast, _)) => {
|
|
||||||
let guard_res = eval(&ast, ctx);
|
|
||||||
match &guard_res {
|
|
||||||
Err(_) => return guard_res,
|
|
||||||
Ok(val) => val.bool(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if !pass_guard {
|
|
||||||
while ctx.len() > to {
|
|
||||||
ctx.pop();
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let res = eval(&body.0, ctx);
|
let res = eval(&body.0, ctx);
|
||||||
while ctx.len() > to {
|
while ctx.len() > to {
|
||||||
ctx.pop();
|
ctx.pop();
|
||||||
|
@ -337,9 +260,7 @@ pub fn eval<'src, 'a>(
|
||||||
let val = if let Some((_, value)) = ctx.iter().rev().find(|(name, _)| w == name) {
|
let val = if let Some((_, value)) = ctx.iter().rev().find(|(name, _)| w == name) {
|
||||||
value.clone()
|
value.clone()
|
||||||
} else {
|
} else {
|
||||||
return Err(LudusError {
|
unreachable!()
|
||||||
msg: format!("unbound name {w}"),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
Ok(val)
|
Ok(val)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user