labels and mutable variables

This commit is contained in:
me 2025-12-17 23:20:26 +02:00
parent a16571acf2
commit 4f13870182
12 changed files with 178 additions and 47 deletions

View file

@ -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,
}

View file

@ -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 {

View file

@ -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(
},
),
};
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(())
}

View file

@ -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)]

View file

@ -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)? {
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 {
Err(Error::UnexpectedToken {
expected: Token::Dot,
got: tokens.next(),
})
}
} 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();

View file

@ -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,

View file

@ -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",
),

View file

@ -0,0 +1,17 @@
---
source: src/parser/parser.rs
expression: result
---
Ok(
Definition {
mutable: true,
name: Name(
"x",
),
expr: Value(
Int(
108,
),
),
},
)

View file

@ -4,6 +4,7 @@ expression: result
---
Ok(
Definition {
mutable: false,
name: Name(
"x",
),

View file

@ -18,8 +18,7 @@ expression: result
Identifier(
"console",
),
Dot,
Identifier(
Label(
"log",
),
OpenParen,

View file

@ -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,

View file

@ -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,
})
}