Compare commits

...

7 Commits

Author SHA1 Message Date
Greg Shuflin
4c6a93302d Support Record patterns 2021-11-01 13:48:04 -07:00
Greg Shuflin
a3f2539993 Support NotEqual builtin 2021-11-01 12:40:41 -07:00
Greg Shuflin
d9f53abeb2 While loops 2021-11-01 12:35:25 -07:00
Greg Shuflin
f28f4eab78 Additional eval test 2021-11-01 11:16:42 -07:00
Greg Shuflin
f4d3282090 Implement return control flow 2021-11-01 04:19:18 -07:00
Greg Shuflin
7289504ab7 Adjust types in TreeWalkEvaluator 2021-11-01 01:21:03 -07:00
Greg Shuflin
cd1bb91555 Add control flow types 2021-11-01 00:25:52 -07:00
14 changed files with 334 additions and 47 deletions

View File

@ -1,5 +1,10 @@
# Immediate TODOs / General Code Cleanup
## Evaluator
* Make the evaluator take ReducedIR items by reference
## Testing
* Make an automatic (macro-based?) system for numbering compiler errors, this should be every type of error

View File

@ -54,6 +54,14 @@ pub enum StatementKind {
Declaration(Declaration),
Import(ImportSpecifier),
Module(ModuleSpecifier),
Flow(FlowControl),
}
#[derive(Debug, Clone, PartialEq)]
pub enum FlowControl {
Continue,
Break,
Return(Option<Expression>),
}
#[derive(Debug, Clone, PartialEq, Default)]

View File

@ -47,6 +47,12 @@ pub fn walk_block<V: ASTVisitor>(v: &mut V, block: &Block) {
if let Recursion::Continue = v.module(module_spec) {
walk_block(v, &module_spec.contents);
},
Flow(ref flow_control) => match flow_control {
FlowControl::Return(Some(ref retval)) => {
walk_expression(v, retval);
}
_ => (),
},
}
}
}

View File

@ -1,7 +1,9 @@
#![allow(clippy::single_char_add_str)]
use std::fmt::Write;
use super::{
Block, Declaration, Expression, ExpressionKind, ImportSpecifier, InvocationArgument, ModuleSpecifier,
Signature, Statement, StatementKind, AST,
Block, Declaration, Expression, ExpressionKind, FlowControl, ImportSpecifier, InvocationArgument,
ModuleSpecifier, Signature, Statement, StatementKind, AST,
};
const LEVEL: usize = 2;
@ -35,6 +37,7 @@ fn render_statement(stmt: &Statement, indent: usize, buf: &mut String) {
Declaration(ref decl) => render_declaration(decl, indent, buf),
Import(ref spec) => render_import(spec, indent, buf),
Module(ref spec) => render_module(spec, indent, buf),
Flow(ref flow_control) => render_flow_control(flow_control, indent, buf),
}
}
@ -191,6 +194,15 @@ fn render_module(_expr: &ModuleSpecifier, _indent: usize, buf: &mut String) {
buf.push_str("(Module <some mod>)");
}
fn render_flow_control(flow: &FlowControl, _indent: usize, buf: &mut String) {
use FlowControl::*;
match flow {
Return(ref _expr) => write!(buf, "return <expr>").unwrap(),
Break => write!(buf, "break").unwrap(),
Continue => write!(buf, "continue").unwrap(),
}
}
#[cfg(test)]
mod test {
use super::render_ast;

View File

@ -33,6 +33,7 @@ pub enum Builtin {
IOGetLine,
Assignment,
Concatenate,
NotEqual,
}
impl Builtin {
@ -65,6 +66,7 @@ impl Builtin {
Concatenate => ty!(StringT -> StringT -> StringT),
Increment => ty!(Nat -> Int),
Negate => ty!(Nat -> Int),
NotEqual => ty!(Nat -> Nat -> Bool),
}
}
}
@ -116,6 +118,7 @@ impl FromStr for Builtin {
"<" => LessThan,
"<=" => LessThanOrEqual,
"==" => Equality,
"!=" => NotEqual,
"=" => Assignment,
"<=>" => Comparison,
"print" => IOPrint,

View File

@ -15,7 +15,7 @@
//! ```text
//! program := (statement delimiter)* EOF
//! delimiter := NEWLINE | ";"
//! statement := expression | declaration | import | module
//! statement := expression | declaration | import | module | flow
//! block := "{" (statement delimiter)* "}"
//! declaration := type_declaration | func_declaration | binding_declaration | impl_declaration
//! ```
@ -401,12 +401,28 @@ impl Parser {
Keyword(Impl) => self.impl_declaration().map(StatementKind::Declaration),
Keyword(Import) => self.import_declaration().map(StatementKind::Import),
Keyword(Module) => self.module_declaration().map(StatementKind::Module),
Keyword(Continue) | Keyword(Return) | Keyword(Break) =>
self.flow_control().map(StatementKind::Flow),
_ => self.expression().map(StatementKind::Expression),
}?;
let id = self.id_store.fresh();
Ok(Statement { kind, id, location: tok.location })
}
#[recursive_descent_method]
fn flow_control(&mut self) -> ParseResult<FlowControl> {
let tok = self.token_handler.next();
Ok(match tok.get_kind() {
Keyword(Continue) => FlowControl::Continue,
Keyword(Break) => FlowControl::Break,
Keyword(Return) => match self.token_handler.peek_kind() {
Semicolon | Newline => FlowControl::Return(None),
_ => FlowControl::Return(Some(self.expression()?)),
},
_ => unreachable!(),
})
}
#[recursive_descent_method]
fn annotation(&mut self) -> ParseResult<Declaration> {
expect!(self, AtSign);
@ -1107,7 +1123,11 @@ impl Parser {
let pat = self.pattern()?;
(name, pat)
}
_ => (name.clone(), Pattern::Literal(PatternLiteral::StringPattern(name))),
_ => {
let qualified_identifier =
QualifiedName { id: self.id_store.fresh(), components: vec![name.clone()] };
(name, Pattern::VarOrName(qualified_identifier))
}
})
}

View File

@ -1099,10 +1099,7 @@ fn pattern_matching() {
body: bx(IfExpressionBody::SimplePatternMatch {
pattern: Pattern::Record(
qn!(Something),
vec![
(rc("a"), Pattern::Literal(PatternLiteral::StringPattern(rc("a")))),
(rc("b"), Pattern::VarOrName(qn!(x)))
]
vec![(rc("a"), Pattern::VarOrName(qn!(a))), (rc("b"), Pattern::VarOrName(qn!(x)))]
),
then_case: vec![exst(NatLiteral(4))].into(),
else_case: Some(vec![exst(NatLiteral(9))].into()),
@ -1215,3 +1212,38 @@ if (45, "panda", false, 2.2) {
)
};
}
#[test]
fn flow_control() {
use ExpressionKind::*;
// This is an incorrect program, but shoudl parse correctly.
let source = r#"
fn test() {
let a = 10;
break;
continue;
return;
return 10;
}"#;
assert_ast!(
source,
vec![fn_decl(
Signature { name: rc("test"), operator: false, type_anno: None, params: vec![] },
vec![
decl(Declaration::Binding {
name: rc("a"),
constant: true,
type_anno: None,
expr: expr(NatLiteral(10))
}),
stmt(StatementKind::Flow(FlowControl::Break)),
stmt(StatementKind::Flow(FlowControl::Continue)),
stmt(StatementKind::Flow(FlowControl::Return(None))),
stmt(StatementKind::Flow(FlowControl::Return(Some(expr(NatLiteral(10)))))),
]
.into()
)]
);
}

View File

@ -1,4 +1,4 @@
use std::{collections::HashMap, str::FromStr};
use std::{collections::HashMap, rc::Rc, str::FromStr};
use crate::{
ast,
@ -80,6 +80,9 @@ impl<'a, 'b> Reducer<'a, 'b> {
ast::StatementKind::Module(_modspec) => {
//TODO handle modules
}
ast::StatementKind::Flow(..) => {
//TODO this should be an error
}
}
}
@ -100,7 +103,18 @@ impl<'a, 'b> Reducer<'a, 'b> {
_ => None,
},
_ => None,
ast::StatementKind::Module(_) | ast::StatementKind::Import(_) => {
//TODO need to handle function-internal modules, imports
None
}
ast::StatementKind::Flow(ast::FlowControl::Return(expr)) =>
if let Some(expr) = expr {
Some(Statement::Return(self.expression(expr)))
} else {
Some(Statement::Return(Expression::unit()))
},
ast::StatementKind::Flow(ast::FlowControl::Break) => Some(Statement::Break),
ast::StatementKind::Flow(ast::FlowControl::Continue) => Some(Statement::Continue),
}
}
@ -135,10 +149,9 @@ impl<'a, 'b> Reducer<'a, 'b> {
}),
NamedStruct { name, fields } => {
self.symbol_table.debug();
println!("Namedstruct name {} id: {}", name, name.id);
let symbol = self.symbol_table.lookup_symbol(&name.id).unwrap();
let (tag, type_id) = match symbol.spec() {
SymbolSpec::RecordConstructor { tag, members: _, type_id } => (tag, type_id),
SymbolSpec::RecordConstructor { tag, type_id } => (tag, type_id),
e => return Expression::ReductionError(format!("Bad symbol for NamedStruct: {:?}", e)),
};
@ -167,7 +180,15 @@ impl<'a, 'b> Reducer<'a, 'b> {
Expression::Call { f: Box::new(constructor), args: ordered_args }
}
Index { .. } => Expression::ReductionError("Index expr not implemented".to_string()),
WhileExpression { .. } => Expression::ReductionError("While expr not implemented".to_string()),
WhileExpression { condition, body } => {
let cond = Box::new(if let Some(condition) = condition {
self.expression(condition)
} else {
Expression::Literal(Literal::Bool(true))
});
let statements = self.function_internal_block(body);
Expression::Loop { cond, statements }
}
ForExpression { .. } => Expression::ReductionError("For expr not implemented".to_string()),
ListLiteral { .. } => Expression::ReductionError("ListLiteral expr not implemented".to_string()),
Access { name, expr } =>
@ -372,11 +393,27 @@ impl ast::Pattern {
spec => return Err(format!("Unexpected VarOrName symbol: {:?}", spec).into()),
}
}
ast::Pattern::Record(name, _specified_members /*Vec<(Rc<String>, Pattern)>*/) => {
ast::Pattern::Record(name, specified_members) => {
let symbol = symbol_table.lookup_symbol(&name.id).unwrap();
match symbol.spec() {
SymbolSpec::RecordConstructor { tag: _, members: _, type_id: _ } => unimplemented!(),
spec => return Err(format!("Unexpected Record pattern symbol: {:?}", spec).into()),
if let SymbolSpec::RecordConstructor { tag, type_id: _ } = symbol.spec() {
//TODO do this computation from the type_id
/*
if specified_members.iter().any(|(member, _)| !members.contains_key(member)) {
return Err(format!("Unknown key in record pattern").into());
}
*/
let subpatterns: Result<Vec<(Rc<String>, Pattern)>, PatternError> = specified_members
.iter()
.map(|(name, pat)| {
pat.reduce(symbol_table).map(|reduced_pat| (name.clone(), reduced_pat))
})
.into_iter()
.collect();
let subpatterns = subpatterns?;
Pattern::Record { tag, subpatterns }
} else {
return Err(format!("Unexpected Record pattern symbol: {:?}", symbol.spec()).into());
}
}
})

