labels and mutable variables
This commit is contained in:
parent
a16571acf2
commit
4f13870182
12 changed files with 178 additions and 47 deletions
|
|
@ -34,6 +34,7 @@ pub fn stmt_expr(expr: Expr) -> Statement {
|
|||
|
||||
pub fn define_expr(name: &str, expr: Expr) -> Definition {
|
||||
Definition {
|
||||
mutable: false,
|
||||
name: name.into(),
|
||||
expr,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ pub enum Statement {
|
|||
|
||||
#[derive(PartialEq, PartialOrd, Debug, Clone)]
|
||||
pub struct Definition {
|
||||
pub mutable: bool,
|
||||
pub name: Name,
|
||||
pub expr: Expr,
|
||||
}
|
||||
|
|
@ -65,9 +66,15 @@ pub enum Value {
|
|||
Boolean(bool),
|
||||
Record(Record),
|
||||
Vector(Vector),
|
||||
Label(Label),
|
||||
Ref(Ref),
|
||||
Closure { env: EnvName, expr: Box<Expr> },
|
||||
}
|
||||
|
||||
/// A mutable variable.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone)]
|
||||
pub struct Ref(pub u64);
|
||||
|
||||
/// An anonymous function.
|
||||
#[derive(PartialEq, PartialOrd, Debug, Clone)]
|
||||
pub struct Fn {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ pub fn run(
|
|||
let env_name = ast::EnvName("global".to_string());
|
||||
let mut state = State::new("global".into());
|
||||
|
||||
defs_to_env(program.0, &env_name, &mut state.envs)?;
|
||||
defs_to_env(program.0, &env_name, &mut state)?;
|
||||
|
||||
let main = ast::Expr::Value(state.envs.get(&env_name)?.get(&func)?.clone());
|
||||
|
||||
|
|
@ -39,8 +39,16 @@ fn eval_statement(
|
|||
) -> Result<StatementResult, Error> {
|
||||
match statement {
|
||||
ast::Statement::Expr(expr) => eval_expr(expr_env, state, expr).map(StatementResult::Result),
|
||||
ast::Statement::Let(ast::Definition { name, expr }) => {
|
||||
let result = eval_expr(expr_env, state, expr)?;
|
||||
ast::Statement::Let(ast::Definition {
|
||||
mutable,
|
||||
name,
|
||||
expr,
|
||||
}) => {
|
||||
let mut result = eval_expr(expr_env, state, expr)?;
|
||||
if *mutable {
|
||||
let reference = state.insert_variable(result);
|
||||
result = ast::Value::Ref(reference);
|
||||
}
|
||||
expr_env.insert(name.clone(), result.clone());
|
||||
Ok(StatementResult::Result(result))
|
||||
}
|
||||
|
|
@ -142,7 +150,19 @@ fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result<ast:
|
|||
.insert(name.clone(), rhs);
|
||||
eval_expr(expr_env, state, &ast::UNIT)
|
||||
}
|
||||
|
||||
/*
|
||||
// access via labels
|
||||
ast::Expr::Access { expr, field } => {
|
||||
let rhs = eval_expr(expr_env, state, rhs)?;
|
||||
if let Some(mut nested_field) =
|
||||
access_nested_field(expr_env, state, expr, field)?
|
||||
{
|
||||
todo!()
|
||||
} else {
|
||||
todo!() // error nested field unreachable
|
||||
}
|
||||
} */
|
||||
_ => todo!(),
|
||||
},
|
||||
_ => {
|
||||
|
|
@ -153,17 +173,45 @@ fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result<ast:
|
|||
},
|
||||
}
|
||||
}
|
||||
/*
|
||||
fn access_nested_field<'a>(
|
||||
expr_env_name: &Env,
|
||||
state: &'a mut State,
|
||||
expr: &ast::Expr,
|
||||
label: &ast::Label,
|
||||
) -> Result<&'a mut ast::Value, Error> {
|
||||
//let lhs = eval_expr(expr_env, state, expr)?;
|
||||
match expr {
|
||||
ast::Expr::Var(var) => {
|
||||
if let Some(mut value) = state.envs.get(&expr_env.name)?.get(&var)? {
|
||||
match &value {
|
||||
&ast::Value::Record(record) => {
|
||||
record.
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
fn defs_to_env(
|
||||
defs: Vec<ast::Definition>,
|
||||
env_name: &ast::EnvName,
|
||||
envs: &mut Envs,
|
||||
state: &mut State,
|
||||
) -> Result<(), Error> {
|
||||
let mut env = Env::new(env_name.clone());
|
||||
|
||||
for def in defs {
|
||||
let (name, closure) = match def {
|
||||
ast::Definition { expr, name } => (
|
||||
let (mutable, name, closure) = match def {
|
||||
ast::Definition {
|
||||
mutable,
|
||||
expr,
|
||||
name,
|
||||
} => (
|
||||
mutable,
|
||||
name,
|
||||
ast::Value::Closure {
|
||||
expr: Box::new(expr.clone()),
|
||||
|
|
@ -171,10 +219,15 @@ fn defs_to_env(
|
|||
},
|
||||
),
|
||||
};
|
||||
env.insert_nodup(&name, closure)?;
|
||||
if mutable {
|
||||
let reference = state.insert_variable(closure);
|
||||
env.insert_nodup(&name, ast::Value::Ref(reference))?;
|
||||
} else {
|
||||
env.insert_nodup(&name, closure)?;
|
||||
}
|
||||
}
|
||||
|
||||
envs.0.insert(env_name.clone(), env);
|
||||
state.envs.0.insert(env_name.clone(), env);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use crate::ast;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(PartialEq, Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
|
|
@ -51,23 +52,35 @@ impl Namer {
|
|||
|
||||
pub struct State {
|
||||
pub name: String,
|
||||
namer: Namer,
|
||||
env_namer: Namer,
|
||||
pub envs: Envs,
|
||||
variable_namer: Namer,
|
||||
pub variables: Variables,
|
||||
}
|
||||
|
||||
pub struct Variables(pub HashMap<ast::Ref, ast::Value>);
|
||||
|
||||
impl State {
|
||||
pub fn new(name: String) -> State {
|
||||
State {
|
||||
name,
|
||||
namer: Namer::new(),
|
||||
env_namer: Namer::new(),
|
||||
envs: Envs(BTreeMap::new()),
|
||||
variable_namer: Namer::new(),
|
||||
variables: Variables(HashMap::new()),
|
||||
}
|
||||
}
|
||||
pub fn generate_env(&mut self, env: Env) -> Result<ast::EnvName, Error> {
|
||||
let env_name = ast::EnvName(format!("{}_{}", self.name, self.namer.next()));
|
||||
let env_name = ast::EnvName(format!("{}_{}", self.name, self.env_namer.next()));
|
||||
self.envs.insert(&env_name, env)?;
|
||||
Ok(env_name)
|
||||
}
|
||||
|
||||
pub fn insert_variable(&mut self, value: ast::Value) -> ast::Ref {
|
||||
let name = ast::Ref(self.variable_namer.next());
|
||||
self.variables.0.insert(name.clone(), value);
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -45,20 +45,13 @@ fn parse_set(tokens: &mut Tokens) -> ParseResult<ast::Expr> {
|
|||
}
|
||||
|
||||
fn parse_dot(tokens: &mut Tokens, expr: ast::Expr) -> Result<ast::Expr, Error> {
|
||||
if let Some(_dot) = tokens.next_if(&Token::Dot) {
|
||||
if let Some(labels) = tokens.many_sep_by(&Token::Dot, parse_identifier_label)? {
|
||||
Ok(labels
|
||||
.into_iter()
|
||||
.fold(expr, |acc, label| ast::Expr::Access {
|
||||
expr: Box::new(acc),
|
||||
field: label,
|
||||
}))
|
||||
} else {
|
||||
Err(Error::UnexpectedToken {
|
||||
expected: Token::Dot,
|
||||
got: tokens.next(),
|
||||
})
|
||||
}
|
||||
if let Some(labels) = tokens.many(parse_label)? {
|
||||
Ok(labels
|
||||
.into_iter()
|
||||
.fold(expr, |acc, label| ast::Expr::Access {
|
||||
expr: Box::new(acc),
|
||||
field: label,
|
||||
}))
|
||||
} else {
|
||||
Ok(expr)
|
||||
}
|
||||
|
|
@ -66,9 +59,17 @@ fn parse_dot(tokens: &mut Tokens, expr: ast::Expr) -> Result<ast::Expr, Error> {
|
|||
|
||||
fn parse_definition(tokens: &mut Tokens) -> ParseResult<ast::Definition> {
|
||||
if let Some(_) = tokens.next_if(&Token::Let) {
|
||||
let mutable = match tokens.next_if(&Token::Mut) {
|
||||
Some(_) => true,
|
||||
_ => false,
|
||||
};
|
||||
if let Some(name) = parse_identifier_name(tokens)? {
|
||||
if let Some(expr) = parse_set(tokens)? {
|
||||
Ok(Some(ast::Definition { name, expr }))
|
||||
Ok(Some(ast::Definition {
|
||||
mutable,
|
||||
name,
|
||||
expr,
|
||||
}))
|
||||
} else {
|
||||
Err(Error::UnexpectedToken {
|
||||
expected: Token::Equals,
|
||||
|
|
@ -220,7 +221,7 @@ fn parse_vector(tokens: &mut Tokens) -> ParseResult<ast::Expr> {
|
|||
}
|
||||
|
||||
fn parse_record_field_expr(tokens: &mut Tokens) -> ParseResult<(ast::Label, ast::Expr)> {
|
||||
if let Some(label) = parse_identifier_label(tokens)? {
|
||||
if let Some(label) = parse_label(tokens)? {
|
||||
if let Some(_) = tokens.next_if(&Token::Colon) {
|
||||
if let Some(expr) = parse_expr(tokens)? {
|
||||
Ok(Some((label, expr)))
|
||||
|
|
@ -277,8 +278,16 @@ fn parse_identifier_name(tokens: &mut Tokens) -> ParseResult<ast::Name> {
|
|||
Ok(parse_identifier_string(tokens)?.map(|string| ast::Name(string)))
|
||||
}
|
||||
|
||||
fn parse_identifier_label(tokens: &mut Tokens) -> ParseResult<ast::Label> {
|
||||
Ok(parse_identifier_string(tokens)?.map(|string| ast::Label(string)))
|
||||
fn parse_label(tokens: &mut Tokens) -> ParseResult<ast::Label> {
|
||||
if let Some(LocatedToken {
|
||||
token: Token::Label(string),
|
||||
..
|
||||
}) = tokens.next_if(&Token::Label("".into()))
|
||||
{
|
||||
Ok(Some(ast::Label(string)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_identifier_string(tokens: &mut Tokens) -> ParseResult<String> {
|
||||
|
|
@ -335,7 +344,7 @@ mod tests {
|
|||
}
|
||||
#[test]
|
||||
fn record() {
|
||||
let program = "{ hello: 17, hi: \"cool\"}".to_string();
|
||||
let program = "{ .hello: 17, .hi: \"cool\"}".to_string();
|
||||
let result = parse(&mut scan(program), parse_expr);
|
||||
insta::assert_debug_snapshot!(result);
|
||||
}
|
||||
|
|
@ -353,7 +362,7 @@ mod tests {
|
|||
}
|
||||
#[test]
|
||||
fn assign() {
|
||||
let program = "x = [2, { hello: 7 }];".to_string();
|
||||
let program = "x = [2, { .hello: 7 }];".to_string();
|
||||
let result = parse(&mut scan(program), parse_expr);
|
||||
insta::assert_debug_snapshot!(result);
|
||||
}
|
||||
|
|
@ -382,11 +391,17 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(result);
|
||||
}
|
||||
#[test]
|
||||
fn let_mut_number() {
|
||||
let program = "let mut x = 108;".to_string();
|
||||
let result = parse(&mut scan(program), parse_definition);
|
||||
insta::assert_debug_snapshot!(result);
|
||||
}
|
||||
#[test]
|
||||
fn full_program() {
|
||||
let program = "
|
||||
let init = fn() {
|
||||
return {
|
||||
player: { position: { x: 10, y: 20 }, },
|
||||
.player: { .position: { .x: 10, .y: 20 }, },
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -399,7 +414,7 @@ let draw = fn(frame, state) {
|
|||
};
|
||||
|
||||
let migrate = fn(state) {
|
||||
return { player: { pos: state.player.position } };
|
||||
return { .player: { .pos: state.player.position } };
|
||||
};
|
||||
"
|
||||
.to_string();
|
||||
|
|
|
|||
|
|
@ -11,11 +11,6 @@ pub fn scan(source: String) -> Tokens {
|
|||
let start = scanner.cursor();
|
||||
if let Some(c) = scanner.pop() {
|
||||
match *c {
|
||||
'.' => tokens.push(LocatedToken {
|
||||
start,
|
||||
end: scanner.cursor(),
|
||||
token: Token::Dot,
|
||||
}),
|
||||
',' => tokens.push(LocatedToken {
|
||||
start,
|
||||
end: scanner.cursor(),
|
||||
|
|
@ -66,6 +61,31 @@ pub fn scan(source: String) -> Tokens {
|
|||
end: scanner.cursor(),
|
||||
token: Token::Equals,
|
||||
}),
|
||||
// labels
|
||||
'.' => {
|
||||
let mut str = "".to_string();
|
||||
loop {
|
||||
if let Some(ch) = scanner.peek()
|
||||
&& !ch.is_alphanumeric()
|
||||
&& *ch != '_'
|
||||
{
|
||||
break;
|
||||
}
|
||||
if let Some(ch) = scanner.pop() {
|
||||
str.push(*ch);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tokens.push(LocatedToken {
|
||||
start,
|
||||
end: scanner.cursor(),
|
||||
token: match str.as_str() {
|
||||
_ => Token::Label(str),
|
||||
},
|
||||
});
|
||||
}
|
||||
// comments
|
||||
'#' => {
|
||||
let mut str = "".to_string();
|
||||
|
|
@ -91,6 +111,7 @@ pub fn scan(source: String) -> Tokens {
|
|||
token: Token::String(str),
|
||||
});
|
||||
}
|
||||
// whitespace
|
||||
_ if c.is_whitespace() => {}
|
||||
// numbers
|
||||
_ if c.is_numeric() => {
|
||||
|
|
@ -137,6 +158,7 @@ pub fn scan(source: String) -> Tokens {
|
|||
token: match str.as_str() {
|
||||
"fn" => Token::Fn,
|
||||
"let" => Token::Let,
|
||||
"mut" => Token::Mut,
|
||||
"return" => Token::Return,
|
||||
"true" => Token::True,
|
||||
"false" => Token::False,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ expression: result
|
|||
Ok(
|
||||
[
|
||||
Definition {
|
||||
mutable: false,
|
||||
name: Name(
|
||||
"init",
|
||||
),
|
||||
|
|
@ -65,6 +66,7 @@ Ok(
|
|||
),
|
||||
},
|
||||
Definition {
|
||||
mutable: false,
|
||||
name: Name(
|
||||
"update",
|
||||
),
|
||||
|
|
@ -99,6 +101,7 @@ Ok(
|
|||
),
|
||||
},
|
||||
Definition {
|
||||
mutable: false,
|
||||
name: Name(
|
||||
"draw",
|
||||
),
|
||||
|
|
@ -155,6 +158,7 @@ Ok(
|
|||
),
|
||||
},
|
||||
Definition {
|
||||
mutable: false,
|
||||
name: Name(
|
||||
"migrate",
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
source: src/parser/parser.rs
|
||||
expression: result
|
||||
---
|
||||
Ok(
|
||||
Definition {
|
||||
mutable: true,
|
||||
name: Name(
|
||||
"x",
|
||||
),
|
||||
expr: Value(
|
||||
Int(
|
||||
108,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
|
@ -4,6 +4,7 @@ expression: result
|
|||
---
|
||||
Ok(
|
||||
Definition {
|
||||
mutable: false,
|
||||
name: Name(
|
||||
"x",
|
||||
),
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ expression: result
|
|||
Identifier(
|
||||
"console",
|
||||
),
|
||||
Dot,
|
||||
Identifier(
|
||||
Label(
|
||||
"log",
|
||||
),
|
||||
OpenParen,
|
||||
|
|
|
|||
|
|
@ -88,8 +88,7 @@ expression: result
|
|||
Identifier(
|
||||
"frame",
|
||||
),
|
||||
Dot,
|
||||
Identifier(
|
||||
Label(
|
||||
"clear",
|
||||
),
|
||||
OpenParen,
|
||||
|
|
@ -134,12 +133,10 @@ expression: result
|
|||
Identifier(
|
||||
"state",
|
||||
),
|
||||
Dot,
|
||||
Identifier(
|
||||
Label(
|
||||
"player",
|
||||
),
|
||||
Dot,
|
||||
Identifier(
|
||||
Label(
|
||||
"position",
|
||||
),
|
||||
CloseCurly,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ pub struct LocatedToken {
|
|||
pub enum Token {
|
||||
Let,
|
||||
Fn,
|
||||
Mut,
|
||||
Return,
|
||||
Equals,
|
||||
OpenParen,
|
||||
|
|
@ -17,7 +18,6 @@ pub enum Token {
|
|||
CloseBracket,
|
||||
OpenCurly,
|
||||
CloseCurly,
|
||||
Dot,
|
||||
Comma,
|
||||
Semicolon,
|
||||
Colon,
|
||||
|
|
@ -26,6 +26,7 @@ pub enum Token {
|
|||
Number(u32),
|
||||
String(String),
|
||||
Identifier(String),
|
||||
Label(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
|
@ -52,6 +53,7 @@ impl Tokens {
|
|||
(Token::Identifier(_), Token::Identifier(_)) => true,
|
||||
(Token::Number(_), Token::Number(_)) => true,
|
||||
(Token::String(_), Token::String(_)) => true,
|
||||
(Token::Label(_), Token::Label(_)) => true,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue