Simple interpreter
parent
dc557c19de
commit
3ce75cfed8
|
@ -11,6 +11,12 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.71"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ascii-canvas"
|
name = "ascii-canvas"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
|
@ -272,6 +278,7 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
name = "mul"
|
name = "mul"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"lalrpop",
|
"lalrpop",
|
||||||
"lalrpop-util",
|
"lalrpop-util",
|
||||||
"regex",
|
"regex",
|
||||||
|
|
|
@ -7,5 +7,6 @@ edition = "2021"
|
||||||
lalrpop = "0.19.9"
|
lalrpop = "0.19.9"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.71"
|
||||||
lalrpop-util = { version = "0.19.9", features = ["lexer"] }
|
lalrpop-util = { version = "0.19.9", features = ["lexer"] }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|
|
@ -1,17 +1,28 @@
|
||||||
# implementation roadmap
|
# implementation roadmap
|
||||||
|
|
||||||
- [ ] parser
|
## stages
|
||||||
- [ ] uniform function call syntax
|
- [x] parser
|
||||||
- [ ] lossless syntax trees (rowan + ungrammar)
|
- [ ] lossless syntax trees (rowan + ungrammar)
|
||||||
- [ ] typechecker
|
- [ ] typechecker
|
||||||
- [ ] interpreter
|
- [x] interpreter
|
||||||
- [ ] code generator
|
- [ ] code generator
|
||||||
|
- [ ] formatter (pretty-printer)
|
||||||
|
|
||||||
|
## features
|
||||||
|
- [ ] primitives
|
||||||
|
- [x] booleans
|
||||||
|
- [x] integers
|
||||||
|
- [ ] floating point numbers
|
||||||
|
- [ ] text
|
||||||
- [ ] functions
|
- [ ] functions
|
||||||
- [ ] lambdas / closures
|
- [x] lambdas / closures
|
||||||
- [ ] generic functions
|
- [ ] generic functions
|
||||||
|
- [ ] uniform function call syntax
|
||||||
|
- [ ] operators
|
||||||
- [ ] algebraic data types
|
- [ ] algebraic data types
|
||||||
- [ ] product types
|
- [ ] product types
|
||||||
- [ ] sum types
|
- [ ] sum types
|
||||||
- [ ] variant types
|
- [ ] variant types
|
||||||
- [ ] pattern matching
|
- [ ] pattern matching
|
||||||
- [ ] trait / interface system
|
- [ ] traits
|
||||||
|
- [ ] operator overloading
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
main = || {
|
let main = || {
|
||||||
let result = sum_equals(1, 2, 3);
|
let result = sum_equals(1, 2, 3);
|
||||||
print(result)
|
print(result)
|
||||||
}
|
};
|
||||||
|
|
||||||
sum_equals = |x, y, expected| {
|
let sum_equals = |x, y, expected| {
|
||||||
let sum = add(x, y);
|
let sum = add(x, y);
|
||||||
equals(sum, expected)
|
equals(sum, expected)
|
||||||
}
|
};
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::syntax::{Expression, Name, Program, Statement};
|
||||||
|
|
||||||
|
pub type Environment = HashMap<Name, Value>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Value {
|
||||||
|
Boolean(bool),
|
||||||
|
Integer(i64),
|
||||||
|
Closure(Vec<Name>, Expression, Environment),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Interpreter {
|
||||||
|
environment: Environment,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InterpreterError {
|
||||||
|
VariableNotFound(Name),
|
||||||
|
IncorrectArgumentCount(usize, usize),
|
||||||
|
NonFunctionCall(Value),
|
||||||
|
EmptyProgram,
|
||||||
|
ExecutionError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for InterpreterError {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
InterpreterError::ExecutionError(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for InterpreterError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
InterpreterError::VariableNotFound(name) => write!(f, "Variable '{}' not found", name),
|
||||||
|
InterpreterError::IncorrectArgumentCount(expected, actual) => {
|
||||||
|
write!(f, "Expected {} arguments, but got {}", expected, actual)
|
||||||
|
}
|
||||||
|
InterpreterError::NonFunctionCall(value) => {
|
||||||
|
write!(f, "Function call on non-function value: {:?}", value)
|
||||||
|
}
|
||||||
|
InterpreterError::EmptyProgram => write!(f, "Program is empty"),
|
||||||
|
InterpreterError::ExecutionError(s) => write!(f, "Execution error: {}", s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, InterpreterError>;
|
||||||
|
|
||||||
|
impl Interpreter {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
environment: Environment::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interpret_statement(&mut self, statement: &Statement) -> Result<Value> {
|
||||||
|
match statement {
|
||||||
|
Statement::Binding(name, expression) => {
|
||||||
|
let value = self.evaluate(expression)?;
|
||||||
|
self.environment.insert(name.clone(), value.clone());
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
Statement::Expression(expression) => self.evaluate(expression),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interpret(&mut self, program: &Program) -> Result<Value> {
|
||||||
|
let mut result = None;
|
||||||
|
|
||||||
|
for statement in program {
|
||||||
|
result = Some(self.interpret_statement(statement)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Some(value) => Ok(value),
|
||||||
|
None => Err(InterpreterError::EmptyProgram),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate(&mut self, expression: &Expression) -> Result<Value> {
|
||||||
|
match expression {
|
||||||
|
Expression::Variable(name) => self
|
||||||
|
.environment
|
||||||
|
.get(name)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| InterpreterError::VariableNotFound(name.clone())),
|
||||||
|
|
||||||
|
Expression::Boolean(value) => Ok(Value::Boolean(*value)),
|
||||||
|
|
||||||
|
Expression::Integer(value) => Ok(Value::Integer(*value)),
|
||||||
|
|
||||||
|
Expression::Lambda(params, body) => Ok(Value::Closure(
|
||||||
|
params.clone(),
|
||||||
|
*body.clone(),
|
||||||
|
self.environment.clone(),
|
||||||
|
)),
|
||||||
|
|
||||||
|
Expression::Call(function, args) => {
|
||||||
|
let function_value = self.evaluate(function)?;
|
||||||
|
|
||||||
|
match function_value {
|
||||||
|
Value::Closure(params, body, mut environment) => {
|
||||||
|
if params.len() != args.len() {
|
||||||
|
return Err(InterpreterError::IncorrectArgumentCount(
|
||||||
|
params.len(),
|
||||||
|
args.len(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut arg_values = Vec::new();
|
||||||
|
for arg in args {
|
||||||
|
arg_values.push(self.evaluate(arg)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind argument values to parameter names in a new environment
|
||||||
|
for (param, arg_value) in params.iter().zip(arg_values.into_iter()) {
|
||||||
|
environment.insert(param.clone(), arg_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate the body expression in the new environment
|
||||||
|
let mut interpreter = Interpreter { environment };
|
||||||
|
interpreter.evaluate(&body)
|
||||||
|
}
|
||||||
|
_ => Err(InterpreterError::NonFunctionCall(function_value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression::Block(statements, expression) => {
|
||||||
|
let new_environment = self.environment.clone();
|
||||||
|
|
||||||
|
for statement in statements {
|
||||||
|
self.interpret_statement(statement)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut interpreter = Interpreter {
|
||||||
|
environment: new_environment,
|
||||||
|
};
|
||||||
|
interpreter.evaluate(expression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Interpreter {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
use lalrpop_util::lalrpop_mod;
|
use lalrpop_util::lalrpop_mod;
|
||||||
|
|
||||||
|
pub mod interpreter;
|
||||||
lalrpop_mod!(pub parser);
|
lalrpop_mod!(pub parser);
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
|
|
71
src/main.rs
71
src/main.rs
|
@ -1,22 +1,17 @@
|
||||||
|
use std::env::args;
|
||||||
|
use std::fs::read_to_string;
|
||||||
use std::io::{stdin, stdout, BufRead, Write};
|
use std::io::{stdin, stdout, BufRead, Write};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
use mul::parser::{ExpressionParser, StatementParser};
|
use mul::interpreter::Interpreter;
|
||||||
|
use mul::parser::{ProgramParser, StatementParser};
|
||||||
|
|
||||||
macro_rules! parse {
|
fn repl() {
|
||||||
($parser:expr, $line:expr) => {
|
|
||||||
match $parser.parse($line.as_str()) {
|
|
||||||
Ok(parsed) => println!("{parsed:?}"),
|
|
||||||
Err(lalrpop_util::ParseError::UnrecognizedEOF { .. }) => break,
|
|
||||||
Err(e) => println!("Parse error: {e}"),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut stdin = stdin().lock();
|
let mut stdin = stdin().lock();
|
||||||
let mut stdout = stdout().lock();
|
let mut stdout = stdout().lock();
|
||||||
let expression = ExpressionParser::new();
|
let parser = StatementParser::new();
|
||||||
let statement = StatementParser::new();
|
let mut intepreter = Interpreter::new();
|
||||||
loop {
|
loop {
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
print!("> ");
|
print!("> ");
|
||||||
|
@ -24,10 +19,50 @@ fn main() {
|
||||||
stdin
|
stdin
|
||||||
.read_line(&mut line)
|
.read_line(&mut line)
|
||||||
.expect("Failed to read from stdin");
|
.expect("Failed to read from stdin");
|
||||||
if line.trim_end().ends_with(';') {
|
if line.trim().is_empty() {
|
||||||
parse!(statement, line)
|
println!();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !line.trim_end().ends_with(';') {
|
||||||
|
line.push(';');
|
||||||
|
}
|
||||||
|
let parsed = match parser.parse(&line) {
|
||||||
|
Ok(parsed) => parsed,
|
||||||
|
Err(lalrpop_util::ParseError::UnrecognizedEOF { .. }) => break,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("{e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match intepreter.interpret_statement(&parsed) {
|
||||||
|
Ok(value) => println!("{value:?}"),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("{e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build(file: &Path) {
|
||||||
|
let content = read_to_string(file).expect("Failed to read file");
|
||||||
|
let parser = ProgramParser::new();
|
||||||
|
let mut interpreter = Interpreter::new();
|
||||||
|
let program = parser.parse(content.as_str()).unwrap_or_else(|e| {
|
||||||
|
eprintln!("{e}");
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
let v = interpreter.interpret(&program).unwrap_or_else(|e| {
|
||||||
|
eprintln!("{e}");
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
println!("{v:?}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
if let Some(file) = args().nth(1) {
|
||||||
|
build(file.as_ref())
|
||||||
} else {
|
} else {
|
||||||
parse!(expression, line)
|
repl()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,40 @@
|
||||||
use crate::syntax::{Expression, Name, Statement};
|
use crate::syntax::{Expression, Name, Program, Statement};
|
||||||
|
|
||||||
grammar;
|
grammar;
|
||||||
|
|
||||||
|
pub Program: Program = Statement*;
|
||||||
|
|
||||||
pub Statement: Statement = {
|
pub Statement: Statement = {
|
||||||
"let" <Name> "=" <Expression> ";" => Statement::Binding(<>),
|
"let" <Name> "=" <Expression> ";" => Statement::Binding(<>),
|
||||||
<Expression> ";" => Statement::Expression(<>),
|
<Block> ";"? => Statement::Expression(<>),
|
||||||
|
<Simple> ";" => Statement::Expression(<>),
|
||||||
};
|
};
|
||||||
|
|
||||||
pub Expression: Expression = {
|
pub Expression = {
|
||||||
"|" <params:Comma<Name>> "|" <body:Expression> => Expression::Lambda(params, Box::new(body)),
|
Simple,
|
||||||
|
Block,
|
||||||
|
};
|
||||||
|
|
||||||
|
Simple: Expression = {
|
||||||
|
"|" <parameters:Comma<Name>> "|" <body:Expression> => Expression::Lambda(parameters, Box::new(body)),
|
||||||
Atom,
|
Atom,
|
||||||
};
|
};
|
||||||
|
|
||||||
Atom: Expression = {
|
Atom: Expression = {
|
||||||
Name => Expression::Variable(<>),
|
Name => Expression::Variable(<>),
|
||||||
"{" <statements:Statement*> <result:Expression> "}" => Expression::Block(statements, Box::new(result)),
|
<callee:Atom> "(" <arguments:Comma<Expression>> ")" => Expression::Call(Box::new(callee), arguments),
|
||||||
<callee:Atom> "(" <args:Comma<Expression>> ")" => Expression::Call(Box::new(callee), args),
|
|
||||||
Integer => Expression::Integer(<>),
|
Integer => Expression::Integer(<>),
|
||||||
Boolean => Expression::Boolean(<>),
|
Boolean => Expression::Boolean(<>),
|
||||||
"(" <Expression> ")",
|
"(" <Expression> ")",
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: decide on identifier syntax
|
Block: Expression =
|
||||||
Name: Name =
|
"{" <statements:Statement*> <result:Expression> "}" => Expression::Block(statements, Box::new(result));
|
||||||
r"[a-zA-Z_]+" => <>.to_string();
|
|
||||||
|
|
||||||
Integer: i64 =
|
// TODO: decide on identifier syntax
|
||||||
r"[0-9]+" => <>.parse::<i64>().unwrap();
|
Name: Name = r"[a-zA-Z_]+" => <>.to_string();
|
||||||
|
|
||||||
|
Integer: i64 = r"[0-9]+" => <>.parse::<i64>().unwrap();
|
||||||
|
|
||||||
Boolean: bool = {
|
Boolean: bool = {
|
||||||
"true" => true,
|
"true" => true,
|
||||||
|
|
|
@ -1,23 +1,14 @@
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
pub type Name = String;
|
pub type Name = String;
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub type Program = Vec<Statement>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum Statement {
|
pub enum Statement {
|
||||||
Binding(Name, Expression),
|
Binding(Name, Expression),
|
||||||
Expression(Expression),
|
Expression(Expression),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Statement {
|
#[derive(Debug, Clone)]
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Statement::Binding(name, body) => write!(f, "{name} = {body};"),
|
|
||||||
Statement::Expression(body) => write!(f, "{body};"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Expression {
|
pub enum Expression {
|
||||||
Variable(Name),
|
Variable(Name),
|
||||||
Block(Vec<Statement>, Box<Expression>),
|
Block(Vec<Statement>, Box<Expression>),
|
||||||
|
@ -26,32 +17,3 @@ pub enum Expression {
|
||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
Integer(i64),
|
Integer(i64),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Expression {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Expression::Variable(name) => write!(f, "{name}"),
|
|
||||||
Expression::Block(statements, result) => {
|
|
||||||
writeln!(f, "{{")?;
|
|
||||||
for statement in statements {
|
|
||||||
writeln!(f, "\t{statement}")?;
|
|
||||||
}
|
|
||||||
writeln!(f, "\t{result}")?;
|
|
||||||
writeln!(f, "}}")
|
|
||||||
}
|
|
||||||
Expression::Lambda(params, body) => write!(f, "({}) -> {body}", params.join(", ")),
|
|
||||||
Expression::Call(callee, args) => {
|
|
||||||
write!(f, "{callee}(")?;
|
|
||||||
if let Some(arg) = args.first() {
|
|
||||||
write!(f, "{arg}")?;
|
|
||||||
}
|
|
||||||
for arg in args.iter().skip(1) {
|
|
||||||
write!(f, ", {arg}")?;
|
|
||||||
}
|
|
||||||
write!(f, ")")
|
|
||||||
}
|
|
||||||
Expression::Boolean(v) => write!(f, "{v}"),
|
|
||||||
Expression::Integer(v) => write!(f, "{v}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue