λ Support functions! #5

Open
acw wants to merge 59 commits from awick/functions into develop
18 changed files with 392 additions and 97 deletions
Showing only changes of commit 736d27953f - Show all commits

View File

@@ -1,7 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::eval::PrimitiveType; use crate::eval::PrimitiveType;
use crate::ir::{Expression, Primitive, Program, Statement, Type, Value, ValueOrRef}; use crate::ir::{Expression, Primitive, Program, Statement, TopLevel, Type, Value, ValueOrRef};
use crate::syntax::ConstantType; use crate::syntax::ConstantType;
use cranelift_codegen::entity::EntityRef; use cranelift_codegen::entity::EntityRef;
use cranelift_codegen::ir::{ use cranelift_codegen::ir::{
@@ -120,12 +120,14 @@ impl<M: Module> Backend<M> {
// this is likely to become more cumbersome, and we'll want to separate // this is likely to become more cumbersome, and we'll want to separate
// these off. But for now, given the amount of tables we keep around to track // these off. But for now, given the amount of tables we keep around to track
// state, it's easier to just include them. // state, it's easier to just include them.
for stmt in program.statements.drain(..) { for item in program.items.drain(..) {
match stmt { match item {
TopLevel::Function(_, _, _) => unimplemented!(),
// Print statements are fairly easy to compile: we just lookup the // Print statements are fairly easy to compile: we just lookup the
// output buffer, the address of the string to print, and the value // output buffer, the address of the string to print, and the value
// of whatever variable we're printing. Then we just call print. // of whatever variable we're printing. Then we just call print.
Statement::Print(ann, t, var) => { TopLevel::Statement(Statement::Print(ann, t, var)) => {
// Get the output buffer (or null) from our general compilation context. // Get the output buffer (or null) from our general compilation context.
let buffer_ptr = self.output_buffer_ptr(); let buffer_ptr = self.output_buffer_ptr();
let buffer_ptr = builder.ins().iconst(types::I64, buffer_ptr as i64); let buffer_ptr = builder.ins().iconst(types::I64, buffer_ptr as i64);
@@ -163,7 +165,7 @@ impl<M: Module> Backend<M> {
} }
// Variable binding is a little more con // Variable binding is a little more con
Statement::Binding(_, var_name, _, value) => { TopLevel::Statement(Statement::Binding(_, var_name, _, value)) => {
// Kick off to the `Expression` implementation to see what value we're going // Kick off to the `Expression` implementation to see what value we're going
// to bind to this variable. // to bind to this variable.
let (val, etype) = let (val, etype) =

View File

@@ -31,7 +31,7 @@ type Variable = ArcIntern<String>;
pub struct Program { pub struct Program {
// For now, a program is just a vector of statements. In the future, we'll probably // For now, a program is just a vector of statements. In the future, we'll probably
// extend this to include a bunch of other information, but for now: just a list. // extend this to include a bunch of other information, but for now: just a list.
pub(crate) statements: Vec<Statement>, pub(crate) items: Vec<TopLevel>,
} }
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Program impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Program
@@ -42,7 +42,7 @@ where
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> { fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
let mut result = allocator.nil(); let mut result = allocator.nil();
for stmt in self.statements.iter() { for stmt in self.items.iter() {
// there's probably a better way to do this, rather than constantly // there's probably a better way to do this, rather than constantly
// adding to the end, but this works. // adding to the end, but this works.
result = result result = result
@@ -69,6 +69,44 @@ impl Arbitrary for Program {
} }
} }
/// A thing that can sit at the top level of a file.
///
/// For the moment, these are statements and functions. Other things
/// will likely be added in the future, but for now: just statements
/// and functions
#[derive(Debug)]
pub enum TopLevel {
Statement(Statement),
Function(Variable, Vec<Variable>, Expression),
}
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b TopLevel
where
A: 'a,
D: ?Sized + DocAllocator<'a, A>,
{
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
match self {
TopLevel::Function(name, args, body) => allocator
.text("function")
.append(allocator.space())
.append(allocator.text(name.as_ref().to_string()))
.append(
allocator
.intersperse(
args.iter().map(|x| allocator.text(x.as_ref().to_string())),
", ",
)
.parens(),
)
.append(allocator.space())
.append(body.pretty(allocator)),
TopLevel::Statement(stmt) => stmt.pretty(allocator),
}
}
}
/// The representation of a statement in the language. /// The representation of a statement in the language.
/// ///
/// For now, this is either a binding site (`x = 4`) or a print statement /// For now, this is either a binding site (`x = 4`) or a print statement

View File

@@ -1,5 +1,5 @@
use crate::eval::{EvalEnvironment, EvalError, Value}; use crate::eval::{EvalEnvironment, EvalError, Value};
use crate::ir::{Expression, Program, Statement}; use crate::ir::{Expression, Program, Statement, TopLevel};
use super::{Primitive, Type, ValueOrRef}; use super::{Primitive, Type, ValueOrRef};
@@ -12,14 +12,16 @@ impl Program {
let mut env = EvalEnvironment::empty(); let mut env = EvalEnvironment::empty();
let mut stdout = String::new(); let mut stdout = String::new();
for stmt in self.statements.iter() { for stmt in self.items.iter() {
match stmt { match stmt {
Statement::Binding(_, name, _, value) => { TopLevel::Function(_, _, _) => unimplemented!(),
TopLevel::Statement(Statement::Binding(_, name, _, value)) => {
let actual_value = value.eval(&env)?; let actual_value = value.eval(&env)?;
env = env.extend(name.clone(), actual_value); env = env.extend(name.clone(), actual_value);
} }
Statement::Print(_, _, name) => { TopLevel::Statement(Statement::Print(_, _, name)) => {
let value = env.lookup(name.clone())?; let value = env.lookup(name.clone())?;
let line = format!("{} = {}\n", name, value); let line = format!("{} = {}\n", name, value);
stdout.push_str(&line); stdout.push_str(&line);

View File

@@ -1,4 +1,4 @@
use super::ast::{Expression, Program, Statement}; use super::ast::{Expression, Program, Statement, TopLevel};
use internment::ArcIntern; use internment::ArcIntern;
use std::collections::HashSet; use std::collections::HashSet;
@@ -10,7 +10,7 @@ impl Program {
pub fn strings(&self) -> HashSet<ArcIntern<String>> { pub fn strings(&self) -> HashSet<ArcIntern<String>> {
let mut result = HashSet::new(); let mut result = HashSet::new();
for stmt in self.statements.iter() { for stmt in self.items.iter() {
stmt.register_strings(&mut result); stmt.register_strings(&mut result);
} }
@@ -18,6 +18,15 @@ impl Program {
} }
} }
impl TopLevel {
fn register_strings(&self, string_set: &mut HashSet<ArcIntern<String>>) {
match self {
TopLevel::Function(_, _, body) => body.register_strings(string_set),
TopLevel::Statement(stmt) => stmt.register_strings(string_set),
}
}
}
impl Statement { impl Statement {
fn register_strings(&self, string_set: &mut HashSet<ArcIntern<String>>) { fn register_strings(&self, string_set: &mut HashSet<ArcIntern<String>>) {
match self { match self {

View File

@@ -68,6 +68,7 @@ mod examples;
pub mod ir; pub mod ir;
pub mod syntax; pub mod syntax;
pub mod type_infer; pub mod type_infer;
pub mod util;
/// Implementation module for the high-level compiler. /// Implementation module for the high-level compiler.
mod compiler; mod compiler;

View File

@@ -1,13 +1,13 @@
use crate::backend::{Backend, BackendError}; use crate::backend::{Backend, BackendError};
use crate::syntax::{ConstantType, Location, ParserError, Statement}; use crate::syntax::{ConstantType, Location, ParserError, Statement, TopLevel};
use crate::type_infer::TypeInferenceResult; use crate::type_infer::TypeInferenceResult;
use crate::util::scoped_map::ScopedMap;
use codespan_reporting::diagnostic::Diagnostic; use codespan_reporting::diagnostic::Diagnostic;
use codespan_reporting::files::SimpleFiles; use codespan_reporting::files::SimpleFiles;
use codespan_reporting::term::{self, Config}; use codespan_reporting::term::{self, Config};
use cranelift_jit::JITModule; use cranelift_jit::JITModule;
use cranelift_module::ModuleError; use cranelift_module::ModuleError;
use pretty::termcolor::{ColorChoice, StandardStream}; use pretty::termcolor::{ColorChoice, StandardStream};
use std::collections::HashMap;
/// A high-level REPL helper for NGR. /// A high-level REPL helper for NGR.
/// ///
@@ -23,7 +23,7 @@ use std::collections::HashMap;
pub struct REPL { pub struct REPL {
file_database: SimpleFiles<String, String>, file_database: SimpleFiles<String, String>,
jitter: Backend<JITModule>, jitter: Backend<JITModule>,
variable_binding_sites: HashMap<String, Location>, variable_binding_sites: ScopedMap<String, Location>,
console: StandardStream, console: StandardStream,
console_config: Config, console_config: Config,
} }
@@ -70,7 +70,7 @@ impl REPL {
Ok(REPL { Ok(REPL {
file_database: SimpleFiles::new(), file_database: SimpleFiles::new(),
jitter: Backend::jit(None)?, jitter: Backend::jit(None)?,
variable_binding_sites: HashMap::new(), variable_binding_sites: ScopedMap::new(),
console, console,
console_config, console_config,
}) })
@@ -127,10 +127,14 @@ impl REPL {
.get(entry) .get(entry)
.expect("entry exists") .expect("entry exists")
.source(); .source();
let syntax = Statement::parse(entry, source)?; let syntax = TopLevel::parse(entry, source)?;
let program = match syntax { let program = match syntax {
Statement::Binding(loc, name, expr) => { TopLevel::Function(_, _, _) => {
unimplemented!()
}
TopLevel::Statement(Statement::Binding(loc, name, expr)) => {
// if this is a variable binding, and we've never defined this variable before, // if this is a variable binding, and we've never defined this variable before,
// we should tell cranelift about it. this is optimistic; if we fail to compile, // we should tell cranelift about it. this is optimistic; if we fail to compile,
// then we won't use this definition until someone tries again. // then we won't use this definition until someone tries again.
@@ -141,15 +145,15 @@ impl REPL {
} }
crate::syntax::Program { crate::syntax::Program {
statements: vec![ items: vec![
Statement::Binding(loc.clone(), name.clone(), expr), TopLevel::Statement(Statement::Binding(loc.clone(), name.clone(), expr)),
Statement::Print(loc, name), TopLevel::Statement(Statement::Print(loc, name)),
], ],
} }
} }
nonbinding => crate::syntax::Program { TopLevel::Statement(nonbinding) => crate::syntax::Program {
statements: vec![nonbinding], items: vec![TopLevel::Statement(nonbinding)],
}, },
}; };

View File

@@ -8,7 +8,7 @@
//! //!
//! * Turning the string into a series of language-specific [`Token`]s. //! * Turning the string into a series of language-specific [`Token`]s.
//! * Taking those tokens, and computing a basic syntax tree from them, //! * Taking those tokens, and computing a basic syntax tree from them,
//! using our parser ([`ProgramParser`] or [`StatementParser`], generated //! using our parser ([`ProgramParser`] or [`TopLevelParser`], generated
//! by [`lalrpop`](https://lalrpop.github.io/lalrpop/)). //! by [`lalrpop`](https://lalrpop.github.io/lalrpop/)).
//! * Validating the tree we have parsed, using [`Program::validate`], //! * Validating the tree we have parsed, using [`Program::validate`],
//! returning any warnings or errors we have found. //! returning any warnings or errors we have found.
@@ -44,7 +44,7 @@ mod validate;
use crate::syntax::arbitrary::GenerationEnvironment; use crate::syntax::arbitrary::GenerationEnvironment;
pub use crate::syntax::ast::*; pub use crate::syntax::ast::*;
pub use crate::syntax::location::Location; pub use crate::syntax::location::Location;
pub use crate::syntax::parser::{ProgramParser, StatementParser}; pub use crate::syntax::parser::{ProgramParser, TopLevelParser};
pub use crate::syntax::tokens::{LexerError, Token}; pub use crate::syntax::tokens::{LexerError, Token};
#[cfg(test)] #[cfg(test)]
use ::pretty::{Arena, Pretty}; use ::pretty::{Arena, Pretty};
@@ -243,18 +243,18 @@ impl Program {
} }
} }
impl Statement { impl TopLevel {
/// Parse a statement that you have in memory, using the given index for [`Location`]s. /// Parse a top-level item that you have in memory, using the given index for [`Location`]s.
/// ///
/// As with [`Program::parse`], if you use a bad file index, you'll get weird behaviors /// As with [`Program::parse`], if you use a bad file index, you'll get weird behaviors
/// when you try to print errors, but things should otherwise work fine. This function /// when you try to print errors, but things should otherwise work fine. This function
/// will only parse a single statement, which is useful in the REPL, but probably shouldn't /// will only parse a single statement, which is useful in the REPL, but probably shouldn't
/// be used when reading in whole files. /// be used when reading in whole files.
pub fn parse(file_idx: usize, buffer: &str) -> Result<Statement, ParserError> { pub fn parse(file_idx: usize, buffer: &str) -> Result<TopLevel, ParserError> {
let lexer = Token::lexer(buffer) let lexer = Token::lexer(buffer)
.spanned() .spanned()
.map(|(token, range)| (range.start, token, range.end)); .map(|(token, range)| (range.start, token, range.end));
StatementParser::new() TopLevelParser::new()
.parse(file_idx, lexer) .parse(file_idx, lexer)
.map_err(|e| ParserError::convert(file_idx, e)) .map_err(|e| ParserError::convert(file_idx, e))
} }
@@ -276,7 +276,7 @@ fn order_of_operations() {
assert_eq!( assert_eq!(
Program::from_str(muladd1).unwrap(), Program::from_str(muladd1).unwrap(),
Program { Program {
statements: vec![Statement::Binding( items: vec![TopLevel::Statement(Statement::Binding(
Location::new(testfile, 0..1), Location::new(testfile, 0..1),
Name::manufactured("x"), Name::manufactured("x"),
Expression::Primitive( Expression::Primitive(
@@ -303,7 +303,7 @@ fn order_of_operations() {
) )
] ]
) )
),], ))],
} }
); );
} }

View File

@@ -1,4 +1,4 @@
use crate::syntax::ast::{ConstantType, Expression, Name, Program, Statement, Value}; use crate::syntax::ast::{ConstantType, Expression, Name, Program, Statement, TopLevel, Value};
use crate::syntax::location::Location; use crate::syntax::location::Location;
use proptest::sample::select; use proptest::sample::select;
use proptest::{ use proptest::{
@@ -57,38 +57,40 @@ impl Arbitrary for Program {
fn arbitrary_with(genenv: Self::Parameters) -> Self::Strategy { fn arbitrary_with(genenv: Self::Parameters) -> Self::Strategy {
proptest::collection::vec( proptest::collection::vec(
ProgramStatementInfo::arbitrary(), ProgramTopLevelInfo::arbitrary(),
genenv.block_length.clone(), genenv.block_length.clone(),
) )
.prop_flat_map(move |mut items| { .prop_flat_map(move |mut ptlis| {
let mut statements = Vec::new(); let mut items = Vec::new();
let mut genenv = genenv.clone(); let mut genenv = genenv.clone();
for psi in items.drain(..) { for psi in ptlis.drain(..) {
if genenv.bindings.is_empty() || psi.should_be_binding { if genenv.bindings.is_empty() || psi.should_be_binding {
genenv.return_type = psi.binding_type; genenv.return_type = psi.binding_type;
let expr = Expression::arbitrary_with(genenv.clone()); let expr = Expression::arbitrary_with(genenv.clone());
genenv.bindings.insert(psi.name.clone(), psi.binding_type); genenv.bindings.insert(psi.name.clone(), psi.binding_type);
statements.push( items.push(
expr.prop_map(move |expr| { expr.prop_map(move |expr| {
Statement::Binding(Location::manufactured(), psi.name.clone(), expr) TopLevel::Statement(Statement::Binding(
Location::manufactured(),
psi.name.clone(),
expr,
))
}) })
.boxed(), .boxed(),
); );
} else { } else {
let printers = genenv.bindings.keys().map(|n| { let printers = genenv.bindings.keys().map(|n| {
Just(Statement::Print( Just(TopLevel::Statement(Statement::Print(
Location::manufactured(), Location::manufactured(),
Name::manufactured(n), Name::manufactured(n),
)) )))
}); });
statements.push(Union::new(printers).boxed()); items.push(Union::new(printers).boxed());
} }
} }
statements items.prop_map(|items| Program { items }).boxed()
.prop_map(|statements| Program { statements })
.boxed()
}) })
.boxed() .boxed()
} }
@@ -104,13 +106,13 @@ impl Arbitrary for Name {
} }
#[derive(Debug)] #[derive(Debug)]
struct ProgramStatementInfo { struct ProgramTopLevelInfo {
should_be_binding: bool, should_be_binding: bool,
name: Name, name: Name,
binding_type: ConstantType, binding_type: ConstantType,
} }
impl Arbitrary for ProgramStatementInfo { impl Arbitrary for ProgramTopLevelInfo {
type Parameters = (); type Parameters = ();
type Strategy = BoxedStrategy<Self>; type Strategy = BoxedStrategy<Self>;
@@ -121,7 +123,7 @@ impl Arbitrary for ProgramStatementInfo {
ConstantType::arbitrary(), ConstantType::arbitrary(),
) )
.prop_map( .prop_map(
|(should_be_binding, name, binding_type)| ProgramStatementInfo { |(should_be_binding, name, binding_type)| ProgramTopLevelInfo {
should_be_binding, should_be_binding,
name, name,
binding_type, binding_type,

View File

@@ -16,7 +16,18 @@ use crate::syntax::Location;
/// `validate` and it comes back without errors. /// `validate` and it comes back without errors.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Program { pub struct Program {
pub statements: Vec<Statement>, pub items: Vec<TopLevel>,
}
/// A thing that can sit at the top level of a file.
///
/// For the moment, these are statements and functions. Other things
/// will likely be added in the future, but for now: just statements
/// and functions
#[derive(Clone, Debug, PartialEq)]
pub enum TopLevel {
Statement(Statement),
Function(Name, Vec<Name>, Expression),
} }
/// A Name. /// A Name.

View File

@@ -1,7 +1,7 @@
use internment::ArcIntern; use internment::ArcIntern;
use crate::eval::{EvalEnvironment, EvalError, PrimitiveType, Value}; use crate::eval::{EvalEnvironment, EvalError, PrimitiveType, Value};
use crate::syntax::{ConstantType, Expression, Program, Statement}; use crate::syntax::{ConstantType, Expression, Program, Statement, TopLevel};
use std::str::FromStr; use std::str::FromStr;
impl Program { impl Program {
@@ -19,16 +19,19 @@ impl Program {
let mut env = EvalEnvironment::empty(); let mut env = EvalEnvironment::empty();
let mut stdout = String::new(); let mut stdout = String::new();
for stmt in self.statements.iter() { for stmt in self.items.iter() {
// at this point, evaluation is pretty simple. just walk through each
// statement, in order, and record printouts as we come to them.
match stmt { match stmt {
Statement::Binding(_, name, value) => { TopLevel::Function(_name, _arg_names, _body) => {
unimplemented!()
}
// at this point, evaluation is pretty simple. just walk through each
// statement, in order, and record printouts as we come to them.
TopLevel::Statement(Statement::Binding(_, name, value)) => {
let actual_value = value.eval(&env)?; let actual_value = value.eval(&env)?;
env = env.extend(name.clone().intern(), actual_value); env = env.extend(name.clone().intern(), actual_value);
} }
Statement::Print(_, name) => { TopLevel::Statement(Statement::Print(_, name)) => {
let value = env.lookup(name.clone().intern())?; let value = env.lookup(name.clone().intern())?;
let line = format!("{} = {}\n", name, value); let line = format!("{} = {}\n", name, value);
stdout.push_str(&line); stdout.push_str(&line);

View File

@@ -9,7 +9,7 @@
//! eventually want to leave lalrpop behind.) //! eventually want to leave lalrpop behind.)
//! //!
use crate::syntax::{LexerError, Location}; use crate::syntax::{LexerError, Location};
use crate::syntax::ast::{Program,Statement,Expression,Value,Name}; use crate::syntax::ast::{Program,TopLevel,Statement,Expression,Value,Name};
use crate::syntax::tokens::{ConstantType, Token}; use crate::syntax::tokens::{ConstantType, Token};
use internment::ArcIntern; use internment::ArcIntern;
@@ -57,21 +57,25 @@ extern {
pub Program: Program = { pub Program: Program = {
// a program is just a set of statements // a program is just a set of statements
<stmts:ProgramTopLevel> => Program { <items:ProgramTopLevel> => Program {
statements: stmts items
} }
} }
ProgramTopLevel: Vec<Statement> = { ProgramTopLevel: Vec<TopLevel> = {
<rest: ProgramTopLevel> Function => unimplemented!(), <mut rest: ProgramTopLevel> <t:TopLevel> => {
<mut rest: ProgramTopLevel> <next:Statement> => { rest.push(t);
rest.push(next);
rest rest
}, },
=> Vec::new(), => Vec::new(),
} }
Function: () = { pub TopLevel: TopLevel = {
<f:Function> => f,
<s:Statement> => TopLevel::Statement(s),
}
Function: TopLevel = {
"function" "(" Arguments OptionalComma ")" Expression => unimplemented!(), "function" "(" Arguments OptionalComma ")" Expression => unimplemented!(),
} }
@@ -123,7 +127,7 @@ Statements: Vec<Statement> = {
} }
} }
pub Statement: Statement = { Statement: Statement = {
// A statement can be a variable binding. Note, here, that we use this // A statement can be a variable binding. Note, here, that we use this
// funny @L thing to get the source location before the variable, so that // funny @L thing to get the source location before the variable, so that
// we can say that this statement spans across everything. // we can say that this statement spans across everything.

View File

@@ -1,7 +1,7 @@
use crate::syntax::ast::{Expression, Program, Statement, Value}; use crate::syntax::ast::{Expression, Program, Statement, Value};
use pretty::{DocAllocator, DocBuilder, Pretty}; use pretty::{DocAllocator, DocBuilder, Pretty};
use super::ConstantType; use super::{ConstantType, TopLevel};
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Program impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Program
where where
@@ -11,9 +11,9 @@ where
fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> { fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> {
let mut result = allocator.nil(); let mut result = allocator.nil();
for stmt in self.statements.iter() { for tl in self.items.iter() {
result = result result = result
.append(stmt.pretty(allocator)) .append(tl.pretty(allocator))
.append(allocator.text(";")) .append(allocator.text(";"))
.append(allocator.hardline()); .append(allocator.hardline());
} }
@@ -22,6 +22,32 @@ where
} }
} }
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b TopLevel
where
A: 'a,
D: ?Sized + DocAllocator<'a, A>,
{
fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> {
match self {
TopLevel::Statement(stmt) => stmt.pretty(allocator),
TopLevel::Function(name, arg_names, body) => allocator
.text("function")
.append(allocator.space())
.append(allocator.text(name.to_string()))
.append(
allocator
.intersperse(
arg_names.iter().map(|x| allocator.text(x.to_string())),
CommaSep {},
)
.parens(),
)
.append(allocator.space())
.append(body.pretty(allocator)),
}
}
}
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Statement impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Statement
where where
A: 'a, A: 'a,

View File

@@ -1,9 +1,10 @@
use crate::{ use crate::{
eval::PrimitiveType, eval::PrimitiveType,
syntax::{Expression, Location, Program, Statement}, syntax::{Expression, Location, Program, Statement, TopLevel},
util::scoped_map::ScopedMap,
}; };
use codespan_reporting::diagnostic::Diagnostic; use codespan_reporting::diagnostic::Diagnostic;
use std::{collections::HashMap, str::FromStr}; use std::str::FromStr;
/// An error we found while validating the input program. /// An error we found while validating the input program.
/// ///
@@ -65,7 +66,7 @@ impl Program {
/// example, and generates warnings for things that are inadvisable but not /// example, and generates warnings for things that are inadvisable but not
/// actually a problem. /// actually a problem.
pub fn validate(&self) -> (Vec<Error>, Vec<Warning>) { pub fn validate(&self) -> (Vec<Error>, Vec<Warning>) {
let mut bound_variables = HashMap::new(); let mut bound_variables = ScopedMap::new();
self.validate_with_bindings(&mut bound_variables) self.validate_with_bindings(&mut bound_variables)
} }
@@ -76,13 +77,13 @@ impl Program {
/// actually a problem. /// actually a problem.
pub fn validate_with_bindings( pub fn validate_with_bindings(
&self, &self,
bound_variables: &mut HashMap<String, Location>, bound_variables: &mut ScopedMap<String, Location>,
) -> (Vec<Error>, Vec<Warning>) { ) -> (Vec<Error>, Vec<Warning>) {
let mut errors = vec![]; let mut errors = vec![];
let mut warnings = vec![]; let mut warnings = vec![];
for stmt in self.statements.iter() { for stmt in self.items.iter() {
let (mut new_errors, mut new_warnings) = stmt.validate(bound_variables); let (mut new_errors, mut new_warnings) = stmt.validate_with_bindings(bound_variables);
errors.append(&mut new_errors); errors.append(&mut new_errors);
warnings.append(&mut new_warnings); warnings.append(&mut new_warnings);
} }
@@ -91,6 +92,44 @@ impl Program {
} }
} }
impl TopLevel {
/// Validate that the top level item 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 thins that are inadvisable but not
/// actually a problem.
pub fn validate(&self) -> (Vec<Error>, Vec<Warning>) {
let mut bound_variables = ScopedMap::new();
self.validate_with_bindings(&mut bound_variables)
}
/// Validate that the top level item 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 thins that are inadvisable but not
/// actually a problem.
pub fn validate_with_bindings(
&self,
bound_variables: &mut ScopedMap<String, Location>,
) -> (Vec<Error>, Vec<Warning>) {
match self {
TopLevel::Function(name, arguments, body) => {
bound_variables.new_scope();
bound_variables.insert(name.name.clone(), name.location.clone());
for arg in arguments.iter() {
bound_variables.insert(arg.name.clone(), arg.location.clone());
}
let result = body.validate(&bound_variables);
bound_variables.release_scope();
result
}
TopLevel::Statement(stmt) => stmt.validate(bound_variables),
}
}
}
impl Statement { impl Statement {
/// Validate that the statement makes semantic sense, not just syntactic sense. /// Validate that the statement makes semantic sense, not just syntactic sense.
/// ///
@@ -103,7 +142,7 @@ impl Statement {
/// and warnings. /// and warnings.
fn validate( fn validate(
&self, &self,
bound_variables: &mut HashMap<String, Location>, bound_variables: &mut ScopedMap<String, Location>,
) -> (Vec<Error>, Vec<Warning>) { ) -> (Vec<Error>, Vec<Warning>) {
let mut errors = vec![]; let mut errors = vec![];
let mut warnings = vec![]; let mut warnings = vec![];
@@ -139,7 +178,7 @@ impl Statement {
} }
impl Expression { impl Expression {
fn validate(&self, variable_map: &HashMap<String, Location>) -> (Vec<Error>, Vec<Warning>) { fn validate(&self, variable_map: &ScopedMap<String, Location>) -> (Vec<Error>, Vec<Warning>) {
match self { match self {
Expression::Value(_, _) => (vec![], vec![]), Expression::Value(_, _) => (vec![], vec![]),
Expression::Reference(_, var) if variable_map.contains_key(var) => (vec![], vec![]), Expression::Reference(_, var) if variable_map.contains_key(var) => (vec![], vec![]),
@@ -174,14 +213,14 @@ impl Expression {
#[test] #[test]
fn cast_checks_are_reasonable() { fn cast_checks_are_reasonable() {
let good_stmt = Statement::parse(0, "x = <u16>4u8;").expect("valid test case"); let good_stmt = TopLevel::parse(0, "x = <u16>4u8;").expect("valid test case");
let (good_errs, good_warns) = good_stmt.validate(&mut HashMap::new()); let (good_errs, good_warns) = good_stmt.validate();
assert!(good_errs.is_empty()); assert!(good_errs.is_empty());
assert!(good_warns.is_empty()); assert!(good_warns.is_empty());
let bad_stmt = Statement::parse(0, "x = <apple>4u8;").expect("valid test case"); let bad_stmt = TopLevel::parse(0, "x = <apple>4u8;").expect("valid test case");
let (bad_errs, bad_warns) = bad_stmt.validate(&mut HashMap::new()); let (bad_errs, bad_warns) = bad_stmt.validate();
assert!(bad_warns.is_empty()); assert!(bad_warns.is_empty());
assert_eq!(bad_errs.len(), 1); assert_eq!(bad_errs.len(), 1);

View File

@@ -36,7 +36,7 @@ type Variable = ArcIntern<String>;
pub struct Program { pub struct Program {
// For now, a program is just a vector of statements. In the future, we'll probably // For now, a program is just a vector of statements. In the future, we'll probably
// extend this to include a bunch of other information, but for now: just a list. // extend this to include a bunch of other information, but for now: just a list.
pub(crate) statements: Vec<Statement>, pub(crate) items: Vec<TopLevel>,
} }
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Program impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Program
@@ -47,7 +47,7 @@ where
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> { fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
let mut result = allocator.nil(); let mut result = allocator.nil();
for stmt in self.statements.iter() { for stmt in self.items.iter() {
// there's probably a better way to do this, rather than constantly // there's probably a better way to do this, rather than constantly
// adding to the end, but this works. // adding to the end, but this works.
result = result result = result
@@ -60,6 +60,45 @@ where
} }
} }
/// A thing that can sit at the top level of a file.
///
/// For the moment, these are statements and functions. Other things
/// will likely be added in the future, but for now: just statements
/// and functions
#[derive(Debug)]
pub enum TopLevel {
Statement(Statement),
Function(Variable, Vec<Variable>, Expression),
}
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b TopLevel
where
A: 'a,
D: ?Sized + DocAllocator<'a, A>,
{
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
match self {
TopLevel::Function(name, args, body) => allocator
.text("function")
.append(allocator.space())
.append(allocator.text(name.as_ref().to_string()))
.append(allocator.space())
.append(
allocator
.intersperse(
args.iter().map(|x| allocator.text(x.as_ref().to_string())),
", ",
)
.parens(),
)
.append(allocator.space())
.append(body.pretty(allocator)),
TopLevel::Statement(stmt) => stmt.pretty(allocator),
}
}
}
/// The representation of a statement in the language. /// The representation of a statement in the language.
/// ///
/// For now, this is either a binding site (`x = 4`) or a print statement /// For now, this is either a binding site (`x = 4`) or a print statement

View File

@@ -18,20 +18,42 @@ pub fn convert_program(
mut program: syntax::Program, mut program: syntax::Program,
constraint_db: &mut Vec<Constraint>, constraint_db: &mut Vec<Constraint>,
) -> ir::Program { ) -> ir::Program {
let mut statements = Vec::new(); let mut items = Vec::new();
let mut renames = HashMap::new(); let mut renames = HashMap::new();
let mut bindings = HashMap::new(); let mut bindings = HashMap::new();
for stmt in program.statements.drain(..) { for item in program.items.drain(..) {
statements.append(&mut convert_statement( items.append(&mut convert_top_level(
stmt, item,
constraint_db, constraint_db,
&mut renames, &mut renames,
&mut bindings, &mut bindings,
)); ));
} }
ir::Program { statements } ir::Program { items }
}
/// This function takes a top-level item and converts it into the IR version of the
/// program, with all the appropriate type variables introduced and their constraints
/// added to the given database.
pub fn convert_top_level(
top_level: syntax::TopLevel,
constraint_db: &mut Vec<Constraint>,
renames: &mut HashMap<ArcIntern<String>, ArcIntern<String>>,
bindings: &mut HashMap<ArcIntern<String>, Type>,
) -> Vec<ir::TopLevel> {
match top_level {
syntax::TopLevel::Function(_, _arg_name, _) => {
unimplemented!()
}
syntax::TopLevel::Statement(stmt) => {
convert_statement(stmt, constraint_db, renames, bindings)
.drain(..)
.map(ir::TopLevel::Statement)
.collect()
}
}
} }
/// This function takes a syntactic statements and converts it into a series of /// This function takes a syntactic statements and converts it into a series of
@@ -269,11 +291,11 @@ mod tests {
(expr, stmts, constraints, ty) (expr, stmts, constraints, ty)
} }
fn infer_statement(x: syntax::Statement) -> (Vec<ir::Statement>, Vec<Constraint>) { fn infer_top_level(x: syntax::TopLevel) -> (Vec<ir::TopLevel>, Vec<Constraint>) {
let mut constraints = Vec::new(); let mut constraints = Vec::new();
let mut renames = HashMap::new(); let mut renames = HashMap::new();
let mut bindings = HashMap::new(); let mut bindings = HashMap::new();
let res = convert_statement(x, &mut constraints, &mut renames, &mut bindings); let res = convert_top_level(x, &mut constraints, &mut renames, &mut bindings);
(res, constraints) (res, constraints)
} }
@@ -321,24 +343,24 @@ mod tests {
#[test] #[test]
fn one_plus_one_plus_one() { fn one_plus_one_plus_one() {
let stmt = syntax::Statement::parse(1, "x = 1 + 1 + 1;").expect("basic parse"); let stmt = syntax::TopLevel::parse(1, "x = 1 + 1 + 1;").expect("basic parse");
let (stmts, constraints) = infer_statement(stmt); let (stmts, constraints) = infer_top_level(stmt);
assert_eq!(stmts.len(), 2); assert_eq!(stmts.len(), 2);
let ir::Statement::Binding( let ir::TopLevel::Statement(ir::Statement::Binding(
_args, _args,
name1, name1,
temp_ty1, temp_ty1,
ir::Expression::Primitive(_, primty1, ir::Primitive::Plus, primargs1), ir::Expression::Primitive(_, primty1, ir::Primitive::Plus, primargs1),
) = stmts.get(0).expect("item two") )) = stmts.get(0).expect("item two")
else { else {
panic!("Failed to match first statement"); panic!("Failed to match first statement");
}; };
let ir::Statement::Binding( let ir::TopLevel::Statement(ir::Statement::Binding(
_args, _args,
name2, name2,
temp_ty2, temp_ty2,
ir::Expression::Primitive(_, primty2, ir::Primitive::Plus, primargs2), ir::Expression::Primitive(_, primty2, ir::Primitive::Plus, primargs2),
) = stmts.get(1).expect("item two") )) = stmts.get(1).expect("item two")
else { else {
panic!("Failed to match second statement"); panic!("Failed to match second statement");
}; };

View File

@@ -6,14 +6,25 @@ pub fn finalize_program(
resolutions: &TypeResolutions, resolutions: &TypeResolutions,
) -> output::Program { ) -> output::Program {
output::Program { output::Program {
statements: program items: program
.statements .items
.drain(..) .drain(..)
.map(|x| finalize_statement(x, resolutions)) .map(|x| finalize_top_level(x, resolutions))
.collect(), .collect(),
} }
} }
fn finalize_top_level(item: input::TopLevel, resolutions: &TypeResolutions) -> output::TopLevel {
match item {
input::TopLevel::Function(name, args, body) => {
output::TopLevel::Function(name, args, finalize_expression(body, resolutions))
}
input::TopLevel::Statement(stmt) => {
output::TopLevel::Statement(finalize_statement(stmt, resolutions))
}
}
}
fn finalize_statement( fn finalize_statement(
statement: input::Statement, statement: input::Statement,
resolutions: &TypeResolutions, resolutions: &TypeResolutions,

1
src/util.rs Normal file
View File

@@ -0,0 +1 @@
pub mod scoped_map;

81
src/util/scoped_map.rs Normal file
View File

@@ -0,0 +1,81 @@
use std::{borrow::Borrow, collections::HashMap, hash::Hash};
/// A version of [`std::collections::HashMap`] with a built-in notion of scope.
pub struct ScopedMap<K: Eq + Hash + PartialEq, V> {
scopes: Vec<HashMap<K, V>>,
}
impl<K: Eq + Hash + PartialEq, V> ScopedMap<K, V> {
/// Generate a new scoped map.
///
/// In addition to generate the map structure, this method also generates
/// an initial scope for use by the caller.
pub fn new() -> ScopedMap<K, V> {
ScopedMap {
scopes: vec![HashMap::new()],
}
}
/// Get a value from the scoped map.
pub fn get<Q>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
for map in self.scopes.iter().rev() {
match map.get(k) {
None => continue,
Some(v) => return Some(v),
}
}
None
}
/// Returns true if the map contains the given key.
pub fn contains_key(&self, k: &K) -> bool {
self.scopes.iter().any(|x| x.contains_key(k))
}
/// Insert a value into the current binding scope.
///
/// If this variable is bound in the current scope, then its value will be
/// overridden. If it's bound in a previous scope, however, that value will
/// be shadowed, so that its value will preserved if/when the current scope
/// is popped.
pub fn insert(&mut self, k: K, v: V) {
self.scopes
.last_mut()
.expect("tried to insert into ScopedMap with no scopes")
.insert(k, v);
}
/// Create a new scope.
///
/// Modifications to this scope will shadow all previous scopes without
/// modifying them. Consider the following examples:
///
/// ```
/// use ngr::util::scoped_map::ScopedMap;
///
/// let mut example1 = ScopedMap::new();
/// example1.insert(1, true);
/// example1.insert(1, false);
/// assert_eq!(Some(&false), example1.get(&1));
/// let mut example2 = ScopedMap::new();
/// example2.insert(1, true);
/// example2.new_scope();
/// example2.insert(1, false);
/// assert_eq!(Some(&false), example2.get(&1));
/// example2.release_scope().expect("scope releases");
/// assert_eq!(Some(&true), example2.get(&1));
/// ```
pub fn new_scope(&mut self) {
self.scopes.push(HashMap::new());
}
/// Pop the current scope, returning to whatever was bound in the previous
/// scope. If there is no prior scope, `None` will be returned.
pub fn release_scope(&mut self) -> Option<HashMap<K, V>> {
self.scopes.pop()
}
}