Compare commits

...

6 commits

15 changed files with 286 additions and 56 deletions

View file

@ -25,5 +25,14 @@ Options:
-V, --version Print version
```
For example running the
[Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life)
implemented in [brainfuck](https://en.wikipedia.org/wiki/Brainfuck) (read the
source file to figure out how to use it):
```
RUST_LOG=info cargo run --release -- run -s ./lox/bf.lox -- bf/game_of_life.bf
```
Please note that some parts of the implementation have not been given enough
thought.

View file

@ -4,3 +4,6 @@
[>+<-[>+<-[>+<-[>[-]>+>+<<<-[>+<-]]]]]]]]]]]+>>>
]<<<
]
This program doesn't terminate; you will have to kill it.
Daniel B Cristofani (cristofdathevanetdotcom)
http://www.hevanet.com/cristofd/brainfuck/

38
bf/game_of_life.bf Normal file
View file

@ -0,0 +1,38 @@
[life.b -- John Horton Conway's Game of Life
(c) 2021 Daniel B. Cristofani
http://brainfuck.org/]
>>>->+>+++++>(++++++++++)[[>>>+<<<-]>+++++>+>>+[<<+>>>>>+<<<-]<-]>>>>[
[>>>+>+<<<<-]+++>>+[<+>>>+>+<<<-]>>[>[[>>>+<<<-]<]<<++>+>>>>>>-]<-
]+++>+>[[-]<+<[>+++++++++++++++++<-]<+]>>[
[+++++++++.-------->>>]+[-<<<]>>>[>>,----------[>]<]<<[
<<<[
>--[<->>+>-<<-]<[[>>>]+>-[+>>+>-]+[<<<]<-]>++>[<+>-]
>[[>>>]+[<<<]>>>-]+[->>>]<-[++>]>[------<]>+++[<<<]>
]<
]>[
-[+>>+>-]+>>+>>>+>[<<<]>->+>[
>[->+>+++>>++[>>>]+++<<<++<<<++[>>>]>>>]<<<[>[>>>]+>>>]
<<<<<<<[<<++<+[-<<<+]->++>>>++>>>++<<<<]<<<+[-<<<+]+>->>->>
]<<+<<+<<<+<<-[+<+<<-]+<+[
->+>[-<-<<[<<<]>[>>[>>>]<<+<[<<<]>-]]
<[<[<[<<<]>+>>[>>>]<<-]<[<<<]]>>>->>>[>>>]+>
]>+[-<<[-]<]-[
[>>>]<[<<[<<<]>>>>>+>[>>>]<-]>>>[>[>>>]<<<<+>[<<<]>>-]>
]<<<<<<[---<-----[-[-[<->>+++<+++++++[-]]]]<+<+]>
]>>
]
[This program simulates the Game of Life cellular automaton.
It duplicates the interface of the classic program at
http://www.linusakesson.net/programming/brainfuck/index.php,
but this program was written from scratch.
Type e.g. "be" to toggle the fifth cell in the second row, "q" to quit,
or a bare linefeed to advance one generation.
Grid wraps toroidally. Board size in parentheses in first line (2-166 work).
This program is licensed under a Creative Commons Attribution-ShareAlike 4.0
International License (http://creativecommons.org/licenses/by-sa/4.0/).]

View file

@ -68,10 +68,10 @@ class Interpreter {
}
if (instruction == ",") {
tape.current.data = promptAscii("> ");
}
if (instruction == "[" and tape.current.data < 0.0001) {
if (instruction == "[" and tape.current.data == 0) {
instruction = read(this.bfpath);
var bracketCount = 1;
while (bracketCount > 0 and instruction != nil) {
@ -81,7 +81,7 @@ class Interpreter {
}
}
if (instruction == "]" and tape.current.data >= 0.0001) {
if (instruction == "]" and tape.current.data != 0) {
instruction = read(this.bfpath, true);
var bracketCount = 1;
while (bracketCount > 0 and instruction != nil) {
@ -96,5 +96,6 @@ class Interpreter {
}
}
var interpreter = Interpreter("bf/helloworld.bf");
var bfScript = args(0);
var interpreter = Interpreter(bfScript);
interpreter.run();

2
lox/read_ascii.lox Normal file
View file

@ -0,0 +1,2 @@
var a = promptAscii("> ");
print a;

View file

@ -1,5 +1,6 @@
use crate::{expression::Expression, statement::Statement, token::Literal};
/// Generate a string representation of a list of AST statements.
pub fn print(statements: &[Statement]) -> String {
let mut result = String::new();
for statement in statements {
@ -8,6 +9,7 @@ pub fn print(statements: &[Statement]) -> String {
result
}
/// Recursively generate a string representation of a single AST statement.
fn print_statement(statement: &Statement, indent: usize) -> String {
let indent_str = " ".repeat(indent);
match statement {
@ -93,9 +95,45 @@ fn print_statement(statement: &Statement, indent: usize) -> String {
}
result.trim_end().to_string()
}
Statement::For {
initializer,
condition,
increment,
body,
} => {
let mut result = format!("{}For:\n", indent_str);
if let Some(init_stmt) = initializer {
result.push_str(&format!(
"{} Initializer:\n{}\n",
indent_str,
print_statement(init_stmt, indent + 2)
));
}
if let Some(cond_expr) = condition {
result.push_str(&format!(
"{} Condition: {}\n",
indent_str,
print_expression(cond_expr)
));
}
if let Some(inc_expr) = increment {
result.push_str(&format!(
"{} Increment: {}\n",
indent_str,
print_expression(inc_expr)
));
}
result.push_str(&format!(
"{} Body:\n{}",
indent_str,
print_statement(body, indent + 2)
));
result.trim_end().to_string()
}
}
}
/// Recursively generate a string representation of a single AST expression.
fn print_expression(expr: &Expression) -> String {
match expr {
Expression::Assign { name, value } => {

View file

@ -19,6 +19,9 @@ pub struct RunConfig {
/// Path to Lox source file.
#[arg(short, long)]
pub source: PathBuf,
/// Arguments to pass to the Lox script (must be after '--').
#[arg(last = true)]
pub script_args: Vec<String>,
}
/// Available commands for the Lox interpreter

View file

@ -60,12 +60,18 @@ pub struct Interpreter {
/// Default configuration for the interpreter, with builtin native functions.
impl Default for Interpreter {
fn default() -> Self {
Interpreter::new(Vec::new())
}
}
impl Interpreter {
pub fn new(cli_args: Vec<String>) -> Self {
let env: Rc<RefCell<Environment>> = Default::default();
{
// add all native functions to global environment
let mut env = env.borrow_mut();
for (name, fun) in native_functions::all() {
for (name, fun) in native_functions::all(cli_args) {
env.define(name, fun);
}
}
@ -76,9 +82,7 @@ impl Default for Interpreter {
locals: HashMap::new(),
}
}
}
impl Interpreter {
/// Execute a list of statements in sequence.
/// Log errors but continue execution.
pub fn run(&mut self, statements: Vec<Statement>) -> Result<(), InterpreterError> {
@ -107,16 +111,17 @@ impl Interpreter {
Statement::Print(expression) => self.print_statement(expression),
Statement::Expression(expression) => Ok(Some(self.evaluate(expression)?)),
Statement::Var { name, initializer } => {
self.var_statement(name, initializer.as_ref().as_ref())
self.var_statement(name, (**initializer).as_ref())
}
Statement::If {
condition,
then_branch,
else_branch,
} => {
let else_branch = else_branch.as_ref().map(|x| *x.clone());
self.if_statement(condition, then_branch, else_branch.as_ref())
}
} => self.if_statement(
condition,
then_branch,
else_branch.as_ref().map(|b| b.as_ref()),
),
Statement::While { condition, body } => self.while_statement(condition, body),
Statement::Function { name, params, body } => {
self.function_statement(name, params, body)
@ -127,6 +132,12 @@ impl Interpreter {
superclass,
methods,
} => self.class(name, superclass, methods),
Statement::For {
initializer,
condition,
increment,
body,
} => self.for_statement(initializer, condition, increment, body),
}
}
@ -449,6 +460,47 @@ impl Interpreter {
Ok(None)
}
/// Execute a for loop.
fn for_statement(
&mut self,
initializer: &Option<Box<Statement>>,
condition: &Option<Expression>,
increment: &Option<Expression>,
body: &Statement,
) -> Result<Option<Value>, InterpreterError> {
let previous_environment = self.environment.clone();
self.environment = Rc::new(RefCell::new(Environment::with_enclosing(
previous_environment.clone(),
)));
if let Some(init_stmt) = initializer {
self.execute(init_stmt)?;
}
loop {
let mut loop_condition_met = true;
if let Some(cond_expr) = condition {
loop_condition_met = self.evaluate(cond_expr)?.is_truthy();
}
if !loop_condition_met {
break;
}
if let Some(Value::Return(val)) = self.execute(body)? {
self.environment = previous_environment;
return Ok(Some(Value::Return(val)));
}
if let Some(inc_expr) = increment {
self.evaluate(inc_expr)?;
}
}
self.environment = previous_environment;
Ok(None)
}
/// Assign the value of an expression to a variable.
fn assign(&mut self, assign_expr: &Expression) -> Result<Value, InterpreterError> {
if let Expression::Assign { name, value } = assign_expr {
@ -468,7 +520,6 @@ impl Interpreter {
}
Ok(evaluated_value)
} else {
// This should ideally not be reached if called correctly from `evaluate`
unreachable!("Interpreter::assign called with a non-Assign expression");
}
}
@ -479,7 +530,7 @@ impl Interpreter {
}
/// Evaluate left and if the operator is Or and left is truthy or the operator is not Or and
/// not leftis truthy short circuit and return left. Otherwise evaluate and return right.
/// not left is truthy short circuit and return left. Otherwise evaluate and return right.
fn logical_expression(
&mut self,
left: &Expression,

View file

@ -53,9 +53,9 @@ pub enum RoxError {
}
/// Read the source code in a file and scan it to tokens.
pub fn compile(source: &Path) -> Result<(), io::Error> {
pub fn compile(source: &Path, cli_args: Vec<String>) -> Result<(), io::Error> {
let input = fs::read_to_string(source)?;
let mut interpreter = Interpreter::default();
let mut interpreter = Interpreter::new(cli_args);
run(&input, &mut interpreter);

View file

@ -23,7 +23,8 @@ fn main() {
exit(1);
}
if let Err(e) = rox::compile(&compile_config.source) {
if let Err(e) = rox::compile(&compile_config.source, compile_config.script_args.clone())
{
error!(
"failed to compile {}: {}",
&compile_config.source.to_string_lossy(),

View file

@ -187,7 +187,7 @@ impl Callable for AsciiOut {
struct PromptAscii;
impl Callable for PromptAscii {
fn name(&self) -> String {
"prompt_ascii".into()
"promptAscii".into()
}
fn arity(&self) -> usize {
@ -207,22 +207,64 @@ impl Callable for PromptAscii {
.first()
.ok_or(CallingError::CallFailed("arg not readable".into()))?;
if let Value::String(prompt) = prompt {
print!("{prompt} ");
stdout().flush();
print!("{prompt}");
stdout()
.flush()
.map_err(|e| CallingError::CallFailed(e.to_string()))?;
let mut buffer = [0; 1];
stdin().read_exact(&mut buffer);
stdin()
.read_exact(&mut buffer)
.map_err(|e| CallingError::CallFailed(e.to_string()))?;
todo!()
// Ok(Value::Number(buffer[0] as char))
Ok(Value::Number(OrderedFloat(buffer[0] as f64)))
} else {
Err(CallingError::CallFailed("prompt must be a string".into()))
}
}
}
struct CliArgs {
cli_args: Vec<String>,
}
impl Callable for CliArgs {
fn name(&self) -> String {
"args".into()
}
fn arity(&self) -> usize {
1
}
fn call(
&self,
_interpreter: &mut Interpreter,
args: Vec<Value>,
) -> Result<Value, CallingError> {
if args.len() != self.arity() {
return Err(CallingError::ArgumentMismatch(self.arity(), args.len()));
}
let idx = args
.first()
.ok_or(CallingError::CallFailed("arg not readable".into()))?;
if let Value::Number(idx) = idx {
let idx: usize = idx.0 as usize;
Ok(self
.cli_args
.get(idx)
.map(|x| Value::String(x.clone()))
.unwrap_or(Value::Nil))
} else {
Err(CallingError::CallFailed(
"arg index must be a number".into(),
))
}
}
}
/// Return all native functions available to the Lox interpreter
pub fn all() -> Vec<(String, Value)> {
pub fn all(cli_args: Vec<String>) -> Vec<(String, Value)> {
vec![
(
"asciiOut".into(),
@ -241,8 +283,12 @@ pub fn all() -> Vec<(String, Value)> {
Value::Callable((Rc::new(ReadFile {}), CallableType::Function)),
),
(
"prompt".into(),
"promptAscii".into(),
Value::Callable((Rc::new(PromptAscii {}), CallableType::Function)),
),
(
"args".into(),
Value::Callable((Rc::new(CliArgs { cli_args }), CallableType::Function)),
),
]
}

View file

@ -2,7 +2,7 @@ use crate::{
expression::Expression,
statement::Statement,
token::{
self, Literal, Token,
self, Token,
TokenType::{self, *},
},
};
@ -221,7 +221,7 @@ impl Parser {
}
}
/// Parse a for statement by desugaring it into a while loop.
/// Parse a for statement.
fn for_statement(&mut self) -> Result<Statement, ParserError> {
let line = self.current_token.line;
self.consume(&LeftParen)
@ -230,20 +230,19 @@ impl Parser {
let initializer = if self.matches(&[Semicolon]) {
None
} else if self.matches(&[Var]) {
Some(self.var_declaration()?)
Some(Box::new(self.var_declaration()?))
} else {
Some(self.expression_statement()?)
Some(Box::new(self.expression_statement()?))
};
let condition = if !self.matches(&[Semicolon]) {
self.expression()?
let condition = if self.matches(&[Semicolon]) {
None // No condition means it defaults to true later
} else {
Expression::Literal {
value: Literal::Boolean(true),
}
let cond_expr = self.expression()?;
self.consume(&Semicolon)
.ok_or(ParserError::SemicolonAfterLoopConditionExpected(line))?;
Some(cond_expr)
};
self.consume(&Semicolon)
.ok_or(ParserError::SemicolonAfterLoopConditionExpected(line))?;
let increment = if !self.check(&RightParen) {
Some(self.expression()?)
@ -253,21 +252,14 @@ impl Parser {
self.consume(&RightParen)
.ok_or(ParserError::RightParenAfterForClausesExpected(line))?;
let body = self.statement()?;
let body = match increment {
Some(inc) => Statement::Block(vec![body, Statement::Expression(inc)]),
None => body,
};
let body = Statement::While {
condition,
body: Box::new(body),
};
let body = match initializer {
Some(initializer) => Statement::Block(vec![initializer, body]),
None => body,
};
let body = Box::new(self.statement()?);
Ok(body)
Ok(Statement::For {
initializer,
condition,
increment,
body,
})
}
/// Parse an if statement with a condition, then branch, and optional else branch.
@ -676,7 +668,6 @@ impl Parser {
}
/// Synchronize the parser after an error by advancing to the next statement boundary.
/// This allows parsing to continue after encountering a syntax error.
fn synchronize(&mut self) {
let _ = self.advance();
while !self.is_at_end()

View file

@ -40,9 +40,13 @@ enum ClassType {
/// Resolve variable references and perform static analysis before interpretation.
#[derive(Debug)]
pub struct Resolver<'a> {
/// Stack of scopes, each mapping variable names to their initialized state.
scopes: Vec<HashMap<String, bool>>,
/// Reference to the interpreter to resolve variable depths.
interpreter: &'a mut Interpreter,
/// Track the type of the current function being resolved (regular, method, initializer, ...).
current_fun: FunctionType,
/// Track the type of the current class being resolved (regular, subclass, ...).
current_class: ClassType,
}
@ -71,7 +75,9 @@ impl<'a> Resolver<'a> {
Statement::Block(statements) => self.resolve_block(statements),
Statement::Print(expression) => self.resolve_expression(expression),
Statement::Expression(expression) => self.resolve_expression(expression),
Statement::Var { name, initializer } => self.resolve_var(name, initializer.as_ref().as_ref()),
Statement::Var { name, initializer } => {
self.resolve_var(name, (**initializer).as_ref())
}
Statement::If {
condition,
then_branch,
@ -79,7 +85,7 @@ impl<'a> Resolver<'a> {
} => self.resolve_if_stmt(
condition,
then_branch,
else_branch.as_ref().map(|boxed| *boxed.clone()),
else_branch.as_ref().map(|b| b.as_ref()),
),
Statement::While { condition, body } => self.resolve_while_stmt(condition, body),
Statement::Function { name, params, body } => {
@ -91,6 +97,12 @@ impl<'a> Resolver<'a> {
superclass,
methods,
} => self.resolve_class_stmt(name, superclass, methods),
Statement::For {
initializer,
condition,
increment,
body,
} => self.resolve_for_statement(initializer, condition, increment, body),
}
}
@ -374,13 +386,13 @@ impl<'a> Resolver<'a> {
&mut self,
condition: &Expression,
then_branch: &Statement,
else_branch: Option<Statement>,
else_branch: Option<&Statement>,
) -> Result<(), ResolverError> {
self.resolve_expression(condition)?;
self.resolve_statement(then_branch)?;
if let Some(stmt) = else_branch {
self.resolve_statement(&stmt)?;
if let Some(stmt_ref) = else_branch {
self.resolve_statement(stmt_ref)?;
}
Ok(())
@ -398,6 +410,35 @@ impl<'a> Resolver<'a> {
Ok(())
}
/// Resolve a for statement.
/// This creates a new scope for the entire for construct.
fn resolve_for_statement(
&mut self,
initializer: &Option<Box<Statement>>,
condition: &Option<Expression>,
increment: &Option<Expression>,
body: &Statement,
) -> Result<(), ResolverError> {
self.begin_scope();
if let Some(init_stmt) = initializer {
self.resolve_statement(init_stmt)?;
}
if let Some(cond_expr) = condition {
self.resolve_expression(cond_expr)?;
}
self.resolve_statement(body)?;
if let Some(inc_expr) = increment {
self.resolve_expression(inc_expr)?;
}
self.end_scope();
Ok(())
}
/// Resolve a function definition with parameters and body.
/// Create a new scope for the function body and declare all parameters.
fn resolve_function(

View file

@ -33,4 +33,10 @@ pub enum Statement {
superclass: Option<Expression>,
methods: Vec<Statement>,
},
For {
initializer: Option<Box<Statement>>,
condition: Option<Expression>,
increment: Option<Expression>,
body: Box<Statement>,
},
}