Clean up primitive handling, finally.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use crate::syntax::ast::{ConstantType, Expression, Name, Program, Statement, TopLevel, Value};
|
||||
use crate::syntax::ast::{ConstantType, Expression, Name, Program, TopLevel, Value};
|
||||
use crate::syntax::location::Location;
|
||||
use proptest::sample::select;
|
||||
use proptest::{
|
||||
@@ -72,19 +72,26 @@ impl Arbitrary for Program {
|
||||
genenv.bindings.insert(psi.name.clone(), psi.binding_type);
|
||||
items.push(
|
||||
expr.prop_map(move |expr| {
|
||||
TopLevel::Statement(Statement::Expression(Expression::Binding(
|
||||
TopLevel::Expression(Expression::Binding(
|
||||
Location::manufactured(),
|
||||
psi.name.clone(),
|
||||
Box::new(expr),
|
||||
)))
|
||||
))
|
||||
})
|
||||
.boxed(),
|
||||
);
|
||||
} else {
|
||||
let printers = genenv.bindings.keys().map(|n| {
|
||||
Just(TopLevel::Statement(Statement::Print(
|
||||
Just(TopLevel::Expression(Expression::Call(
|
||||
Location::manufactured(),
|
||||
Name::manufactured(n),
|
||||
Box::new(Expression::Primitive(
|
||||
Location::manufactured(),
|
||||
Name::manufactured("print"),
|
||||
)),
|
||||
vec![Expression::Reference(
|
||||
Location::manufactured(),
|
||||
n.to_string(),
|
||||
)],
|
||||
)))
|
||||
});
|
||||
items.push(Union::new(printers).boxed());
|
||||
@@ -186,7 +193,14 @@ impl Arbitrary for Expression {
|
||||
while args.len() > count {
|
||||
args.pop();
|
||||
}
|
||||
Expression::Primitive(Location::manufactured(), oper.to_string(), args)
|
||||
Expression::Call(
|
||||
Location::manufactured(),
|
||||
Box::new(Expression::Primitive(
|
||||
Location::manufactured(),
|
||||
Name::manufactured(oper),
|
||||
)),
|
||||
args,
|
||||
)
|
||||
})
|
||||
})
|
||||
.boxed()
|
||||
|
||||
@@ -26,7 +26,7 @@ pub struct Program {
|
||||
/// and functions
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum TopLevel {
|
||||
Statement(Statement),
|
||||
Expression(Expression),
|
||||
Function(
|
||||
Option<Name>,
|
||||
Vec<(Name, Option<Type>)>,
|
||||
@@ -86,38 +86,6 @@ impl fmt::Display for Name {
|
||||
}
|
||||
}
|
||||
|
||||
/// A parsed statement.
|
||||
///
|
||||
/// Statements are guaranteed to be syntactically valid, but may be
|
||||
/// complete nonsense at the semantic level. Which is to say, all the
|
||||
/// print statements were correctly formatted, and all the variables
|
||||
/// referenced are definitely valid symbols, but they may not have
|
||||
/// been defined or anything.
|
||||
///
|
||||
/// Note that equivalence testing on statements is independent of
|
||||
/// source location; it is testing if the two statements say the same
|
||||
/// thing, not if they are the exact same statement.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Statement {
|
||||
Print(Location, Name),
|
||||
Expression(Expression),
|
||||
}
|
||||
|
||||
impl PartialEq for Statement {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match self {
|
||||
Statement::Print(_, name1) => match other {
|
||||
Statement::Print(_, name2) => name1 == name2,
|
||||
_ => false,
|
||||
},
|
||||
Statement::Expression(e1) => match other {
|
||||
Statement::Expression(e2) => e1 == e2,
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An expression in the underlying syntax.
|
||||
///
|
||||
/// Like statements, these expressions are guaranteed to have been
|
||||
@@ -131,12 +99,25 @@ pub enum Expression {
|
||||
Reference(Location, String),
|
||||
FieldRef(Location, Box<Expression>, Name),
|
||||
Cast(Location, String, Box<Expression>),
|
||||
Primitive(Location, String, Vec<Expression>),
|
||||
Primitive(Location, Name),
|
||||
Call(Location, Box<Expression>, Vec<Expression>),
|
||||
Block(Location, Vec<Statement>),
|
||||
Block(Location, Vec<Expression>),
|
||||
Binding(Location, Name, Box<Expression>),
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
pub fn primitive(loc: Location, name: &str, args: Vec<Expression>) -> Expression {
|
||||
Expression::Call(
|
||||
loc.clone(),
|
||||
Box::new(Expression::Primitive(
|
||||
loc.clone(),
|
||||
Name::new(name, loc.clone()),
|
||||
)),
|
||||
args,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Expression {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match self {
|
||||
@@ -160,8 +141,8 @@ impl PartialEq for Expression {
|
||||
Expression::Cast(_, t2, e2) => t1 == t2 && e1 == e2,
|
||||
_ => false,
|
||||
},
|
||||
Expression::Primitive(_, prim1, args1) => match other {
|
||||
Expression::Primitive(_, prim2, args2) => prim1 == prim2 && args1 == args2,
|
||||
Expression::Primitive(_, prim1) => match other {
|
||||
Expression::Primitive(_, prim2) => prim1 == prim2,
|
||||
_ => false,
|
||||
},
|
||||
Expression::Call(_, f1, a1) => match other {
|
||||
@@ -189,7 +170,7 @@ impl Expression {
|
||||
Expression::Reference(loc, _) => loc,
|
||||
Expression::FieldRef(loc, _, _) => loc,
|
||||
Expression::Cast(loc, _, _) => loc,
|
||||
Expression::Primitive(loc, _, _) => loc,
|
||||
Expression::Primitive(loc, _) => loc,
|
||||
Expression::Call(loc, _, _) => loc,
|
||||
Expression::Block(loc, _) => loc,
|
||||
Expression::Binding(loc, _, _) => loc,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::eval::{EvalError, PrimitiveType, Value};
|
||||
use crate::syntax::{ConstantType, Expression, Name, Program, Statement, TopLevel};
|
||||
use crate::syntax::{ConstantType, Expression, Name, Program, TopLevel};
|
||||
use crate::util::scoped_map::ScopedMap;
|
||||
use internment::ArcIntern;
|
||||
use std::collections::HashMap;
|
||||
@@ -40,7 +40,7 @@ impl Program {
|
||||
}
|
||||
}
|
||||
|
||||
TopLevel::Statement(stmt) => last_result = stmt.eval(&mut stdout, &mut env)?,
|
||||
TopLevel::Expression(expr) => last_result = expr.eval(&mut stdout, &mut env)?,
|
||||
|
||||
TopLevel::Structure(_, _, _) => {
|
||||
last_result = Value::Void;
|
||||
@@ -52,32 +52,6 @@ impl Program {
|
||||
}
|
||||
}
|
||||
|
||||
impl Statement {
|
||||
fn eval(
|
||||
&self,
|
||||
stdout: &mut String,
|
||||
env: &mut ScopedMap<ArcIntern<String>, Value<Expression>>,
|
||||
) -> Result<Value<Expression>, EvalError<Expression>> {
|
||||
match self {
|
||||
Statement::Print(loc, name) => {
|
||||
let value = env
|
||||
.get(&name.clone().intern())
|
||||
.ok_or_else(|| EvalError::LookupFailed(loc.clone(), name.name.clone()))?;
|
||||
let value = if let Value::Number(x) = value {
|
||||
Value::U64(*x)
|
||||
} else {
|
||||
value.clone()
|
||||
};
|
||||
let line = format!("{} = {}\n", name, value);
|
||||
stdout.push_str(&line);
|
||||
Ok(Value::Void)
|
||||
}
|
||||
|
||||
Statement::Expression(e) => e.eval(stdout, env),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
fn eval(
|
||||
&self,
|
||||
@@ -144,16 +118,7 @@ impl Expression {
|
||||
Ok(target_type.safe_cast(&value)?)
|
||||
}
|
||||
|
||||
Expression::Primitive(_, op, args) => {
|
||||
let mut arg_values = Vec::with_capacity(args.len());
|
||||
|
||||
for arg in args.iter() {
|
||||
// yay, recursion! makes this pretty straightforward
|
||||
arg_values.push(arg.eval(stdout, env)?);
|
||||
}
|
||||
|
||||
Ok(Value::calculate(op, arg_values)?)
|
||||
}
|
||||
Expression::Primitive(_, op) => Ok(Value::primitive(op.name.clone())),
|
||||
|
||||
Expression::Call(loc, fun, args) => {
|
||||
let function = fun.eval(stdout, env)?;
|
||||
@@ -178,6 +143,31 @@ impl Expression {
|
||||
closure_env.release_scope();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
Value::Primitive(name) if name == "print" => {
|
||||
if let [Expression::Reference(_, name)] = &args[..] {
|
||||
let value = Expression::Reference(loc.clone(), name.clone()).eval(stdout, env)?;
|
||||
let value = match value {
|
||||
Value::Number(x) => Value::U64(x),
|
||||
x => x,
|
||||
};
|
||||
let addendum = format!("{} = {}\n", name, value);
|
||||
|
||||
stdout.push_str(&addendum);
|
||||
Ok(Value::Void)
|
||||
} else {
|
||||
panic!("Non-reference/non-singleton argument to 'print': {:?}", args);
|
||||
}
|
||||
}
|
||||
|
||||
Value::Primitive(name) => {
|
||||
let values = args
|
||||
.iter()
|
||||
.map(|x| x.eval(stdout, env))
|
||||
.collect::<Result<_, _>>()?;
|
||||
Value::calculate(name.as_str(), values).map_err(Into::into)
|
||||
}
|
||||
|
||||
_ => Err(EvalError::NotAFunction(loc.clone(), function)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
//! eventually want to leave lalrpop behind.)
|
||||
//!
|
||||
use crate::syntax::{Location, ParserError};
|
||||
use crate::syntax::ast::{Program,TopLevel,Statement,Expression,Value,Name,Type};
|
||||
use crate::syntax::ast::{Program,TopLevel,Expression,Value,Name,Type};
|
||||
use crate::syntax::tokens::{ConstantType, Token};
|
||||
use internment::ArcIntern;
|
||||
|
||||
@@ -81,7 +81,7 @@ ProgramTopLevel: Vec<TopLevel> = {
|
||||
pub TopLevel: TopLevel = {
|
||||
<f:Function> => f,
|
||||
<s:Structure> => s,
|
||||
<s:Statement> ";" => TopLevel::Statement(s),
|
||||
<s:Expression> ";" => TopLevel::Expression(s),
|
||||
}
|
||||
|
||||
Function: TopLevel = {
|
||||
@@ -132,34 +132,6 @@ TypeName: Name = {
|
||||
Name::new(v, Location::new(file_idx, name_start..name_end)),
|
||||
}
|
||||
|
||||
Statements: Vec<Statement> = {
|
||||
// a statement is either a set of statements followed by another
|
||||
// statement (note, here, that you can name the result of a sub-parse
|
||||
// using <name: subrule>) ...
|
||||
<mut stmts:Statements> ";" <stmt:Statement> => {
|
||||
stmts.push(stmt);
|
||||
stmts
|
||||
},
|
||||
|
||||
<stmt:Statement> => {
|
||||
vec![stmt]
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
Statement: Statement = {
|
||||
// A statement can just be a print statement.
|
||||
<ls: @L> "print" <name_start: @L> <v:"<var>"> <name_end: @L> <le: @L> =>
|
||||
Statement::Print(
|
||||
Location::new(file_idx, ls..le),
|
||||
Name::new(v, Location::new(file_idx, name_start..name_end)),
|
||||
),
|
||||
|
||||
// A statement can just be an expression.
|
||||
<e: Expression> =>
|
||||
Statement::Expression(e),
|
||||
}
|
||||
|
||||
// Expressions! Expressions are a little fiddly, because we're going to
|
||||
// use a little bit of a trick to make sure that we get operator precedence
|
||||
// right. The trick works by creating a top-level `Expression` grammar entry
|
||||
@@ -198,6 +170,21 @@ BindingExpression: Expression = {
|
||||
Box::new(e),
|
||||
),
|
||||
|
||||
PrintExpression,
|
||||
}
|
||||
|
||||
PrintExpression: Expression = {
|
||||
<ls: @L> "print" <pe: @L> <e: ConstructorExpression> <le: @L> =>
|
||||
Expression::Call(
|
||||
Location::new(file_idx, ls..le),
|
||||
Box::new(
|
||||
Expression::Primitive(
|
||||
Location::new(file_idx, ls..pe),
|
||||
Name::new("print", Location::new(file_idx, ls..pe)),
|
||||
),
|
||||
),
|
||||
vec![e],
|
||||
),
|
||||
ConstructorExpression,
|
||||
}
|
||||
|
||||
@@ -214,24 +201,24 @@ FieldSetter: (Name, Expression) = {
|
||||
// we group addition and subtraction under the heading "additive"
|
||||
AdditiveExpression: Expression = {
|
||||
<ls: @L> <e1:AdditiveExpression> <l: @L> "+" <e2:MultiplicativeExpression> <le: @L> =>
|
||||
Expression::Primitive(Location::new(file_idx, ls..le), "+".to_string(), vec![e1, e2]),
|
||||
Expression::primitive(Location::new(file_idx, ls..le), "+", vec![e1, e2]),
|
||||
<ls: @L> <e1:AdditiveExpression> <l: @L> "-" <e2:MultiplicativeExpression> <le: @L> =>
|
||||
Expression::Primitive(Location::new(file_idx, ls..le), "-".to_string(), vec![e1, e2]),
|
||||
Expression::primitive(Location::new(file_idx, ls..le), "-", vec![e1, e2]),
|
||||
MultiplicativeExpression,
|
||||
}
|
||||
|
||||
// similarly, we group multiplication and division under "multiplicative"
|
||||
MultiplicativeExpression: Expression = {
|
||||
<ls: @L> <e1:MultiplicativeExpression> <l: @L> "*" <e2:UnaryExpression> <le: @L> =>
|
||||
Expression::Primitive(Location::new(file_idx, ls..le), "*".to_string(), vec![e1, e2]),
|
||||
Expression::primitive(Location::new(file_idx, ls..le), "*", vec![e1, e2]),
|
||||
<ls: @L> <e1:MultiplicativeExpression> <l: @L> "/" <e2:UnaryExpression> <le: @L> =>
|
||||
Expression::Primitive(Location::new(file_idx, ls..le), "/".to_string(), vec![e1, e2]),
|
||||
Expression::primitive(Location::new(file_idx, ls..le), "/", vec![e1, e2]),
|
||||
UnaryExpression,
|
||||
}
|
||||
|
||||
UnaryExpression: Expression = {
|
||||
<l: @L> "-" <e:UnaryExpression> <le: @L> =>
|
||||
Expression::Primitive(Location::new(file_idx, l..le), "-".to_string(), vec![e]),
|
||||
Expression::primitive(Location::new(file_idx, l..le), "negate", vec![e]),
|
||||
<l: @L> "<" <v:"<var>"> ">" <e:UnaryExpression> <le: @L> =>
|
||||
Expression::Cast(Location::new(file_idx, l..le), v.to_string(), Box::new(e)),
|
||||
CallExpression,
|
||||
@@ -257,13 +244,21 @@ AtomicExpression: Expression = {
|
||||
// just a number
|
||||
<l: @L> <n:"<num>"> <end: @L> => Expression::Value(Location::new(file_idx, l..end), Value::Number(n.0, n.1, n.2)),
|
||||
// this expression could actually be a block!
|
||||
<s:@L> "{" <stmts:Statements> "}" <e:@L> => Expression::Block(Location::new(file_idx, s..e), stmts),
|
||||
<s:@L> "{" <exprs:Expressions> ";"? "}" <e:@L> => Expression::Block(Location::new(file_idx, s..e), exprs),
|
||||
<s:@L> "{" "}" <e:@L> => Expression::Block(Location::new(file_idx, s..e), vec![]),
|
||||
// finally, let people parenthesize expressions and get back to a
|
||||
// lower precedence
|
||||
"(" <e:Expression> ")" => e,
|
||||
}
|
||||
|
||||
Expressions: Vec<Expression> = {
|
||||
<e:Expression> => vec![e],
|
||||
<mut exps:Expressions> ";" <e:Expression> => {
|
||||
exps.push(e);
|
||||
exps
|
||||
}
|
||||
}
|
||||
|
||||
// Lifted from the LALRPop book, a comma-separated list of T that may or
|
||||
// may not conclude with a comma.
|
||||
Comma<T>: Vec<T> = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::syntax::ast::{ConstantType, Expression, Program, Statement, TopLevel, Type, Value};
|
||||
use crate::syntax::ast::{ConstantType, Expression, Program, TopLevel, Type, Value};
|
||||
use crate::util::pretty::{derived_display, Allocator};
|
||||
use pretty::{DocAllocator, DocBuilder};
|
||||
|
||||
@@ -20,7 +20,7 @@ impl Program {
|
||||
impl TopLevel {
|
||||
pub fn pretty<'a>(&self, allocator: &'a Allocator<'a>) -> DocBuilder<'a, Allocator<'a>> {
|
||||
match self {
|
||||
TopLevel::Statement(stmt) => stmt.pretty(allocator),
|
||||
TopLevel::Expression(expr) => expr.pretty(allocator),
|
||||
TopLevel::Function(name, args, rettype, body) => allocator
|
||||
.text("function")
|
||||
.append(allocator.space())
|
||||
@@ -87,18 +87,6 @@ impl TopLevel {
|
||||
}
|
||||
}
|
||||
|
||||
impl Statement {
|
||||
pub fn pretty<'a>(&self, allocator: &'a Allocator<'a>) -> DocBuilder<'a, Allocator<'a>> {
|
||||
match self {
|
||||
Statement::Print(_, var) => allocator
|
||||
.text("print")
|
||||
.append(allocator.space())
|
||||
.append(allocator.text(var.to_string())),
|
||||
Statement::Expression(e) => e.pretty(allocator),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
pub fn pretty<'a>(&self, allocator: &'a Allocator<'a>) -> DocBuilder<'a, Allocator<'a>> {
|
||||
match self {
|
||||
@@ -129,25 +117,7 @@ impl Expression {
|
||||
.text(t.clone())
|
||||
.angles()
|
||||
.append(e.pretty(allocator)),
|
||||
Expression::Primitive(_, op, exprs) if exprs.len() == 1 => allocator
|
||||
.text(op.to_string())
|
||||
.append(exprs[0].pretty(allocator)),
|
||||
Expression::Primitive(_, op, exprs) if exprs.len() == 2 => {
|
||||
let left = exprs[0].pretty(allocator);
|
||||
let right = exprs[1].pretty(allocator);
|
||||
|
||||
left.append(allocator.space())
|
||||
.append(allocator.text(op.to_string()))
|
||||
.append(allocator.space())
|
||||
.append(right)
|
||||
.parens()
|
||||
}
|
||||
Expression::Primitive(_, op, exprs) => {
|
||||
let call = allocator.text(op.to_string());
|
||||
let args = exprs.iter().map(|x| x.pretty(allocator));
|
||||
let comma_sepped_args = allocator.intersperse(args, allocator.text(","));
|
||||
call.append(comma_sepped_args.parens())
|
||||
}
|
||||
Expression::Primitive(_, op) => allocator.text(op.name.clone()),
|
||||
Expression::Call(_, fun, args) => {
|
||||
let args = args.iter().map(|x| x.pretty(allocator));
|
||||
let comma_sepped_args = allocator.intersperse(args, allocator.text(","));
|
||||
@@ -245,6 +215,5 @@ impl Type {
|
||||
|
||||
derived_display!(Program);
|
||||
derived_display!(TopLevel);
|
||||
derived_display!(Statement);
|
||||
derived_display!(Expression);
|
||||
derived_display!(Value);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
eval::PrimitiveType,
|
||||
syntax::{Expression, Location, Program, Statement, TopLevel},
|
||||
syntax::{Expression, Location, Program, TopLevel},
|
||||
util::scoped_map::ScopedMap,
|
||||
};
|
||||
use codespan_reporting::diagnostic::Diagnostic;
|
||||
@@ -132,47 +132,12 @@ impl TopLevel {
|
||||
bound_variables.release_scope();
|
||||
result
|
||||
}
|
||||
TopLevel::Statement(stmt) => stmt.validate(bound_variables),
|
||||
TopLevel::Expression(expr) => expr.validate(bound_variables),
|
||||
TopLevel::Structure(_, _, _) => (vec![], vec![]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Statement {
|
||||
/// Validate that the statement makes semantic sense, not just syntactic sense.
|
||||
///
|
||||
/// This checks for things like references to variables that don't exist, for
|
||||
/// example, and generates warnings for things that are inadvisable but not
|
||||
/// actually a problem. Since statements appear in a broader context, you'll
|
||||
/// need to provide the set of variables that are bound where this statement
|
||||
/// occurs. We use a `HashMap` to map these bound locations to the locations
|
||||
/// where their bound, because these locations are handy when generating errors
|
||||
/// and warnings.
|
||||
fn validate(
|
||||
&self,
|
||||
bound_variables: &mut ScopedMap<String, Location>,
|
||||
) -> (Vec<Error>, Vec<Warning>) {
|
||||
let mut errors = vec![];
|
||||
let mut warnings = vec![];
|
||||
|
||||
match self {
|
||||
Statement::Print(_, var) if bound_variables.contains_key(&var.name) => {}
|
||||
Statement::Print(loc, var) => {
|
||||
errors.push(Error::UnboundVariable(loc.clone(), var.to_string()))
|
||||
}
|
||||
|
||||
Statement::Expression(e) => {
|
||||
let (mut exp_errors, mut exp_warnings) = e.validate(bound_variables);
|
||||
|
||||
errors.append(&mut exp_errors);
|
||||
warnings.append(&mut exp_warnings);
|
||||
}
|
||||
}
|
||||
|
||||
(errors, warnings)
|
||||
}
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
fn validate(
|
||||
&self,
|
||||
@@ -207,18 +172,7 @@ impl Expression {
|
||||
|
||||
(errs, warns)
|
||||
}
|
||||
Expression::Primitive(_, _, args) => {
|
||||
let mut errors = vec![];
|
||||
let mut warnings = vec![];
|
||||
|
||||
for expr in args.iter() {
|
||||
let (mut err, mut warn) = expr.validate(variable_map);
|
||||
errors.append(&mut err);
|
||||
warnings.append(&mut warn);
|
||||
}
|
||||
|
||||
(errors, warnings)
|
||||
}
|
||||
Expression::Primitive(_, _) => (vec![], vec![]),
|
||||
Expression::Call(_, func, args) => {
|
||||
let (mut errors, mut warnings) = func.validate(variable_map);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user