Compare commits

..

6 Commits

Author SHA1 Message Date
2167b524ec test closures! 2025-04-19 15:24:55 +03:00
af28442913 Use a helper function 2025-04-19 15:22:19 +03:00
a7ba99b3a8 Add helper functions 2025-04-19 15:20:57 +03:00
b131d40702 Add table expression constructor 2025-04-19 15:13:48 +03:00
ead7ac054d add table expressions 2025-04-19 15:12:42 +03:00
5138ada816 remove obsolete code 2025-04-19 15:07:07 +03:00
5 changed files with 134 additions and 368 deletions

View File

@ -30,6 +30,7 @@ pub enum Expression {
Variable(Location), Variable(Location),
Call(Call), Call(Call),
Function(Function), Function(Function),
Table(Table),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -38,6 +39,17 @@ pub struct Call {
pub args: Vec<Expression>, pub args: Vec<Expression>,
} }
#[derive(Debug, Clone, Default)]
pub struct Table {
pub fields: Vec<Field>,
}
#[derive(Debug, Clone)]
pub struct Field {
pub name: Expression,
pub init: Expression,
}
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Function { pub struct Function {
pub name: Option<Ident>, pub name: Option<Ident>,
@ -53,7 +65,6 @@ pub enum Constant {
Boolean(bool), Boolean(bool),
Number(i64), Number(i64),
String(String), String(String),
EmptyTable,
} }
impl Location { impl Location {
@ -77,6 +88,17 @@ impl Expression {
pub fn new_variable(name: impl Into<String>) -> Self { pub fn new_variable(name: impl Into<String>) -> Self {
Self::Variable(Location::new_variable(name)) Self::Variable(Location::new_variable(name))
} }
pub fn new_table<'a>(fields: impl Iterator<Item = (&'a str, Expression)>) -> Self {
Self::Table(Table {
fields: fields
.map(|(key, value)| Field {
name: Expression::new_constant(key),
init: value,
})
.collect(),
})
}
} }
impl From<()> for Constant { impl From<()> for Constant {

View File

@ -1,5 +1,3 @@
pub mod ast; pub mod ast;
pub mod run;
pub mod scope;
pub mod twopass; pub mod twopass;
pub mod types; pub mod types;

View File

@ -1,297 +0,0 @@
use std::{collections::HashMap, fmt::Debug, rc::Rc};
use crate::{
ast::{Call, Constant, Expression, Location, Statement},
types::{Value, ValueInner},
};
#[derive(Clone)]
struct Function {
name: String,
inner: Rc<dyn Fn(&mut Scope, Vec<Value>) -> Value>,
}
impl Debug for Function {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<function {}>", self.name)
}
}
#[derive(Debug, Clone, Default)]
pub struct Scope {
variables: HashMap<String, ValueInner>,
functions: HashMap<String, Function>,
}
impl Scope {
pub fn get(&self, name: impl AsRef<str>) -> Value {
self.variables.get(name.as_ref()).cloned()
}
pub fn rawset(&mut self, name: String, value: Value) {
if let Some(value) = value {
self.variables.insert(name, value);
} else {
self.variables.remove(&name);
}
}
pub fn set(&mut self, name: impl AsRef<str>, value: &(impl Clone + Into<ValueInner>)) {
self.variables
.insert(name.as_ref().to_string(), value.clone().into());
}
pub fn add_fn(
&mut self,
name: impl Into<String>,
f: impl Fn(&mut Scope, Vec<Value>) -> Value + 'static,
) {
let name = name.into();
self.functions.insert(
name.clone(),
Function {
name,
inner: Rc::new(f),
},
);
}
}
pub trait Eval {
fn eval(&self, scope: &mut Scope) -> Value;
}
impl Eval for Constant {
fn eval(&self, _scope: &mut Scope) -> Value {
match self {
Constant::Nil => None,
Constant::Boolean(value) => Some(ValueInner::Boolean(*value)),
Constant::Number(value) => Some(ValueInner::Number(*value)),
Constant::String(value) => Some(ValueInner::String(value.clone())),
Constant::EmptyTable => Some(ValueInner::Table(Default::default())),
}
}
}
impl Eval for Location {
fn eval(&self, scope: &mut Scope) -> Value {
match self {
Location::Variable { name } => scope.get(name),
Location::Field { table, index } => {
let table = table.eval(scope);
let Some(ValueInner::Table(table)) = table else {
panic!("attempt to index non-table value {table:?}")
};
let index = index.eval(scope);
let Some(index) = index else {
panic!("attempt to index with a nil value")
};
table.get(index)
}
}
}
}
impl Eval for Call {
fn eval(&self, scope: &mut Scope) -> Value {
let Self { callee, args } = self;
let Some(function) = scope.functions.get(callee.as_str()).cloned() else {
panic!("attempt to call non-existent function {callee}")
};
let args = args.iter().map(|arg| arg.eval(scope)).collect();
(function.inner)(scope, args)
}
}
impl Eval for Expression {
fn eval(&self, scope: &mut Scope) -> Value {
match self {
Expression::Constant(inner) => inner.eval(scope),
Expression::Variable(inner) => inner.eval(scope),
Expression::Call(inner) => inner.eval(scope),
Expression::Function { .. } => todo!(),
}
}
}
pub trait Exec {
fn exec(&self, scope: &mut Scope);
}
impl Exec for Statement {
fn exec(&self, scope: &mut Scope) {
match self {
Statement::Assign { target, source } => {
let value = source.eval(scope);
match target {
Location::Variable { name } => {
scope.rawset(name.into(), value);
}
Location::Field { table, index } => {
let table = table.eval(scope);
let Some(ValueInner::Table(table)) = table else {
panic!("attempt to index non-table value {table:?}")
};
let index = index.eval(scope);
let Some(index) = index else {
panic!("attempt to index with a nil value")
};
table.set(index, value);
}
};
}
Statement::Call(call) => {
call.eval(scope);
}
Statement::Local { .. } => todo!(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ast, types};
#[test]
fn test_consts() {
let mut scope = Scope::default();
assert_eq!(ast::Constant::Nil.eval(&mut scope), None);
assert_eq!(
ast::Constant::Boolean(true).eval(&mut scope),
Some(true.into())
);
assert_eq!(ast::Constant::Number(42).eval(&mut scope), Some(42.into()));
assert_eq!(
ast::Constant::String("foobar".into()).eval(&mut scope),
Some("foobar".into())
);
}
#[test]
fn test_vars() {
let mut scope = Scope::default();
let foo = ast::Location::Variable { name: "foo".into() };
let bar = ast::Location::Variable { name: "bar".into() };
assert_eq!(foo.eval(&mut scope), None);
assert_eq!(bar.eval(&mut scope), None);
scope.set("bar", &42);
assert_eq!(foo.eval(&mut scope), None);
assert_eq!(bar.eval(&mut scope), Some(42.into()));
}
#[test]
fn test_fields() {
let mut scope = Scope::default();
let loc = ast::Location::Field {
table: Box::new(ast::Expression::Variable(ast::Location::Variable {
name: "foo".into(),
})),
index: Box::new(ast::Expression::Constant(ast::Constant::String(
"bar".into(),
))),
};
let foo = types::Table::default();
let bar = types::Table::default();
scope.set("foo", &foo);
assert_eq!(loc.eval(&mut scope), None);
scope.set("bar", &bar);
assert_eq!(loc.eval(&mut scope), None);
bar.set("foo".into(), Some(666.into()));
assert_eq!(loc.eval(&mut scope), None);
foo.set("bar".into(), Some(42.into()));
assert_eq!(loc.eval(&mut scope), Some(42.into()));
}
#[test]
fn test_call() {
let mut scope = Scope::default();
scope.add_fn("sqr", |_, args| {
let [arg] = args.as_slice() else {
panic!("exactly one argument expected");
};
let Some(ValueInner::Number(arg)) = arg else {
panic!("number expected");
};
Some(ValueInner::Number(arg * arg))
});
assert_eq!(
ast::Expression::Call(ast::Call {
callee: "sqr".into(),
args: vec![ast::Expression::Constant(ast::Constant::Number(7))],
})
.eval(&mut scope),
Some(49.into())
);
}
#[test]
fn test_var_assign() {
let mut scope = Scope::default();
assert_eq!(scope.get("foo"), None);
assert_eq!(scope.get("bar"), None);
ast::Statement::Assign {
target: ast::Location::Variable { name: "foo".into() },
source: Box::new(ast::Expression::Constant(ast::Constant::Number(42))),
}
.exec(&mut scope);
assert_eq!(scope.get("foo"), Some(42.into()));
assert_eq!(scope.get("bar"), None);
}
#[test]
fn test_table_assign() {
let mut scope = Scope::default();
let foo = types::Table::default();
let bar = types::Table::default();
scope.set("foo", &foo);
scope.set("bar", &bar);
assert_eq!(foo.get("foo".into()), None);
assert_eq!(foo.get("bar".into()), None);
assert_eq!(bar.get("foo".into()), None);
assert_eq!(bar.get("bar".into()), None);
ast::Statement::Assign {
target: ast::Location::Field {
table: Box::new(ast::Expression::Variable(ast::Location::Variable {
name: "foo".into(),
})),
index: Box::new(ast::Expression::Constant(ast::Constant::String(
"bar".into(),
))),
},
source: Box::new(ast::Expression::Constant(ast::Constant::Number(42))),
}
.exec(&mut scope);
assert_eq!(foo.get("foo".into()), None);
assert_eq!(foo.get("bar".into()), Some(42.into()));
assert_eq!(bar.get("foo".into()), None);
assert_eq!(bar.get("bar".into()), None);
}
#[test]
fn test_call_statement() {
let mut scope = Scope::default();
scope.add_fn("set", |scope, args| {
let [key, value] = args.as_slice() else {
panic!("exactly two arguments expected");
};
let Some(ValueInner::String(key)) = key else {
panic!("string expected");
};
scope.rawset(key.clone(), value.clone());
None
});
assert_eq!(scope.get("foo"), None);
assert_eq!(scope.get("bar"), None);
ast::Statement::Call(ast::Call {
callee: "set".into(),
args: vec![
ast::Expression::Constant(ast::Constant::String("foo".into())),
ast::Expression::Constant(ast::Constant::Number(42)),
],
})
.exec(&mut scope);
assert_eq!(scope.get("foo"), Some(42.into()));
assert_eq!(scope.get("bar"), None);
}
}

View File

@ -1,63 +0,0 @@
use std::{
cell::RefCell,
collections::{hash_map::Entry, HashMap},
rc::Rc,
};
use crate::types::Value;
#[derive(Debug, Clone, Default)]
pub struct Variable(Rc<RefCell<Value>>);
impl Variable {
pub fn get(&self) -> Value {
self.0.borrow().clone()
}
pub fn set(&self, value: Value) {
*self.0.borrow_mut() = value;
}
}
#[derive(Debug, Clone, Default)]
struct ScopeInner {
parent: Option<Scope>,
locals: HashMap<String, Variable>,
}
#[derive(Debug, Clone, Default)]
pub struct Scope(Rc<RefCell<ScopeInner>>);
impl Scope {
pub fn new() -> Self {
Self::default()
}
pub fn nested(&self) -> Self {
Self(Rc::new(RefCell::new(ScopeInner {
parent: Some(self.clone()),
..Default::default()
})))
}
pub fn add(&self, name: String) {
self.0.borrow_mut().locals.insert(name, Variable::default());
}
pub fn get(&self, name: String) -> Variable {
let mut this = self.0.borrow_mut();
let parent = this.parent.clone();
match this.locals.entry(name) {
Entry::Occupied(var) => var.get().clone(),
Entry::Vacant(var) => {
if let Some(parent) = parent {
let name = var.into_key();
drop(this);
parent.get(name)
} else {
var.insert(Variable::default()).clone()
}
}
}
}
}

View File

@ -48,6 +48,7 @@ enum Expression {
Variable(Location), Variable(Location),
Call(Call), Call(Call),
Function(Function), Function(Function),
Table(Table),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -56,6 +57,17 @@ struct Call {
args: Vec<Expression>, args: Vec<Expression>,
} }
#[derive(Debug, Clone)]
struct Table {
fields: Vec<TableField>,
}
#[derive(Debug, Clone)]
struct TableField {
key: Expression,
value: Expression,
}
mod scope { mod scope {
use std::collections::HashMap; use std::collections::HashMap;
@ -174,6 +186,7 @@ fn build_expression(scope: &BuildContext<'_>, code: &ast::Expression) -> Express
ast::Expression::Variable(loc) => Expression::Variable(lookup(scope, loc)), ast::Expression::Variable(loc) => Expression::Variable(lookup(scope, loc)),
ast::Expression::Call(call) => Expression::Call(build_call(scope, call)), ast::Expression::Call(call) => Expression::Call(build_call(scope, call)),
ast::Expression::Function(f) => Expression::Function(build_function(scope, f)), ast::Expression::Function(f) => Expression::Function(build_function(scope, f)),
ast::Expression::Table(table) => Expression::Table(build_table(scope, table)),
} }
} }
@ -193,6 +206,19 @@ fn build_call(scope: &BuildContext<'_>, code: &ast::Call) -> Call {
} }
} }
fn build_table(scope: &BuildContext<'_>, code: &ast::Table) -> Table {
Table {
fields: code
.fields
.iter()
.map(|field| TableField {
key: build_expression(scope, &field.name),
value: build_expression(scope, &field.init),
})
.collect(),
}
}
fn build_function(parent: &BuildContext<'_>, code: &ast::Function) -> Function { fn build_function(parent: &BuildContext<'_>, code: &ast::Function) -> Function {
let scope = BuildContext { let scope = BuildContext {
parent: Some(parent), parent: Some(parent),
@ -291,7 +317,6 @@ impl Eval for ast::Constant {
ast::Constant::Boolean(value) => Some(types::ValueInner::Boolean(*value)), ast::Constant::Boolean(value) => Some(types::ValueInner::Boolean(*value)),
ast::Constant::Number(value) => Some(types::ValueInner::Number(*value)), ast::Constant::Number(value) => Some(types::ValueInner::Number(*value)),
ast::Constant::String(value) => Some(types::ValueInner::String(value.clone())), ast::Constant::String(value) => Some(types::ValueInner::String(value.clone())),
ast::Constant::EmptyTable => Some(types::ValueInner::Table(Default::default())),
} }
} }
} }
@ -337,6 +362,18 @@ impl Eval for Function {
} }
} }
impl Eval for Table {
fn eval(&self, ctx: &RunContext) -> types::Value {
let table = types::Table::default();
for field in &self.fields {
let key = field.key.eval(ctx).expect("attempt to set a nil index");
let value = field.value.eval(ctx);
table.set(key, value);
}
Some(table.into())
}
}
impl Eval for Expression { impl Eval for Expression {
fn eval(&self, ctx: &RunContext) -> types::Value { fn eval(&self, ctx: &RunContext) -> types::Value {
match self { match self {
@ -344,6 +381,7 @@ impl Eval for Expression {
Expression::Variable(inner) => inner.eval(ctx), Expression::Variable(inner) => inner.eval(ctx),
Expression::Call(inner) => inner.eval(ctx), Expression::Call(inner) => inner.eval(ctx),
Expression::Function(inner) => inner.eval(ctx), Expression::Function(inner) => inner.eval(ctx),
Expression::Table(inner) => inner.eval(ctx),
} }
} }
} }
@ -405,10 +443,34 @@ impl OpenFunction {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ast::*, types::ValueInner}; use crate::{
ast::*,
types::{Value, ValueInner},
};
use super::build; use super::build;
fn get(table: &Value, index: &str) -> Value {
let Some(ValueInner::Table(table)) = table else {
panic!("a table expected");
};
table.get(index.into())
}
fn set(table: &Value, index: &str, value: Value) {
let Some(ValueInner::Table(table)) = table else {
panic!("a table expected");
};
table.set(index.into(), value);
}
fn call(func: &Value, args: &[Value]) -> Value {
let Some(ValueInner::Function(func)) = func else {
panic!("a function expected");
};
func.call(args)
}
#[test] #[test]
fn test_local() { fn test_local() {
let testee = Function { let testee = Function {
@ -484,9 +546,53 @@ mod tests {
}; };
let remember = build(&remember); let remember = build(&remember);
let the_answerer = remember.call(None, &[Some(42.into())]); let the_answerer = remember.call(None, &[Some(42.into())]);
let Some(ValueInner::Function(f)) = the_answerer else { assert_eq!(call(&the_answerer, &[]), Some(42.into()));
panic!("a function expected"); }
#[test]
fn test_rw() {
let make_cell = Function {
name: Some("make_cell".into()),
args: vec![],
body: vec![Statement::Local {
name: "data".into(),
init: None,
}],
ret: Some(Box::new(Expression::new_table(
[
(
"get",
Expression::Function(Function {
name: None,
args: vec![],
body: vec![],
ret: Some(Box::new(Expression::new_variable("data"))),
}),
),
(
"set",
Expression::Function(Function {
name: None,
args: vec!["value".into()],
body: vec![Statement::Assign {
target: Location::new_variable("data"),
source: Box::new(Expression::new_variable("value")),
}],
ret: None,
}),
),
]
.into_iter(),
))),
}; };
assert_eq!(f.call(&[]), Some(42.into())); let make_cell = build(&make_cell);
let cell1 = make_cell.call(None, &[]);
let cell2 = make_cell.call(None, &[]);
assert_eq!(call(&get(&cell1, "get"), &[]), None);
call(&get(&cell1, "set"), &[Some(42.into())]);
call(&get(&cell2, "set"), &[Some("foobar".into())]);
assert_eq!(call(&get(&cell1, "get"), &[]), Some(42.into()));
call(&get(&cell1, "set"), &[call(&get(&cell2, "get"), &[])]);
assert_eq!(call(&get(&cell1, "get"), &[]), Some("foobar".into()));
} }
} }