Move example code to examples directory

The commit moves example and test files to a dedicated examples
directory and updates the code for function and variable handling. The
subject line captures the primary purpose of the move operation.

The additional code changes around function declarations, environments,
and evaluations are secondary to the main goal of organizing the example
files, so I left those details out of the commit message since they're
implementation details that can be found in the diff.
This commit is contained in:
Giulio Agostini
2025-10-06 18:52:32 +02:00
parent 827349cbad
commit fa2e9178fb
16 changed files with 769 additions and 278 deletions
+61 -30
View File
@@ -15,7 +15,9 @@ use std::fmt::{Debug, Display};
*
* expression_statement -> expression ";"
* print_statement -> "print" expression ";"
* var_statement -> "var" IDENTIFIER ("=" expression)? ";"
* var_statement -> ("var")? IDENTIFIER (":" IDENTIFIER)? (("=" expression)? ";")| function_declaration
* function_declaration -> "::" "(" parameters? ")" ("->" IDENTIFIER)? block_statement
* parameters -> ( IDENTIFIER (":" IDENTIFIER)? ("," IDENTIFIER (":" IDENTIFIER)? )* )
* assignment_statement -> IDENTIFIER "=" expression ";"
* block_statement -> "do" statement* "end"
* if_statement -> "if" expression "then" statement ("elif" expression "then" statement)* ("else" statement)? "end"
@@ -29,10 +31,12 @@ use std::fmt::{Debug, Display};
* comparison -> term ((">" | ">=" | "<" | "<=") term)*
* term -> factor (("+" | "-") factor)*
* factor -> unary (("*" | "/") unary)*
* unary -> ("!" | "-") unary | primary
* unary -> ("!" | "-") unary | call
* call -> primary ("(" arguments ")")*
* arguments -> expression ("," expression)*
* primary -> NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")" | IDENTIFIER
*/
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub enum Expr {
Literal {
value: LiteralValue,
@@ -49,9 +53,13 @@ pub enum Expr {
Grouping {
expression: Box<AstNode<Expr>>,
},
Variable {
Identifier {
name: String,
},
Call {
callee: Box<AstNode<Expr>>,
arguments: Vec<AstNode<Expr>>,
},
}
// Implementazione Display per Expr (per debugging)
@@ -66,7 +74,17 @@ impl std::fmt::Display for Expr {
} => write!(f, "Binary ({} {} {})", left.node, operator, right.node),
Expr::Unary { operator, operand } => write!(f, "Unary ({} {})", operator, operand.node),
Expr::Grouping { expression } => write!(f, "Grouping (group {})", expression.node),
Expr::Variable { name } => write!(f, "Variable (variable {})", name),
Expr::Identifier { name } => write!(f, "Identifier (variable {})", name),
Expr::Call { callee, arguments } => write!(
f,
"Call ({}({}))",
callee.node,
arguments
.iter()
.map(|arg| arg.node.to_string())
.collect::<Vec<String>>()
.join(", ")
),
}
}
}
@@ -82,12 +100,22 @@ impl Debug for Expr {
} => write!(f, "({:?} {:?} {:?})", operator, left.node, right.node),
Expr::Unary { operator, operand } => write!(f, "({:?} {:?})", operator, operand.node),
Expr::Grouping { expression } => write!(f, "(group {:?})", expression.node),
Expr::Variable { name } => write!(f, "(variable {:?})", name),
Expr::Identifier { name } => write!(f, "(variable {:?})", name),
Expr::Call { callee, arguments } => write!(
f,
"Call ({}({}))",
callee.node,
arguments
.iter()
.map(|arg| arg.node.to_string())
.collect::<Vec<String>>()
.join(", ")
),
}
}
}
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub enum Stmt {
Expression {
expression: Box<AstNode<Expr>>,
@@ -98,11 +126,11 @@ pub enum Stmt {
Stmt {
expression: Box<AstNode<Expr>>,
},
Var {
VarDeclaration {
name: String,
initializer: Option<Box<AstNode<Expr>>>,
},
Assign {
VarAssigment {
name: String,
value: Box<AstNode<Expr>>,
},
@@ -123,8 +151,9 @@ pub enum Stmt {
body: Box<AstNode<Stmt>>,
},
For {
variable: Box<AstNode<Expr>>,
iterable: Box<AstNode<Expr>>,
variable: Box<AstNode<Stmt>>,
condition: Box<AstNode<Expr>>,
increment: Box<AstNode<Stmt>>,
body: Box<AstNode<Stmt>>,
},
}
@@ -153,11 +182,11 @@ impl Display for Stmt {
}
Stmt::Print { expression } => write!(f, "Print({});", expression.node),
Stmt::Stmt { expression } => write!(f, "Stmt({});", expression.node),
Stmt::Var { name, initializer } => match initializer {
Stmt::VarDeclaration { name, initializer } => match initializer {
Some(init) => write!(f, "Var({} = {});", name, init.node),
None => write!(f, "Var({});", name),
},
Stmt::Assign { name, value } => write!(f, "Assign({} = {});", name, value.node),
Stmt::VarAssigment { name, value } => write!(f, "Assign({} = {});", name, value.node),
Stmt::Return { expression } => write!(f, "Return({});", expression.node),
Stmt::Block { statements } => write!(
f,
@@ -173,12 +202,13 @@ impl Display for Stmt {
}
Stmt::For {
variable,
iterable,
condition,
increment,
body,
} => write!(
f,
"For({} in {}) {{\n{}\n}}",
variable.node, iterable.node, body.node
"For({} = {} in {}) {{\n{}\n}}",
variable.node, condition.node, increment.node, body.node
),
}
}
@@ -209,11 +239,13 @@ impl Debug for Stmt {
}
Stmt::Print { expression } => write!(f, "Print({:?});", expression.node),
Stmt::Stmt { expression } => write!(f, "Stmt({:?});", expression.node),
Stmt::Var { name, initializer } => match initializer {
Stmt::VarDeclaration { name, initializer } => match initializer {
Some(init) => write!(f, "Var({:?} = {:?});", name, init.node),
None => write!(f, "Var({:?});", name),
},
Stmt::Assign { name, value } => write!(f, "Assign({:?} = {:?});", name, value.node),
Stmt::VarAssigment { name, value } => {
write!(f, "Assign({:?} = {:?});", name, value.node)
}
Stmt::Return { expression } => write!(f, "Return({:?});", expression.node),
Stmt::Block { statements } => write!(
f,
@@ -229,12 +261,13 @@ impl Debug for Stmt {
}
Stmt::For {
variable,
iterable,
condition,
increment,
body,
} => write!(
f,
"For({:?} in {:?}) {{\n{:?}\n}}",
variable.node, iterable.node, body.node
"For({:?} = {:?} in {:?}) {{\n{:?}\n}}",
variable.node, condition.node, increment.node, body.node
),
}
}
@@ -253,12 +286,10 @@ impl AstNodeKind for Expr {
operator: _,
right: _,
} => "binary expression",
Expr::Unary {
operator: _,
operand: _,
} => "unary expression",
Expr::Grouping { expression: _ } => "grouping expression",
Expr::Variable { name: _ } => "variable expression",
Expr::Unary { .. } => "unary expression",
Expr::Grouping { .. } => "grouping expression",
Expr::Identifier { .. } => "variable expression",
Expr::Call { .. } => "call expression",
}
}
}
@@ -267,8 +298,8 @@ impl AstNodeKind for Stmt {
fn kind(&self) -> &'static str {
match self {
Stmt::Expression { .. } => "expression",
Stmt::Var { .. } => "variable declaration",
Stmt::Assign { .. } => "assignment",
Stmt::VarDeclaration { .. } => "variable declaration",
Stmt::VarAssigment { .. } => "assignment",
Stmt::Return { .. } => "return statement",
Stmt::Block { .. } => "block statement",
Stmt::If { .. } => "if statement",
@@ -280,7 +311,7 @@ impl AstNodeKind for Stmt {
}
}
#[derive(Clone, PartialEq)]
#[derive(Clone, PartialEq, Default)]
pub struct AstNode<T: AstNodeKind + Debug + Display> {
pub node: T,
pub source_slice: SourceSlice,
+3 -1
View File
@@ -38,6 +38,7 @@ fn get_keyword_token(word: &str) -> Option<TokenType> {
"True" => Some(TokenType::True),
"False" => Some(TokenType::False),
"Nil" => Some(TokenType::Nil),
"fn" => Some(TokenType::Fn),
_ => None,
}
}
@@ -146,6 +147,7 @@ impl Lexer {
('-', _) => Ok(Some(self.make_token(TokenType::Minus))),
('+', _) => Ok(Some(self.make_token(TokenType::Plus))),
(';', _) => Ok(Some(self.make_token(TokenType::Semicolon))),
(':', _) => Ok(Some(self.make_token(TokenType::Colon))),
('*', _) => Ok(Some(self.make_token(TokenType::Star))),
('%', _) => Ok(Some(self.make_token(TokenType::Percent))),
('!', '=') => {
@@ -262,7 +264,7 @@ impl Lexer {
}
fn identifier(&mut self) -> LoxResult<Option<Token>> {
while self.peek().is_alphanumeric() {
while self.peek().is_alphanumeric() || self.peek() == '_' {
self.advance();
}
let text = self.input[self.start_char..self.current_char].to_string();
+217 -53
View File
@@ -1,11 +1,11 @@
use crate::{
frontend::{
ast::{AstNode, Expr, Stmt},
source_registry::SourceSlice,
tokens::{LiteralValue, Token, TokenType},
source_registry::{SourcePosition, SourceSlice},
tokens::{LiteralValue, LoxFunction, Token, TokenType},
},
logging::display_ast::{pretty_print_with_config, PrettyConfig},
result::{parse_error, LoxError, LoxResult},
result::{parse_error, runtime_error, LoxResult},
};
pub struct Parser {
@@ -58,10 +58,14 @@ impl Parser {
fn statement(&mut self) -> LoxResult<AstNode<Stmt>> {
match (&self.peek().token_type, &self.peek_next().token_type) {
(TokenType::Var, _) => self.var_statement(),
(TokenType::Print, _) => self.print_statement(),
(TokenType::Return, _) => self.return_statement(),
(TokenType::StartBlock, _) => self.block_statement(),
(TokenType::Var, TokenType::Identifier) => {
self.advance();
self.var_statement()
}
(TokenType::Identifier, TokenType::Colon) => self.var_statement(),
(TokenType::Identifier, TokenType::Equal) => self.assignment_statement(),
(TokenType::If, _) => self.if_statement(),
(TokenType::While, _) => self.while_statement(),
@@ -72,46 +76,78 @@ impl Parser {
fn for_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
let start_slice = self.peek().source_slice.clone();
self.advance();
let variable = if let Token {
token_type: TokenType::Identifier,
literal: Some(variable_name),
source_slice,
..
} = self.peek()
{
AstNode {
node: Expr::Literal {
value: variable_name.clone(),
},
source_slice: source_slice.clone(),
}
} else {
return parse_error(
self.peek().source_slice.clone(),
"Expect variable name after 'for' keyword.",
);
};
self.advance();
self.consume(TokenType::In, "Expect 'in' after for variable.")?;
let iterable = self.expression()?;
self.advance(); // consume 'for'
let variable = self.var_statement()?;
let condition = self.expression()?;
self.consume(TokenType::Semicolon, "Expected ';' after for condition")?;
let increment = self.assignment_statement()?;
self.consume(TokenType::StartBlock, "Expected 'do' after for range")?;
let body = self.statement()?;
let end_slice = body.source_slice.clone();
self.consume(TokenType::EndBlock, "Expected 'end' after for body")?;
let end_slice = self.previous().source_slice.clone();
let combined_slice = SourceSlice::from_positions(
start_slice.source_id,
start_slice.start_position,
end_slice.end_position,
);
Ok(AstNode::new(
Stmt::For {
variable: Box::new(variable),
iterable: Box::new(iterable),
condition: Box::new(condition),
increment: Box::new(increment),
body: Box::new(body),
},
combined_slice,
))
}
// fn for_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
// let start_slice = self.peek().source_slice.clone();
// self.advance();
// let variable = if let Token {
// token_type: TokenType::Identifier,
// literal: Some(variable_name),
// source_slice,
// ..
// } = self.peek()
// {
// AstNode {
// node: Expr::Literal {
// value: variable_name.clone(),
// },
// source_slice: source_slice.clone(),
// }
// } else {
// return parse_error(
// self.peek().source_slice.clone(),
// "Expect variable name after 'for' keyword.",
// );
// };
// self.advance();
// self.consume(TokenType::In, "Expect 'in' after for variable.")?;
// let iterable = self.expression()?;
// let body = self.statement()?;
// let end_slice = body.source_slice.clone();
// let combined_slice = SourceSlice::from_positions(
// start_slice.source_id,
// start_slice.start_position,
// end_slice.end_position,
// );
// Ok(AstNode::new(
// Stmt::For {
// variable: Box::new(variable),
// iterable: Box::new(iterable),
// body: Box::new(body),
// },
// combined_slice,
// ))
// }
fn while_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
let start_slice = self.peek().source_slice.clone();
self.advance();
@@ -178,11 +214,103 @@ impl Parser {
fn var_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
let start_slice = self.peek().source_slice.clone();
self.advance();
let name = self.consume(TokenType::Identifier, "Expect variable name.")?;
let name_lexeme = name.lexeme.clone();
self.consume(TokenType::Equal, "Expect '=' after variable name.")?;
let value = self.expression()?;
let mut _type_annotation = LiteralValue::Nil;
self.consume(TokenType::Colon, "Expect column")?;
match self.peek().token_type {
TokenType::Equal | TokenType::Identifier => {
self.variable_declaration(start_slice, name_lexeme)
}
TokenType::Colon => self.function_declaration(start_slice, name_lexeme),
_ => runtime_error(start_slice, "this is not supposed to be here"),
}
}
fn function_declaration(
&mut self,
start_slice: SourceSlice,
name_lexeme: String,
) -> LoxResult<AstNode<Stmt>> {
self.advance();
self.consume(TokenType::Fn, "Expect 'fn' after '::'")?;
self.consume(TokenType::LeftParen, "Expect '(' after 'fn' ")?;
let mut parameters: Vec<(String, String)> = vec![];
while self.peek().token_type != TokenType::RightParen {
let expr = self
.consume(TokenType::Identifier, "Expect identifier after '('")?
.clone();
if self.peek().token_type == TokenType::Colon {
self.advance();
let type_ =
self.consume(TokenType::Identifier, "Expected identifier after ':' ")?;
parameters.push((expr.lexeme, type_.lexeme.clone()));
} else {
parameters.push((expr.lexeme, "Any".to_string()));
}
if self.peek().token_type == TokenType::Comma {
self.advance();
}
}
self.advance();
let end_position = self.peek().source_slice.clone();
let combine_position = SourceSlice {
source_id: start_slice.source_id,
start_position: start_slice.start_position.clone(),
end_position: end_position.end_position,
};
let mut guard: Option<Box<Expr>> = None;
if self.peek().token_type == TokenType::RightBrace {
self.advance();
guard = Some(Box::new(self.expression()?.node.clone()));
self.consume(TokenType::RightBrace, "Expected '}' after guard expression")?;
}
let body = self.statement()?;
let node = AstNode {
node: Expr::Literal {
value: LiteralValue::Function(LoxFunction {
parameters,
body,
closure: None,
guard,
}),
},
source_slice: combine_position.clone(),
};
Ok(AstNode::new(
Stmt::VarDeclaration {
name: name_lexeme,
initializer: Some(Box::new(node)),
},
combine_position.clone(),
))
}
fn variable_declaration(
&mut self,
start_slice: SourceSlice,
name_lexeme: String,
) -> LoxResult<AstNode<Stmt>> {
if self.peek().token_type == TokenType::Identifier {
self.advance();
if self.peek().token_type == TokenType::Identifier {
self.advance();
}
// todo: make type annotation
}
let mut value = AstNode {
node: Expr::Literal {
value: LiteralValue::Nil,
},
source_slice: start_slice.clone(),
};
if self.peek().token_type == TokenType::Equal {
self.advance();
value = self.expression()?;
}
let semicolon = self.consume(
TokenType::Semicolon,
"Expect ';' after variable declaration.",
@@ -194,14 +322,13 @@ impl Parser {
end_slice.end_position,
);
Ok(AstNode::new(
Stmt::Var {
Stmt::VarDeclaration {
name: name_lexeme,
initializer: Some(Box::new(value)),
},
combined_slice,
))
}
fn assignment_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
let start_slice = self.peek().source_slice.clone();
let name = self.consume(TokenType::Identifier, "Expect variable name.")?;
@@ -219,7 +346,7 @@ impl Parser {
end_slice.end_position,
);
Ok(AstNode::new(
Stmt::Assign {
Stmt::VarAssigment {
name: name_lexeme,
value: Box::new(value),
},
@@ -481,7 +608,56 @@ impl Parser {
));
}
self.primary()
self.call()
}
fn call(&mut self) -> LoxResult<AstNode<Expr>> {
let mut expr = self.primary()?;
loop {
if self.peek().token_type == TokenType::LeftParen {
expr = self.finish_call(expr)?;
} else {
break;
}
}
Ok(expr)
}
fn finish_call(&mut self, callee: AstNode<Expr>) -> LoxResult<AstNode<Expr>> {
let start_slice = callee.source_slice.start_position.clone();
self.advance(); // Consume '('
let mut arguments = Vec::new();
if self.peek().token_type != TokenType::RightParen {
loop {
arguments.push(self.expression()?);
if self.peek().token_type == TokenType::Comma {
self.advance();
} else {
break;
}
}
}
self.consume(TokenType::RightParen, "Expect ')' after arguments.")?;
// Estendi il source_slice per includere le parentesi di chiusura
let end_slice = self.previous().source_slice.end_position.clone();
let full_slice = SourceSlice {
source_id: callee.source_slice.source_id.clone(),
start_position: start_slice.clone(),
end_position: end_slice.clone(),
};
Ok(AstNode::new(
Expr::Call {
callee: Box::new(callee),
arguments,
},
full_slice,
))
}
fn primary(&mut self) -> LoxResult<AstNode<Expr>> {
@@ -523,9 +699,7 @@ impl Parser {
if let Some(literal) = literal {
Ok(AstNode::new(Expr::Literal { value: literal }, source_slice))
} else {
Err(self
.error(self.previous(), "Expected number literal")
.into())
parse_error(self.peek().source_slice.clone(), "Expected number literal")
}
}
TokenType::String => {
@@ -535,9 +709,7 @@ impl Parser {
if let Some(literal) = literal {
Ok(AstNode::new(Expr::Literal { value: literal }, source_slice))
} else {
Err(self
.error(self.previous(), "Expected string literal")
.into())
parse_error(self.peek().source_slice.clone(), "Expected string literal")
}
}
TokenType::LeftParen => {
@@ -556,9 +728,9 @@ impl Parser {
let name = self.peek().lexeme.clone();
let source_slice = self.peek().source_slice.clone();
self.advance();
Ok(AstNode::new(Expr::Variable { name }, source_slice))
Ok(AstNode::new(Expr::Identifier { name }, source_slice))
}
_ => Err(self.error(self.peek(), "Expect expression.").into()),
_ => parse_error(self.peek().source_slice.clone(), "Expect expression."),
}
}
@@ -606,14 +778,7 @@ impl Parser {
self.advance();
Ok(self.previous())
} else {
Err(self.error(self.peek(), message).into())
}
}
fn error(&self, token: &Token, message: &str) -> LoxError {
LoxError::ParseError {
source_slice: token.source_slice.clone(),
message: message.to_string(),
return parse_error(self.peek().source_slice.clone(), message);
}
}
@@ -628,7 +793,6 @@ impl Parser {
match self.peek().token_type {
TokenType::Class
| TokenType::Fun
| TokenType::Var
| TokenType::For
| TokenType::If
| TokenType::While
+45 -1
View File
@@ -1,6 +1,13 @@
use std::fmt;
use crate::frontend::source_registry::SourceSlice;
use crate::{
backend::environment::EnvironmentStack,
frontend::{
ast::{AstNode, Expr, Stmt},
source_registry::SourceSlice,
},
result::{runtime_error, LoxResult},
};
#[derive(Debug, Clone, PartialEq)]
pub enum LiteralValue {
@@ -9,6 +16,37 @@ pub enum LiteralValue {
Number(f64),
Boolean(bool),
Nil,
Function(LoxFunction),
NativeFunction(NativeFunction),
}
#[derive(Debug, Clone, PartialEq)]
pub struct LoxFunction {
pub parameters: Vec<(String, String)>,
pub body: AstNode<Stmt>,
pub closure: Option<EnvironmentStack>,
pub guard: Option<Box<Expr>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct NativeFunction {
pub arity: usize,
pub function: fn(&[LiteralValue]) -> LiteralValue,
}
impl NativeFunction {
pub fn new(arity: usize, function: fn(&[LiteralValue]) -> LiteralValue) -> Self {
NativeFunction { arity, function }
}
}
impl LiteralValue {
pub fn is_callable(&self) -> bool {
match self {
LiteralValue::Function(..) | LiteralValue::NativeFunction(..) => true,
_ => false,
}
}
}
impl fmt::Display for LiteralValue {
@@ -19,6 +57,8 @@ impl fmt::Display for LiteralValue {
LiteralValue::Number(n) => write!(f, "{}", n),
LiteralValue::Boolean(b) => write!(f, "{}", b),
LiteralValue::Nil => write!(f, "nil"),
LiteralValue::Function(..) => write!(f, "<fn lox function>"),
LiteralValue::NativeFunction(..) => write!(f, "<native native function>"),
}
}
}
@@ -37,6 +77,7 @@ pub enum TokenType {
Minus,
Plus,
Semicolon,
Colon,
Slash,
Star,
Percent,
@@ -57,6 +98,7 @@ pub enum TokenType {
Number,
// Keywords
Fn,
And,
Class,
StartBlock,
@@ -134,6 +176,8 @@ impl fmt::Display for TokenType {
TokenType::Val => write!(f, "val"),
TokenType::Percent => write!(f, "%"),
TokenType::In => write!(f, "in"),
TokenType::Colon => write!(f, ":"),
TokenType::Fn => write!(f, "fn"),
}
}
}