Compare commits

..

3 commits

Author SHA1 Message Date
9aa6bef6d9
update bf interpreter 2025-05-28 09:40:35 +02:00
94b57f8304
fix resolving of vars 2025-05-28 09:21:59 +02:00
463e4abd30
wip 2025-05-28 09:05:18 +02:00
15 changed files with 229 additions and 70 deletions

30
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,30 @@
{
"$schema": "https://raw.githubusercontent.com/mfussenegger/dapconfig-schema/master/dapconfig-schema.json",
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Run Lox Interpreter",
"cargo": {
"args": [
"build",
"--package",
"rox",
"--message-format",
"json"
]
},
"program": "${workspaceFolder}/target/debug/rox",
"args": [
"run",
"-s",
"./lox/simple_for.lox"
],
"env": {
"RUST_LOG": "debug"
},
"cwd": "${workspaceFolder}"
}
]
}

6
bf/fibonacci.bf Normal file
View file

@ -0,0 +1,6 @@
>++++++++++>+>+[
[+++++[>++++++++<-]>.<++++++[>--------<-]+<<<]>.>>[
[-]<[>+<-]>>[<<+>+>-]<[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-
[>+<-[>+<-[>+<-[>[-]>+>+<<<-[>+<-]]]]]]]]]]]+>>>
]<<<
]

1
bf/helloworld.bf Normal file
View file

@ -0,0 +1 @@
++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.

1
bf/simple_loop.bf Normal file
View file

@ -0,0 +1 @@
++++++[>++++++++<-]>. // prints '0'

View file

@ -71,7 +71,7 @@ class Interpreter {
} }
if (instruction == "[" and tape.current.data < 0.0001) { if (instruction == "[" and tape.current.data = 0) {
instruction = read(this.bfpath); instruction = read(this.bfpath);
var bracketCount = 1; var bracketCount = 1;
while (bracketCount > 0 and instruction != nil) { 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); instruction = read(this.bfpath, true);
var bracketCount = 1; var bracketCount = 1;
while (bracketCount > 0 and instruction != nil) { while (bracketCount > 0 and instruction != nil) {

View file

@ -1,7 +1,6 @@
var a = 0; var a = 0;
var temp; var temp;
var b = 1;
for(var b = 1; a < 1000; b = temp + b) { for(var b = 1; a < 1000; b = temp + b) {
print a; print a;
temp = a; temp = a;

14
lox/nested_while.lox Normal file
View file

@ -0,0 +1,14 @@
{
var i = 0;
var j = 0;
while (i < 10) {
print i;
while (j < 100) {
print j;
j = j + 1;
}
i = i + 1;
}
}

8
lox/scope.lox Normal file
View file

@ -0,0 +1,8 @@
var a = "outer";
{
var a = "inner";
print a; // expect: inner
}
print a;

3
lox/simple_for.lox Normal file
View file

@ -0,0 +1,3 @@
for (var i = 0; i < 10; i = i + 1) {
print i;
}

5
lox/simple_while.lox Normal file
View file

@ -0,0 +1,5 @@
var i = 0;
while (i < 10) {
print i;
i = i + 1;
}

View file

@ -55,15 +55,24 @@ impl Environment {
} }
} }
/// Assign a value to a variable at a specific lexical distance (number of scopes away). /// Assign a value to a variable in an environment at a specific lexical distance.
/// Used by the resolver to handle closures correctly.
pub fn assign_at( pub fn assign_at(
&mut self, env: Rc<RefCell<Environment>>,
distance: usize, distance: usize,
token: &Token, token: &Token,
value: Value, value: Value,
) -> Result<(), EnvironmentError> { ) -> Result<(), EnvironmentError> {
self.ancestor(distance)? let mut current_env = env;
for _ in 0..distance {
let enclosing = current_env
.borrow()
.enclosing
.clone()
.ok_or(EnvironmentError::InvalidDistance)?;
current_env = enclosing;
}
current_env
.borrow_mut()
.values .values
.insert(token.lexeme.clone(), value); .insert(token.lexeme.clone(), value);
Ok(()) Ok(())
@ -85,9 +94,22 @@ impl Environment {
} }
/// Get a variable's value from an environment at a specific lexical distance. /// Get a variable's value from an environment at a specific lexical distance.
/// Used by the resolver to handle closures correctly. pub fn get_at(
pub fn get_at(&self, distance: usize, token: &Token) -> Result<Value, EnvironmentError> { env: Rc<RefCell<Environment>>,
if let Some(v) = self.ancestor(distance)?.values.get(token.lexeme.as_str()) { distance: usize,
token: &Token,
) -> Result<Value, EnvironmentError> {
let mut current_env_rc = env;
for _ in 0..distance {
let enclosing = current_env_rc
.borrow()
.enclosing
.clone()
.ok_or(EnvironmentError::InvalidDistance)?;
current_env_rc = enclosing;
}
let borrowed_env = current_env_rc.borrow();
if let Some(v) = borrowed_env.values.get(token.lexeme.as_str()) {
Ok(v.clone()) Ok(v.clone())
} else { } else {
Err(EnvironmentError::UndefinedVariable( Err(EnvironmentError::UndefinedVariable(
@ -96,19 +118,4 @@ impl Environment {
)) ))
} }
} }
/// Find an environment at a specific lexical distance from the current one.
fn ancestor(&self, distance: usize) -> Result<Environment, EnvironmentError> {
let mut environment = self.clone();
for _ in 0..distance {
let enclosing = match &environment.enclosing {
Some(enc) => enc.borrow().clone(),
None => return Err(EnvironmentError::InvalidDistance),
};
environment = enclosing;
}
Ok(environment)
}
} }

View file

@ -52,9 +52,9 @@ pub struct Interpreter {
/// Global environment containing built-in functions and top-level variables /// Global environment containing built-in functions and top-level variables
pub globals: Rc<RefCell<Environment>>, pub globals: Rc<RefCell<Environment>>,
/// Current environment for variable lookups and assignments /// Current environment for variable lookups and assignments
environment: Rc<RefCell<Environment>>, pub environment: Rc<RefCell<Environment>>,
/// Map of expressions to their lexical distance for variable resolution /// Map of expressions to their lexical distance for variable resolution
locals: HashMap<Expression, usize>, pub locals: HashMap<Expression, usize>,
} }
/// Default configuration for the interpreter, with builtin native functions. /// Default configuration for the interpreter, with builtin native functions.
@ -242,7 +242,7 @@ impl Interpreter {
right, right,
} => self.binary(left, operator, right), } => self.binary(left, operator, right),
Expression::Variable { name } => self.var_expression(name, expression), Expression::Variable { name } => self.var_expression(name, expression),
Expression::Assign { name, value } => self.assign(name, value), Expression::Assign { .. } => self.assign(expression),
Expression::Logical { Expression::Logical {
left, left,
operator, operator,
@ -323,8 +323,9 @@ impl Interpreter {
method: &Token, method: &Token,
) -> Result<Value, InterpreterError> { ) -> Result<Value, InterpreterError> {
if let Some(distance) = self.locals.get(expression) { if let Some(distance) = self.locals.get(expression) {
let superclass = self.environment.borrow().get_at(*distance, keyword)?; let superclass = Environment::get_at(self.environment.clone(), *distance, keyword)?;
let object = self.environment.borrow().get_at( let object = Environment::get_at(
self.environment.clone(),
*distance - 1, *distance - 1,
&Token { &Token {
token_type: TokenType::This, token_type: TokenType::This,
@ -449,27 +450,27 @@ impl Interpreter {
} }
/// Assign the value of an expression to a variable. /// Assign the value of an expression to a variable.
fn assign(&mut self, name: &Token, expression: &Expression) -> Result<Value, InterpreterError> { fn assign(&mut self, assign_expr: &Expression) -> Result<Value, InterpreterError> {
let value = self.evaluate(expression)?; if let Expression::Assign { name, value } = assign_expr {
let evaluated_value = self.evaluate(value)?;
self.environment if let Some(distance) = self.locals.get(assign_expr) {
.borrow_mut() Environment::assign_at(
.assign(name, value.clone()) self.environment.clone(),
.map_err(InterpreterError::UndefinedVariable)?; *distance,
name,
if let Some(distance) = self.locals.get(expression) { evaluated_value.clone(),
self.environment )?;
.borrow_mut()
.assign_at(*distance, name, value.clone())
.map_err(InterpreterError::UndefinedVariable)?;
} else { } else {
self.globals self.globals
.borrow_mut() .borrow_mut()
.assign(name, value.clone()) .assign(name, evaluated_value.clone())?;
.map_err(InterpreterError::UndefinedVariable)?; }
Ok(evaluated_value)
} else {
// This should ideally not be reached if called correctly from `evaluate`
unreachable!("Interpreter::assign called with a non-Assign expression");
} }
Ok(value)
} }
/// Convert the literal value into a Value. /// Convert the literal value into a Value.
@ -542,15 +543,13 @@ impl Interpreter {
expression: &Expression, expression: &Expression,
) -> Result<Value, InterpreterError> { ) -> Result<Value, InterpreterError> {
if let Some(distance) = self.locals.get(expression) { if let Some(distance) = self.locals.get(expression) {
self.environment Ok(Environment::get_at(
.borrow() self.environment.clone(),
.get_at(*distance, name) *distance,
.map_err(InterpreterError::UndefinedVariable) name,
)?)
} else { } else {
self.globals Ok(self.globals.borrow().get(name)?)
.borrow()
.get(name)
.map_err(InterpreterError::UndefinedVariable)
} }
} }

View file

@ -3,7 +3,7 @@ use ordered_float::OrderedFloat;
use std::{ use std::{
collections::HashMap, collections::HashMap,
fs::File, fs::File,
io::{BufReader, ErrorKind, Read}, io::{stdin, stdout, BufReader, ErrorKind, Read, Seek, SeekFrom, Write},
rc::Rc, rc::Rc,
sync::RwLock, sync::RwLock,
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
@ -53,7 +53,7 @@ impl Callable for ReadFile {
} }
fn arity(&self) -> usize { fn arity(&self) -> usize {
1 2
} }
fn call( fn call(
@ -61,18 +61,24 @@ impl Callable for ReadFile {
_interpreter: &mut Interpreter, _interpreter: &mut Interpreter,
args: Vec<Value>, args: Vec<Value>,
) -> Result<Value, CallingError> { ) -> Result<Value, CallingError> {
if args.len() != self.arity() { let file_path = args.first().ok_or(CallingError::CallFailed(
return Err(CallingError::ArgumentMismatch(self.arity(), args.len())); "first arg must be file path".into(),
} ))?;
let file_path = args
.first()
.ok_or(CallingError::CallFailed("arg not readable".into()))?;
if let Value::String(file_path) = file_path { if let Value::String(file_path) = file_path {
self.ensure_file_handle(file_path)?; self.ensure_file_handle(file_path)?;
let mut file_handles = FILE_HANDLES.write().unwrap(); let mut file_handles = FILE_HANDLES.write().unwrap();
let reader = file_handles.get_mut(file_path).unwrap(); let reader = file_handles.get_mut(file_path).unwrap();
let mut buffer = [0; 1]; let mut buffer = [0; 1];
let backwards = args.get(1).unwrap_or(&Value::Boolean(false));
if let Value::Boolean(backwards) = backwards {
if *backwards {
reader
.seek(SeekFrom::Current(-2))
.map_err(|e| CallingError::CallFailed(e.to_string()))?;
}
}
let character = match reader.read_exact(&mut buffer) { let character = match reader.read_exact(&mut buffer) {
Ok(_) => { Ok(_) => {
let character = buffer[0] as char; let character = buffer[0] as char;
@ -143,9 +149,85 @@ impl Callable for Out {
} }
} }
struct AsciiOut;
impl Callable for AsciiOut {
fn name(&self) -> String {
"asciiOut".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 value = args
.first()
.ok_or(CallingError::CallFailed("arg not readable".into()))?;
if let Value::Number(value) = value {
let ascii = value.0 as u8 as char;
print!("{ascii}");
Ok(Value::Nil)
} else {
Err(CallingError::CallFailed(
"file_path arg must be a number".into(),
))
}
}
}
struct PromptAscii;
impl Callable for PromptAscii {
fn name(&self) -> String {
"prompt_ascii".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 prompt = args
.first()
.ok_or(CallingError::CallFailed("arg not readable".into()))?;
if let Value::String(prompt) = prompt {
print!("{prompt} ");
stdout().flush();
let mut buffer = [0; 1];
stdin().read_exact(&mut buffer);
todo!()
// Ok(Value::Number(buffer[0] as char))
} else {
Err(CallingError::CallFailed("prompt must be a string".into()))
}
}
}
/// Return all native functions available to the Lox interpreter /// Return all native functions available to the Lox interpreter
pub fn all() -> Vec<(String, Value)> { pub fn all() -> Vec<(String, Value)> {
vec![ vec![
(
"asciiOut".into(),
Value::Callable((Rc::new(AsciiOut {}), CallableType::Function)),
),
( (
"clock".into(), "clock".into(),
Value::Callable((Rc::new(Clock {}), CallableType::Function)), Value::Callable((Rc::new(Clock {}), CallableType::Function)),
@ -158,5 +240,9 @@ pub fn all() -> Vec<(String, Value)> {
"read".into(), "read".into(),
Value::Callable((Rc::new(ReadFile {}), CallableType::Function)), Value::Callable((Rc::new(ReadFile {}), CallableType::Function)),
), ),
(
"prompt".into(),
Value::Callable((Rc::new(PromptAscii {}), CallableType::Function)),
),
] ]
} }

View file

@ -71,7 +71,7 @@ impl<'a> Resolver<'a> {
Statement::Block(statements) => self.resolve_block(statements), Statement::Block(statements) => self.resolve_block(statements),
Statement::Print(expression) => self.resolve_expression(expression), Statement::Print(expression) => self.resolve_expression(expression),
Statement::Expression(expression) => self.resolve_expression(expression), Statement::Expression(expression) => self.resolve_expression(expression),
Statement::Var { name, initializer } => self.resolve_var(name, *initializer.clone()), Statement::Var { name, initializer } => self.resolve_var(name, initializer.as_ref().as_ref()),
Statement::If { Statement::If {
condition, condition,
then_branch, then_branch,
@ -262,12 +262,12 @@ impl<'a> Resolver<'a> {
fn resolve_var( fn resolve_var(
&mut self, &mut self,
name: &Token, name: &Token,
initializer: Option<Expression>, initializer: Option<&Expression>,
) -> Result<(), ResolverError> { ) -> Result<(), ResolverError> {
self.declare(name)?; self.declare(name)?;
if let Some(initializer) = initializer { if let Some(init_expr_ref) = initializer {
self.resolve_expression(&initializer)?; self.resolve_expression(init_expr_ref)?;
} }
self.define(name); self.define(name);

View file

@ -4,7 +4,7 @@ use ordered_float::OrderedFloat;
use crate::{callable::Callable, instance, token::Literal}; use crate::{callable::Callable, instance, token::Literal};
/// Represents the type of a callable value in Lox. /// Represent the type of a callable value in Lox.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum CallableType { pub enum CallableType {
/// A regular function or method /// A regular function or method