Wire functions through everything, with some unimplemented, and add a basic scoped map.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
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 cranelift_codegen::entity::EntityRef;
|
||||
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
|
||||
// these off. But for now, given the amount of tables we keep around to track
|
||||
// state, it's easier to just include them.
|
||||
for stmt in program.statements.drain(..) {
|
||||
match stmt {
|
||||
for item in program.items.drain(..) {
|
||||
match item {
|
||||
TopLevel::Function(_, _, _) => unimplemented!(),
|
||||
|
||||
// Print statements are fairly easy to compile: we just lookup the
|
||||
// output buffer, the address of the string to print, and the value
|
||||
// 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.
|
||||
let buffer_ptr = self.output_buffer_ptr();
|
||||
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
|
||||
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
|
||||
// to bind to this variable.
|
||||
let (val, etype) =
|
||||
|
||||
@@ -31,7 +31,7 @@ type Variable = ArcIntern<String>;
|
||||
pub struct Program {
|
||||
// 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.
|
||||
pub(crate) statements: Vec<Statement>,
|
||||
pub(crate) items: Vec<TopLevel>,
|
||||
}
|
||||
|
||||
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> {
|
||||
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
|
||||
// adding to the end, but this works.
|
||||
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.
|
||||
///
|
||||
/// For now, this is either a binding site (`x = 4`) or a print statement
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::eval::{EvalEnvironment, EvalError, Value};
|
||||
use crate::ir::{Expression, Program, Statement};
|
||||
use crate::ir::{Expression, Program, Statement, TopLevel};
|
||||
|
||||
use super::{Primitive, Type, ValueOrRef};
|
||||
|
||||
@@ -12,14 +12,16 @@ impl Program {
|
||||
let mut env = EvalEnvironment::empty();
|
||||
let mut stdout = String::new();
|
||||
|
||||
for stmt in self.statements.iter() {
|
||||
for stmt in self.items.iter() {
|
||||
match stmt {
|
||||
Statement::Binding(_, name, _, value) => {
|
||||
TopLevel::Function(_, _, _) => unimplemented!(),
|
||||
|
||||
TopLevel::Statement(Statement::Binding(_, name, _, value)) => {
|
||||
let actual_value = value.eval(&env)?;
|
||||
env = env.extend(name.clone(), actual_value);
|
||||
}
|
||||
|
||||
Statement::Print(_, _, name) => {
|
||||
TopLevel::Statement(Statement::Print(_, _, name)) => {
|
||||
let value = env.lookup(name.clone())?;
|
||||
let line = format!("{} = {}\n", name, value);
|
||||
stdout.push_str(&line);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::ast::{Expression, Program, Statement};
|
||||
use super::ast::{Expression, Program, Statement, TopLevel};
|
||||
use internment::ArcIntern;
|
||||
use std::collections::HashSet;
|
||||
|
||||
@@ -10,7 +10,7 @@ impl Program {
|
||||
pub fn strings(&self) -> HashSet<ArcIntern<String>> {
|
||||
let mut result = HashSet::new();
|
||||
|
||||
for stmt in self.statements.iter() {
|
||||
for stmt in self.items.iter() {
|
||||
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 {
|
||||
fn register_strings(&self, string_set: &mut HashSet<ArcIntern<String>>) {
|
||||
match self {
|
||||
|
||||
@@ -68,6 +68,7 @@ mod examples;
|
||||
pub mod ir;
|
||||
pub mod syntax;
|
||||
pub mod type_infer;
|
||||
pub mod util;
|
||||
|
||||
/// Implementation module for the high-level compiler.
|
||||
mod compiler;
|
||||
|
||||
26
src/repl.rs
26
src/repl.rs
@@ -1,13 +1,13 @@
|
||||
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::util::scoped_map::ScopedMap;
|
||||
use codespan_reporting::diagnostic::Diagnostic;
|
||||
use codespan_reporting::files::SimpleFiles;
|
||||
use codespan_reporting::term::{self, Config};
|
||||
use cranelift_jit::JITModule;
|
||||
use cranelift_module::ModuleError;
|
||||
use pretty::termcolor::{ColorChoice, StandardStream};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// A high-level REPL helper for NGR.
|
||||
///
|
||||
@@ -23,7 +23,7 @@ use std::collections::HashMap;
|
||||
pub struct REPL {
|
||||
file_database: SimpleFiles<String, String>,
|
||||
jitter: Backend<JITModule>,
|
||||
variable_binding_sites: HashMap<String, Location>,
|
||||
variable_binding_sites: ScopedMap<String, Location>,
|
||||
console: StandardStream,
|
||||
console_config: Config,
|
||||
}
|
||||
@@ -70,7 +70,7 @@ impl REPL {
|
||||
Ok(REPL {
|
||||
file_database: SimpleFiles::new(),
|
||||
jitter: Backend::jit(None)?,
|
||||
variable_binding_sites: HashMap::new(),
|
||||
variable_binding_sites: ScopedMap::new(),
|
||||
console,
|
||||
console_config,
|
||||
})
|
||||
@@ -127,10 +127,14 @@ impl REPL {
|
||||
.get(entry)
|
||||
.expect("entry exists")
|
||||
.source();
|
||||
let syntax = Statement::parse(entry, source)?;
|
||||
let syntax = TopLevel::parse(entry, source)?;
|
||||
|
||||
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,
|
||||
// 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.
|
||||
@@ -141,15 +145,15 @@ impl REPL {
|
||||
}
|
||||
|
||||
crate::syntax::Program {
|
||||
statements: vec![
|
||||
Statement::Binding(loc.clone(), name.clone(), expr),
|
||||
Statement::Print(loc, name),
|
||||
items: vec![
|
||||
TopLevel::Statement(Statement::Binding(loc.clone(), name.clone(), expr)),
|
||||
TopLevel::Statement(Statement::Print(loc, name)),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
nonbinding => crate::syntax::Program {
|
||||
statements: vec![nonbinding],
|
||||
TopLevel::Statement(nonbinding) => crate::syntax::Program {
|
||||
items: vec![TopLevel::Statement(nonbinding)],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
//!
|
||||
//! * Turning the string into a series of language-specific [`Token`]s.
|
||||
//! * 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/)).
|
||||
//! * Validating the tree we have parsed, using [`Program::validate`],
|
||||
//! returning any warnings or errors we have found.
|
||||
@@ -44,7 +44,7 @@ mod validate;
|
||||
use crate::syntax::arbitrary::GenerationEnvironment;
|
||||
pub use crate::syntax::ast::*;
|
||||
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};
|
||||
#[cfg(test)]
|
||||
use ::pretty::{Arena, Pretty};
|
||||
@@ -243,18 +243,18 @@ impl Program {
|
||||
}
|
||||
}
|
||||
|
||||
impl Statement {
|
||||
/// Parse a statement that you have in memory, using the given index for [`Location`]s.
|
||||
impl TopLevel {
|
||||
/// 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
|
||||
/// 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
|
||||
/// 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)
|
||||
.spanned()
|
||||
.map(|(token, range)| (range.start, token, range.end));
|
||||
StatementParser::new()
|
||||
TopLevelParser::new()
|
||||
.parse(file_idx, lexer)
|
||||
.map_err(|e| ParserError::convert(file_idx, e))
|
||||
}
|
||||
@@ -276,7 +276,7 @@ fn order_of_operations() {
|
||||
assert_eq!(
|
||||
Program::from_str(muladd1).unwrap(),
|
||||
Program {
|
||||
statements: vec![Statement::Binding(
|
||||
items: vec![TopLevel::Statement(Statement::Binding(
|
||||
Location::new(testfile, 0..1),
|
||||
Name::manufactured("x"),
|
||||
Expression::Primitive(
|
||||
@@ -303,7 +303,7 @@ fn order_of_operations() {
|
||||
)
|
||||
]
|
||||
)
|
||||
),],
|
||||
))],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 proptest::sample::select;
|
||||
use proptest::{
|
||||
@@ -57,38 +57,40 @@ impl Arbitrary for Program {
|
||||
|
||||
fn arbitrary_with(genenv: Self::Parameters) -> Self::Strategy {
|
||||
proptest::collection::vec(
|
||||
ProgramStatementInfo::arbitrary(),
|
||||
ProgramTopLevelInfo::arbitrary(),
|
||||
genenv.block_length.clone(),
|
||||
)
|
||||
.prop_flat_map(move |mut items| {
|
||||
let mut statements = Vec::new();
|
||||
.prop_flat_map(move |mut ptlis| {
|
||||
let mut items = Vec::new();
|
||||
let mut genenv = genenv.clone();
|
||||
|
||||
for psi in items.drain(..) {
|
||||
for psi in ptlis.drain(..) {
|
||||
if genenv.bindings.is_empty() || psi.should_be_binding {
|
||||
genenv.return_type = psi.binding_type;
|
||||
let expr = Expression::arbitrary_with(genenv.clone());
|
||||
genenv.bindings.insert(psi.name.clone(), psi.binding_type);
|
||||
statements.push(
|
||||
items.push(
|
||||
expr.prop_map(move |expr| {
|
||||
Statement::Binding(Location::manufactured(), psi.name.clone(), expr)
|
||||
TopLevel::Statement(Statement::Binding(
|
||||
Location::manufactured(),
|
||||
psi.name.clone(),
|
||||
expr,
|
||||
))
|
||||
})
|
||||
.boxed(),
|
||||
);
|
||||
} else {
|
||||
let printers = genenv.bindings.keys().map(|n| {
|
||||
Just(Statement::Print(
|
||||
Just(TopLevel::Statement(Statement::Print(
|
||||
Location::manufactured(),
|
||||
Name::manufactured(n),
|
||||
))
|
||||
)))
|
||||
});
|
||||
statements.push(Union::new(printers).boxed());
|
||||
items.push(Union::new(printers).boxed());
|
||||
}
|
||||
}
|
||||
|
||||
statements
|
||||
.prop_map(|statements| Program { statements })
|
||||
.boxed()
|
||||
items.prop_map(|items| Program { items }).boxed()
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
@@ -104,13 +106,13 @@ impl Arbitrary for Name {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ProgramStatementInfo {
|
||||
struct ProgramTopLevelInfo {
|
||||
should_be_binding: bool,
|
||||
name: Name,
|
||||
binding_type: ConstantType,
|
||||
}
|
||||
|
||||
impl Arbitrary for ProgramStatementInfo {
|
||||
impl Arbitrary for ProgramTopLevelInfo {
|
||||
type Parameters = ();
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
|
||||
@@ -121,7 +123,7 @@ impl Arbitrary for ProgramStatementInfo {
|
||||
ConstantType::arbitrary(),
|
||||
)
|
||||
.prop_map(
|
||||
|(should_be_binding, name, binding_type)| ProgramStatementInfo {
|
||||
|(should_be_binding, name, binding_type)| ProgramTopLevelInfo {
|
||||
should_be_binding,
|
||||
name,
|
||||
binding_type,
|
||||
|
||||
@@ -16,7 +16,18 @@ use crate::syntax::Location;
|
||||
/// `validate` and it comes back without errors.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
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.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use internment::ArcIntern;
|
||||
|
||||
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;
|
||||
|
||||
impl Program {
|
||||
@@ -19,16 +19,19 @@ impl Program {
|
||||
let mut env = EvalEnvironment::empty();
|
||||
let mut stdout = String::new();
|
||||
|
||||
for stmt in self.statements.iter() {
|
||||
// at this point, evaluation is pretty simple. just walk through each
|
||||
// statement, in order, and record printouts as we come to them.
|
||||
for stmt in self.items.iter() {
|
||||
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)?;
|
||||
env = env.extend(name.clone().intern(), actual_value);
|
||||
}
|
||||
|
||||
Statement::Print(_, name) => {
|
||||
TopLevel::Statement(Statement::Print(_, name)) => {
|
||||
let value = env.lookup(name.clone().intern())?;
|
||||
let line = format!("{} = {}\n", name, value);
|
||||
stdout.push_str(&line);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
//! eventually want to leave lalrpop behind.)
|
||||
//!
|
||||
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 internment::ArcIntern;
|
||||
|
||||
@@ -57,21 +57,25 @@ extern {
|
||||
|
||||
pub Program: Program = {
|
||||
// a program is just a set of statements
|
||||
<stmts:ProgramTopLevel> => Program {
|
||||
statements: stmts
|
||||
<items:ProgramTopLevel> => Program {
|
||||
items
|
||||
}
|
||||
}
|
||||
|
||||
ProgramTopLevel: Vec<Statement> = {
|
||||
<rest: ProgramTopLevel> Function => unimplemented!(),
|
||||
<mut rest: ProgramTopLevel> <next:Statement> => {
|
||||
rest.push(next);
|
||||
ProgramTopLevel: Vec<TopLevel> = {
|
||||
<mut rest: ProgramTopLevel> <t:TopLevel> => {
|
||||
rest.push(t);
|
||||
rest
|
||||
},
|
||||
=> Vec::new(),
|
||||
}
|
||||
|
||||
Function: () = {
|
||||
pub TopLevel: TopLevel = {
|
||||
<f:Function> => f,
|
||||
<s:Statement> => TopLevel::Statement(s),
|
||||
}
|
||||
|
||||
Function: TopLevel = {
|
||||
"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
|
||||
// funny @L thing to get the source location before the variable, so that
|
||||
// we can say that this statement spans across everything.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::syntax::ast::{Expression, Program, Statement, Value};
|
||||
use pretty::{DocAllocator, DocBuilder, Pretty};
|
||||
|
||||
use super::ConstantType;
|
||||
use super::{ConstantType, TopLevel};
|
||||
|
||||
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Program
|
||||
where
|
||||
@@ -11,9 +11,9 @@ where
|
||||
fn pretty(self, allocator: &'a D) -> DocBuilder<'a, D, A> {
|
||||
let mut result = allocator.nil();
|
||||
|
||||
for stmt in self.statements.iter() {
|
||||
for tl in self.items.iter() {
|
||||
result = result
|
||||
.append(stmt.pretty(allocator))
|
||||
.append(tl.pretty(allocator))
|
||||
.append(allocator.text(";"))
|
||||
.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
|
||||
where
|
||||
A: 'a,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::{
|
||||
eval::PrimitiveType,
|
||||
syntax::{Expression, Location, Program, Statement},
|
||||
syntax::{Expression, Location, Program, Statement, TopLevel},
|
||||
util::scoped_map::ScopedMap,
|
||||
};
|
||||
use codespan_reporting::diagnostic::Diagnostic;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
/// 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
|
||||
/// actually a problem.
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -76,13 +77,13 @@ impl Program {
|
||||
/// actually a problem.
|
||||
pub fn validate_with_bindings(
|
||||
&self,
|
||||
bound_variables: &mut HashMap<String, Location>,
|
||||
bound_variables: &mut ScopedMap<String, Location>,
|
||||
) -> (Vec<Error>, Vec<Warning>) {
|
||||
let mut errors = vec![];
|
||||
let mut warnings = vec![];
|
||||
|
||||
for stmt in self.statements.iter() {
|
||||
let (mut new_errors, mut new_warnings) = stmt.validate(bound_variables);
|
||||
for stmt in self.items.iter() {
|
||||
let (mut new_errors, mut new_warnings) = stmt.validate_with_bindings(bound_variables);
|
||||
errors.append(&mut new_errors);
|
||||
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 {
|
||||
/// Validate that the statement makes semantic sense, not just syntactic sense.
|
||||
///
|
||||
@@ -103,7 +142,7 @@ impl Statement {
|
||||
/// and warnings.
|
||||
fn validate(
|
||||
&self,
|
||||
bound_variables: &mut HashMap<String, Location>,
|
||||
bound_variables: &mut ScopedMap<String, Location>,
|
||||
) -> (Vec<Error>, Vec<Warning>) {
|
||||
let mut errors = vec![];
|
||||
let mut warnings = vec![];
|
||||
@@ -139,7 +178,7 @@ impl Statement {
|
||||
}
|
||||
|
||||
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 {
|
||||
Expression::Value(_, _) => (vec![], vec![]),
|
||||
Expression::Reference(_, var) if variable_map.contains_key(var) => (vec![], vec![]),
|
||||
@@ -174,14 +213,14 @@ impl Expression {
|
||||
|
||||
#[test]
|
||||
fn cast_checks_are_reasonable() {
|
||||
let good_stmt = Statement::parse(0, "x = <u16>4u8;").expect("valid test case");
|
||||
let (good_errs, good_warns) = good_stmt.validate(&mut HashMap::new());
|
||||
let good_stmt = TopLevel::parse(0, "x = <u16>4u8;").expect("valid test case");
|
||||
let (good_errs, good_warns) = good_stmt.validate();
|
||||
|
||||
assert!(good_errs.is_empty());
|
||||
assert!(good_warns.is_empty());
|
||||
|
||||
let bad_stmt = Statement::parse(0, "x = <apple>4u8;").expect("valid test case");
|
||||
let (bad_errs, bad_warns) = bad_stmt.validate(&mut HashMap::new());
|
||||
let bad_stmt = TopLevel::parse(0, "x = <apple>4u8;").expect("valid test case");
|
||||
let (bad_errs, bad_warns) = bad_stmt.validate();
|
||||
|
||||
assert!(bad_warns.is_empty());
|
||||
assert_eq!(bad_errs.len(), 1);
|
||||
|
||||
@@ -36,7 +36,7 @@ type Variable = ArcIntern<String>;
|
||||
pub struct Program {
|
||||
// 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.
|
||||
pub(crate) statements: Vec<Statement>,
|
||||
pub(crate) items: Vec<TopLevel>,
|
||||
}
|
||||
|
||||
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> {
|
||||
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
|
||||
// adding to the end, but this works.
|
||||
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.
|
||||
///
|
||||
/// For now, this is either a binding site (`x = 4`) or a print statement
|
||||
|
||||
@@ -18,20 +18,42 @@ pub fn convert_program(
|
||||
mut program: syntax::Program,
|
||||
constraint_db: &mut Vec<Constraint>,
|
||||
) -> ir::Program {
|
||||
let mut statements = Vec::new();
|
||||
let mut items = Vec::new();
|
||||
let mut renames = HashMap::new();
|
||||
let mut bindings = HashMap::new();
|
||||
|
||||
for stmt in program.statements.drain(..) {
|
||||
statements.append(&mut convert_statement(
|
||||
stmt,
|
||||
for item in program.items.drain(..) {
|
||||
items.append(&mut convert_top_level(
|
||||
item,
|
||||
constraint_db,
|
||||
&mut renames,
|
||||
&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
|
||||
@@ -269,11 +291,11 @@ mod tests {
|
||||
(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 renames = 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)
|
||||
}
|
||||
|
||||
@@ -321,24 +343,24 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn one_plus_one_plus_one() {
|
||||
let stmt = syntax::Statement::parse(1, "x = 1 + 1 + 1;").expect("basic parse");
|
||||
let (stmts, constraints) = infer_statement(stmt);
|
||||
let stmt = syntax::TopLevel::parse(1, "x = 1 + 1 + 1;").expect("basic parse");
|
||||
let (stmts, constraints) = infer_top_level(stmt);
|
||||
assert_eq!(stmts.len(), 2);
|
||||
let ir::Statement::Binding(
|
||||
let ir::TopLevel::Statement(ir::Statement::Binding(
|
||||
_args,
|
||||
name1,
|
||||
temp_ty1,
|
||||
ir::Expression::Primitive(_, primty1, ir::Primitive::Plus, primargs1),
|
||||
) = stmts.get(0).expect("item two")
|
||||
)) = stmts.get(0).expect("item two")
|
||||
else {
|
||||
panic!("Failed to match first statement");
|
||||
};
|
||||
let ir::Statement::Binding(
|
||||
let ir::TopLevel::Statement(ir::Statement::Binding(
|
||||
_args,
|
||||
name2,
|
||||
temp_ty2,
|
||||
ir::Expression::Primitive(_, primty2, ir::Primitive::Plus, primargs2),
|
||||
) = stmts.get(1).expect("item two")
|
||||
)) = stmts.get(1).expect("item two")
|
||||
else {
|
||||
panic!("Failed to match second statement");
|
||||
};
|
||||
|
||||
@@ -6,14 +6,25 @@ pub fn finalize_program(
|
||||
resolutions: &TypeResolutions,
|
||||
) -> output::Program {
|
||||
output::Program {
|
||||
statements: program
|
||||
.statements
|
||||
items: program
|
||||
.items
|
||||
.drain(..)
|
||||
.map(|x| finalize_statement(x, resolutions))
|
||||
.map(|x| finalize_top_level(x, resolutions))
|
||||
.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(
|
||||
statement: input::Statement,
|
||||
resolutions: &TypeResolutions,
|
||||
|
||||
1
src/util.rs
Normal file
1
src/util.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod scoped_map;
|
||||
81
src/util/scoped_map.rs
Normal file
81
src/util/scoped_map.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user