View File

@ -42,6 +42,9 @@ impl ReducedIR {
pub enum Statement {
Expression(Expression),
Binding { id: DefId, constant: bool, expr: Expression },
Return(Expression),
Continue,
Break,
}
#[derive(Debug, Clone)]
@ -55,6 +58,7 @@ pub enum Expression {
Call { f: Box<Expression>, args: Vec<Expression> },
Conditional { cond: Box<Expression>, then_clause: Vec<Statement>, else_clause: Vec<Statement> },
CaseMatch { cond: Box<Expression>, alternatives: Vec<Alternative> },
Loop { cond: Box<Expression>, statements: Vec<Statement> },
ReductionError(String),
}
@ -104,6 +108,7 @@ pub struct Alternative {
#[derive(Debug, Clone)]
pub enum Pattern {
Tuple { subpatterns: Vec<Pattern>, tag: Option<u32> },
Record { tag: u32, subpatterns: Vec<(Rc<String>, Pattern)> },
Literal(Literal),
Ignored,
Binding(DefId),

View File

@ -282,7 +282,7 @@ pub enum SymbolSpec {
Builtin(Builtin),
Func,
DataConstructor { tag: u32, type_id: TypeId },
RecordConstructor { tag: u32, members: HashMap<Rc<String>, TypeId>, type_id: TypeId },
RecordConstructor { tag: u32, type_id: TypeId },
GlobalBinding, //Only for global variables, not for function-local ones or ones within a `let` scope context
LocalVariable,
FunctionParam(u8),
@ -515,8 +515,7 @@ impl<'a> SymbolTableRunner<'a> {
let spec = match &variant.members {
type_inference::VariantMembers::Unit => SymbolSpec::DataConstructor { tag, type_id },
type_inference::VariantMembers::Tuple(..) => SymbolSpec::DataConstructor { tag, type_id },
type_inference::VariantMembers::Record(..) =>
SymbolSpec::RecordConstructor { tag, members: HashMap::new(), type_id },
type_inference::VariantMembers::Record(..) => SymbolSpec::RecordConstructor { tag, type_id },
};
self.table.add_symbol(id, fqsn, spec);
}

View File

@ -86,11 +86,12 @@ pub enum Kw {
Func,
For,
While,
Const,
Let,
In,
Mut,
Return,
Continue,
Break,
Alias,
Type,
SelfType,
@ -115,11 +116,12 @@ impl TryFrom<&str> for Kw {
"fn" => Kw::Func,
"for" => Kw::For,
"while" => Kw::While,
"const" => Kw::Const,
"let" => Kw::Let,
"in" => Kw::In,
"mut" => Kw::Mut,
"return" => Kw::Return,
"break" => Kw::Break,
"continue" => Kw::Continue,
"alias" => Kw::Alias,
"type" => Kw::Type,
"Self" => Kw::SelfType,

View File

@ -10,12 +10,30 @@ use crate::{
util::ScopeStack,
};
#[derive(Debug)]
enum StatementOutput {
Primitive(Primitive),
Nothing,
}
#[derive(Debug, Clone, Copy)]
enum LoopControlFlow {
Break,
Continue,
}
pub struct Evaluator<'a, 'b> {
pub type_context: &'b TypeContext,
pub state: &'b mut State<'a>,
type_context: &'b TypeContext,
state: &'b mut State<'a>,
early_returning: bool,
loop_control: Option<LoopControlFlow>,
}
impl<'a, 'b> Evaluator<'a, 'b> {
pub(crate) fn new(state: &'b mut State<'a>, type_context: &'b TypeContext) -> Self {
Self { state, type_context, early_returning: false, loop_control: None }
}
pub fn evaluate(&mut self, reduced: ReducedIR, repl: bool) -> Vec<Result<String, String>> {
let mut acc = vec![];
for (def_id, function) in reduced.functions.into_iter() {
@ -25,7 +43,8 @@ impl<'a, 'b> Evaluator<'a, 'b> {
for statement in reduced.entrypoint.into_iter() {
match self.statement(statement) {
Ok(Some(output)) if repl => acc.push(Ok(output.to_repl(self.type_context))),
Ok(StatementOutput::Primitive(output)) if repl =>
acc.push(Ok(output.to_repl(self.type_context))),
Ok(_) => (),
Err(error) => {
acc.push(Err(error.msg));
@ -38,25 +57,47 @@ impl<'a, 'b> Evaluator<'a, 'b> {
fn block(&mut self, statements: Vec<Statement>) -> EvalResult<Primitive> {
//TODO need to handle breaks, returns, etc.
let mut ret = None;
let mut retval = None;
for stmt in statements.into_iter() {
if let Some(MemoryValue::Primitive(prim)) = self.statement(stmt)? {
ret = Some(prim);
match self.statement(stmt)? {
StatementOutput::Nothing => (),
StatementOutput::Primitive(prim) => {
retval = Some(prim);
}
};
if self.early_returning {
break;
}
if let Some(_) = self.loop_control {
break;
}
}
Ok(if let Some(ret) = ret { ret } else { self.expression(Expression::unit())? })
Ok(if let Some(ret) = retval { ret } else { self.expression(Expression::unit())? })
}
fn statement(&mut self, stmt: Statement) -> EvalResult<Option<MemoryValue>> {
fn statement(&mut self, stmt: Statement) -> EvalResult<StatementOutput> {
match stmt {
Statement::Binding { ref id, expr, constant: _ } => {
let evaluated = self.expression(expr)?;
self.state.environments.insert(id.into(), evaluated.into());
Ok(None)
Ok(StatementOutput::Nothing)
}
Statement::Expression(expr) => {
let evaluated = self.expression(expr)?;
Ok(Some(evaluated.into()))
Ok(StatementOutput::Primitive(evaluated))
}
Statement::Return(expr) => {
let evaluated = self.expression(expr)?;
self.early_returning = true;
Ok(StatementOutput::Primitive(evaluated))
}
Statement::Break => {
self.loop_control = Some(LoopControlFlow::Break);
Ok(StatementOutput::Nothing)
}
Statement::Continue => {
self.loop_control = Some(LoopControlFlow::Continue);
Ok(StatementOutput::Nothing)
}
}
}
@ -124,6 +165,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
}
Expression::CaseMatch { box cond, alternatives } =>
self.case_match_expression(cond, alternatives)?,
Expression::Loop { box cond, statements } => self.loop_expression(cond, statements)?,
Expression::ReductionError(e) => return Err(e.into()),
Expression::Access { name, box expr } => {
let expr = self.expression(expr)?;
@ -150,6 +192,37 @@ impl<'a, 'b> Evaluator<'a, 'b> {
})
}
fn loop_expression(&mut self, cond: Expression, statements: Vec<Statement>) -> EvalResult<Primitive> {
let existing = self.loop_control;
let output = self.loop_expression_inner(cond, statements);
self.loop_control = existing;
output
}
fn loop_expression_inner(
&mut self,
cond: Expression,
statements: Vec<Statement>,
) -> EvalResult<Primitive> {
loop {
let cond = self.expression(cond.clone())?;
match cond {
Primitive::Literal(Literal::Bool(true)) => (),
Primitive::Literal(Literal::Bool(false)) => break,
e => return Err(format!("Loop condition evaluates to non-boolean: {:?}", e).into()),
};
//TODO eventually loops shoudl be able to return something
let _output = self.block(statements.clone())?;
match self.loop_control {
None | Some(LoopControlFlow::Continue) => (),
Some(LoopControlFlow::Break) => {
break;
}
}
}
Ok(Primitive::unit())
}
fn case_match_expression(
&mut self,
cond: Expression,
@ -188,6 +261,20 @@ impl<'a, 'b> Evaluator<'a, 'b> {
_ => false,
},
},
Pattern::Record { tag: pattern_tag, subpatterns } => match scrut {
//TODO several types of possible error here
Primitive::Object { tag, items, ordered_fields: Some(ordered_fields), .. }
if tag == pattern_tag =>
subpatterns.iter().all(|(field_name, subpat)| {
let idx = ordered_fields
.iter()
.position(|field| field.as_str() == field_name.as_ref())
.unwrap();
let item = &items[idx];
matches(item, subpat, scope)
}),
_ => false,
},
}
}
let cond = self.expression(cond)?;
@ -196,9 +283,10 @@ impl<'a, 'b> Evaluator<'a, 'b> {
let mut new_scope = self.state.environments.new_scope(None);
if matches(&cond, &alt.pattern, &mut new_scope) {
let mut new_state = State { environments: new_scope };
let mut evaluator = Evaluator { state: &mut new_state, type_context: self.type_context };
return evaluator.block(alt.item);
let mut evaluator = Evaluator::new(&mut new_state, self.type_context);
let output = evaluator.block(alt.item);
self.early_returning = evaluator.early_returning;
return output;
}
}
Err("No valid match in match expression".into())
@ -319,6 +407,12 @@ impl<'a, 'b> Evaluator<'a, 'b> {
(Equality, Lit(Bool(l)), Lit(Bool(r))) => Bool(l == r).into(),
(Equality, Lit(StringLit(ref l)), Lit(StringLit(ref r))) => Bool(l == r).into(),
(NotEqual, Lit(Nat(l)), Lit(Nat(r))) => Bool(l != r).into(),
(NotEqual, Lit(Int(l)), Lit(Int(r))) => Bool(l != r).into(),
(NotEqual, Lit(Float(l)), Lit(Float(r))) => Bool(l != r).into(),
(NotEqual, Lit(Bool(l)), Lit(Bool(r))) => Bool(l != r).into(),
(NotEqual, Lit(StringLit(ref l)), Lit(StringLit(ref r))) => Bool(l != r).into(),
(LessThan, Lit(Nat(l)), Lit(Nat(r))) => Bool(l < r).into(),
(LessThan, Lit(Int(l)), Lit(Int(r))) => Bool(l < r).into(),
(LessThan, Lit(Float(l)), Lit(Float(r))) => Bool(l < r).into(),
@ -360,7 +454,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
}
let mut frame_state = State { environments: self.state.environments.new_scope(None) };
let mut evaluator = Evaluator { state: &mut frame_state, type_context: self.type_context };
let mut evaluator = Evaluator::new(&mut frame_state, self.type_context);
for (n, evaled) in evaluated_args.into_iter().enumerate() {
let n = n as u8;

View File

@ -88,15 +88,6 @@ impl From<Primitive> for MemoryValue {
}
}
impl MemoryValue {
fn to_repl(&self, type_context: &TypeContext) -> String {
match self {
MemoryValue::Primitive(ref prim) => prim.to_repl(type_context),
MemoryValue::Function(..) => "<function>".to_string(),
}
}
}
#[derive(Debug)]
enum RuntimeValue {
Expression(Expression),
@ -181,7 +172,7 @@ impl<'a> State<'a> {
type_context: &TypeContext,
repl: bool,
) -> Vec<Result<String, String>> {
let mut evaluator = evaluator::Evaluator { state: self, type_context };
let mut evaluator = evaluator::Evaluator::new(self, type_context);
evaluator.evaluate(reduced, repl)
}
}

View File

@ -21,7 +21,7 @@ fn evaluate_input(input: &str) -> Result<String, String> {
symbol_table.debug();
let mut state = State::new();
let mut evaluator = Evaluator { state: &mut state, type_context: &type_context };
let mut evaluator = Evaluator::new(&mut state, &type_context);
let mut outputs = evaluator.evaluate(reduced_ir, true);
outputs.pop().unwrap()
}
@ -172,6 +172,29 @@ if x {
eval_assert(&source, expected);
}
#[test]
fn record_patterns() {
let source = r#"
type Ara = Kueh { a: Int, b: String } | Morbuk
let alpha = Ara::Kueh { a: 10, b: "sanchez" }
if alpha {
is Ara::Kueh { a, b } then (b, a)
is _ then ("nooo", 8888)
}"#;
eval_assert(source, r#"("sanchez", 10)"#);
let source = r#"
type Ara = Kueh { a: Int, b: String } | Morbuk
let alpha = Ara::Kueh { a: 10, b: "sanchez" }
if alpha {
is Ara::Kueh { a, b: le_value } then (le_value, (a*2))
is _ then ("nooo", 8888)
}"#;
eval_assert(source, r#"("sanchez", 20)"#);
}
#[test]
fn if_is_patterns() {
let source = r#"
@ -378,3 +401,53 @@ let value = Klewos::Klewos { a: 50, b: "nah" }
eval_assert(source, r#"(50, "nah")"#);
}
#[test]
fn early_return() {
let source = r#"
fn chnurmek(a: Int): Int {
if a == 5 then {
return 9999;
}
return (a + 2);
}
(chnurmek(5), chnurmek(0))
"#;
eval_assert(source, r#"(9999, 2)"#);
let source = r#"
fn marbuk(a: Int, b: Int): (Int, Int) {
if a == 5 then {
if b == 6 then {
return (50, 50);
}
return (a, b + 1)
}
(a * 100, b * 100)
}
let x = marbuk(1, 1)
let y = marbuk(5, 1)
let z = marbuk(5, 6)
(x, y, z)
"#;
eval_assert(source, "((100, 100), (5, 2), (50, 50))");
}
#[test]
fn loops() {
let source = r#"
let mut a = 0
let mut count = 0
while a != 5 {
a = a + 1
count = count + 100
}
count
"#;
eval_assert(source, "500");
}