Clean up primitive handling, finally.

This commit is contained in:
2024-04-16 16:20:31 -07:00
parent 763a895285
commit 7d4f182a67
19 changed files with 399 additions and 550 deletions

View File

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

View File

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

View File

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

View File

@@ -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> = {

View File

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

View File

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