Compare commits
6 Commits
empty-tabl
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2167b524ec | |||
| af28442913 | |||
| a7ba99b3a8 | |||
| b131d40702 | |||
| ead7ac054d | |||
| 5138ada816 |
24
src/ast.rs
24
src/ast.rs
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
297
src/run.rs
297
src/run.rs
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
63
src/scope.rs
63
src/scope.rs
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
116
src/twopass.rs
116
src/twopass.rs
|
|
@ -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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user