Compare commits
3 commits
6e02a1a644
...
9aa6bef6d9
Author | SHA1 | Date | |
---|---|---|---|
9aa6bef6d9 | |||
94b57f8304 | |||
463e4abd30 |
15 changed files with 229 additions and 70 deletions
30
.vscode/launch.json
vendored
Normal file
30
.vscode/launch.json
vendored
Normal 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
6
bf/fibonacci.bf
Normal file
|
@ -0,0 +1,6 @@
|
|||
>++++++++++>+>+[
|
||||
[+++++[>++++++++<-]>.<++++++[>--------<-]+<<<]>.>>[
|
||||
[-]<[>+<-]>>[<<+>+>-]<[>+<-[>+<-[>+<-[>+<-[>+<-[>+<-
|
||||
[>+<-[>+<-[>+<-[>[-]>+>+<<<-[>+<-]]]]]]]]]]]+>>>
|
||||
]<<<
|
||||
]
|
1
bf/helloworld.bf
Normal file
1
bf/helloworld.bf
Normal file
|
@ -0,0 +1 @@
|
|||
++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
|
1
bf/simple_loop.bf
Normal file
1
bf/simple_loop.bf
Normal file
|
@ -0,0 +1 @@
|
|||
++++++[>++++++++<-]>. // prints '0'
|
|
@ -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);
|
||||
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) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
var a = 0;
|
||||
var temp;
|
||||
|
||||
var b = 1;
|
||||
for(var b = 1; a < 1000; b = temp + b) {
|
||||
print a;
|
||||
temp = a;
|
||||
|
|
14
lox/nested_while.lox
Normal file
14
lox/nested_while.lox
Normal 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
8
lox/scope.lox
Normal file
|
@ -0,0 +1,8 @@
|
|||
var a = "outer";
|
||||
|
||||
{
|
||||
var a = "inner";
|
||||
print a; // expect: inner
|
||||
}
|
||||
|
||||
print a;
|
3
lox/simple_for.lox
Normal file
3
lox/simple_for.lox
Normal file
|
@ -0,0 +1,3 @@
|
|||
for (var i = 0; i < 10; i = i + 1) {
|
||||
print i;
|
||||
}
|
5
lox/simple_while.lox
Normal file
5
lox/simple_while.lox
Normal file
|
@ -0,0 +1,5 @@
|
|||
var i = 0;
|
||||
while (i < 10) {
|
||||
print i;
|
||||
i = i + 1;
|
||||
}
|
|
@ -55,15 +55,24 @@ impl Environment {
|
|||
}
|
||||
}
|
||||
|
||||
/// Assign a value to a variable at a specific lexical distance (number of scopes away).
|
||||
/// Used by the resolver to handle closures correctly.
|
||||
/// Assign a value to a variable in an environment at a specific lexical distance.
|
||||
pub fn assign_at(
|
||||
&mut self,
|
||||
env: Rc<RefCell<Environment>>,
|
||||
distance: usize,
|
||||
token: &Token,
|
||||
value: Value,
|
||||
) -> 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
|
||||
.insert(token.lexeme.clone(), value);
|
||||
Ok(())
|
||||
|
@ -85,9 +94,22 @@ impl Environment {
|
|||
}
|
||||
|
||||
/// 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(&self, distance: usize, token: &Token) -> Result<Value, EnvironmentError> {
|
||||
if let Some(v) = self.ancestor(distance)?.values.get(token.lexeme.as_str()) {
|
||||
pub fn get_at(
|
||||
env: Rc<RefCell<Environment>>,
|
||||
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())
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,9 +52,9 @@ pub struct Interpreter {
|
|||
/// Global environment containing built-in functions and top-level variables
|
||||
pub globals: Rc<RefCell<Environment>>,
|
||||
/// 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
|
||||
locals: HashMap<Expression, usize>,
|
||||
pub locals: HashMap<Expression, usize>,
|
||||
}
|
||||
|
||||
/// Default configuration for the interpreter, with builtin native functions.
|
||||
|
@ -242,7 +242,7 @@ impl Interpreter {
|
|||
right,
|
||||
} => self.binary(left, operator, right),
|
||||
Expression::Variable { name } => self.var_expression(name, expression),
|
||||
Expression::Assign { name, value } => self.assign(name, value),
|
||||
Expression::Assign { .. } => self.assign(expression),
|
||||
Expression::Logical {
|
||||
left,
|
||||
operator,
|
||||
|
@ -323,8 +323,9 @@ impl Interpreter {
|
|||
method: &Token,
|
||||
) -> Result<Value, InterpreterError> {
|
||||
if let Some(distance) = self.locals.get(expression) {
|
||||
let superclass = self.environment.borrow().get_at(*distance, keyword)?;
|
||||
let object = self.environment.borrow().get_at(
|
||||
let superclass = Environment::get_at(self.environment.clone(), *distance, keyword)?;
|
||||
let object = Environment::get_at(
|
||||
self.environment.clone(),
|
||||
*distance - 1,
|
||||
&Token {
|
||||
token_type: TokenType::This,
|
||||
|
@ -449,27 +450,27 @@ impl Interpreter {
|
|||
}
|
||||
|
||||
/// Assign the value of an expression to a variable.
|
||||
fn assign(&mut self, name: &Token, expression: &Expression) -> Result<Value, InterpreterError> {
|
||||
let value = self.evaluate(expression)?;
|
||||
fn assign(&mut self, assign_expr: &Expression) -> Result<Value, InterpreterError> {
|
||||
if let Expression::Assign { name, value } = assign_expr {
|
||||
let evaluated_value = self.evaluate(value)?;
|
||||
|
||||
self.environment
|
||||
.borrow_mut()
|
||||
.assign(name, value.clone())
|
||||
.map_err(InterpreterError::UndefinedVariable)?;
|
||||
|
||||
if let Some(distance) = self.locals.get(expression) {
|
||||
self.environment
|
||||
.borrow_mut()
|
||||
.assign_at(*distance, name, value.clone())
|
||||
.map_err(InterpreterError::UndefinedVariable)?;
|
||||
if let Some(distance) = self.locals.get(assign_expr) {
|
||||
Environment::assign_at(
|
||||
self.environment.clone(),
|
||||
*distance,
|
||||
name,
|
||||
evaluated_value.clone(),
|
||||
)?;
|
||||
} else {
|
||||
self.globals
|
||||
.borrow_mut()
|
||||
.assign(name, evaluated_value.clone())?;
|
||||
}
|
||||
Ok(evaluated_value)
|
||||
} else {
|
||||
self.globals
|
||||
.borrow_mut()
|
||||
.assign(name, value.clone())
|
||||
.map_err(InterpreterError::UndefinedVariable)?;
|
||||
// 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.
|
||||
|
@ -542,15 +543,13 @@ impl Interpreter {
|
|||
expression: &Expression,
|
||||
) -> Result<Value, InterpreterError> {
|
||||
if let Some(distance) = self.locals.get(expression) {
|
||||
self.environment
|
||||
.borrow()
|
||||
.get_at(*distance, name)
|
||||
.map_err(InterpreterError::UndefinedVariable)
|
||||
Ok(Environment::get_at(
|
||||
self.environment.clone(),
|
||||
*distance,
|
||||
name,
|
||||
)?)
|
||||
} else {
|
||||
self.globals
|
||||
.borrow()
|
||||
.get(name)
|
||||
.map_err(InterpreterError::UndefinedVariable)
|
||||
Ok(self.globals.borrow().get(name)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use ordered_float::OrderedFloat;
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
fs::File,
|
||||
io::{BufReader, ErrorKind, Read},
|
||||
io::{stdin, stdout, BufReader, ErrorKind, Read, Seek, SeekFrom, Write},
|
||||
rc::Rc,
|
||||
sync::RwLock,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
|
@ -53,7 +53,7 @@ impl Callable for ReadFile {
|
|||
}
|
||||
|
||||
fn arity(&self) -> usize {
|
||||
1
|
||||
2
|
||||
}
|
||||
|
||||
fn call(
|
||||
|
@ -61,18 +61,24 @@ impl Callable for ReadFile {
|
|||
_interpreter: &mut Interpreter,
|
||||
args: Vec<Value>,
|
||||
) -> Result<Value, CallingError> {
|
||||
if args.len() != self.arity() {
|
||||
return Err(CallingError::ArgumentMismatch(self.arity(), args.len()));
|
||||
}
|
||||
|
||||
let file_path = args
|
||||
.first()
|
||||
.ok_or(CallingError::CallFailed("arg not readable".into()))?;
|
||||
let file_path = args.first().ok_or(CallingError::CallFailed(
|
||||
"first arg must be file path".into(),
|
||||
))?;
|
||||
if let Value::String(file_path) = file_path {
|
||||
self.ensure_file_handle(file_path)?;
|
||||
let mut file_handles = FILE_HANDLES.write().unwrap();
|
||||
let reader = file_handles.get_mut(file_path).unwrap();
|
||||
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) {
|
||||
Ok(_) => {
|
||||
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
|
||||
pub fn all() -> Vec<(String, Value)> {
|
||||
vec![
|
||||
(
|
||||
"asciiOut".into(),
|
||||
Value::Callable((Rc::new(AsciiOut {}), CallableType::Function)),
|
||||
),
|
||||
(
|
||||
"clock".into(),
|
||||
Value::Callable((Rc::new(Clock {}), CallableType::Function)),
|
||||
|
@ -158,5 +240,9 @@ pub fn all() -> Vec<(String, Value)> {
|
|||
"read".into(),
|
||||
Value::Callable((Rc::new(ReadFile {}), CallableType::Function)),
|
||||
),
|
||||
(
|
||||
"prompt".into(),
|
||||
Value::Callable((Rc::new(PromptAscii {}), CallableType::Function)),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ 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.clone()),
|
||||
Statement::Var { name, initializer } => self.resolve_var(name, initializer.as_ref().as_ref()),
|
||||
Statement::If {
|
||||
condition,
|
||||
then_branch,
|
||||
|
@ -262,12 +262,12 @@ impl<'a> Resolver<'a> {
|
|||
fn resolve_var(
|
||||
&mut self,
|
||||
name: &Token,
|
||||
initializer: Option<Expression>,
|
||||
initializer: Option<&Expression>,
|
||||
) -> Result<(), ResolverError> {
|
||||
self.declare(name)?;
|
||||
|
||||
if let Some(initializer) = initializer {
|
||||
self.resolve_expression(&initializer)?;
|
||||
if let Some(init_expr_ref) = initializer {
|
||||
self.resolve_expression(init_expr_ref)?;
|
||||
}
|
||||
|
||||
self.define(name);
|
||||
|
|
|
@ -4,7 +4,7 @@ use ordered_float::OrderedFloat;
|
|||
|
||||
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)]
|
||||
pub enum CallableType {
|
||||
/// A regular function or method
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue