variable declarations

This commit is contained in:
Shautvast 2025-10-21 18:33:07 +02:00
parent 6dd22f5e4e
commit 1d58725559
9 changed files with 231 additions and 129 deletions

1
Cargo.lock generated
View file

@ -199,6 +199,7 @@ dependencies = [
"axum",
"chrono",
"dotenv",
"log",
"log4rs",
"reqwest",
"serde",

View file

@ -19,3 +19,4 @@ tracing = "0.1.41"
tracing-subscriber = "0.3.20"
anyhow = "1.0"
tower-livereload = "0.9.6"
log = "0.4.28"

View file

@ -3,7 +3,7 @@ use crate::value::Value;
use crate::vm::{
OP_ADD, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CONSTANT, OP_DIVIDE, OP_FALSE, OP_MULTIPLY,
OP_NEGATE, OP_RETURN, OP_SUBTRACT, OP_TRUE, OP_NOT, OP_SHL, OP_SHR, OP_LESS, OP_LESS_EQUAL,
OP_GREATER, OP_GREATER_EQUAL, OP_EQUAL, OP_PRINT, OP_POP,
OP_GREATER, OP_GREATER_EQUAL, OP_EQUAL, OP_PRINT, OP_POP, OP_DEFINE, OP_GET
};
pub struct Chunk {
@ -73,6 +73,8 @@ impl Chunk {
OP_EQUAL => self.simple_inst("EQ", offset),
OP_PRINT => self.simple_inst("PRT", offset),
OP_POP => self.simple_inst("POP", offset),
OP_DEFINE => self.constant_inst("DEF", offset),
OP_GET => self.constant_inst("GET", offset),
_ => {
println!("Unknown instruction");
offset + 1

View file

@ -2,7 +2,11 @@ use crate::chunk::Chunk;
use crate::scanner::scan;
use crate::tokens::{Token, TokenType};
use crate::value::Value;
use crate::vm::{OP_ADD, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CONSTANT, OP_DIVIDE, OP_EQUAL, OP_FALSE, OP_GREATER, OP_GREATER_EQUAL, OP_LESS, OP_LESS_EQUAL, OP_MULTIPLY, OP_NEGATE, OP_NOT, OP_POP, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT, OP_TRUE};
use crate::vm::{
OP_ADD, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CONSTANT, OP_DEFINE, OP_DIVIDE, OP_EQUAL, OP_FALSE,
OP_GET, OP_GREATER, OP_GREATER_EQUAL, OP_LESS, OP_LESS_EQUAL, OP_MULTIPLY, OP_NEGATE, OP_NOT,
OP_POP, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT, OP_TRUE,
};
use anyhow::anyhow;
use std::collections::HashMap;
use std::sync::LazyLock;
@ -13,6 +17,7 @@ pub fn compile(source: &str) -> anyhow::Result<Chunk> {
debug!("Scanned tokens: {:?}", tokens);
let mut compiler = Compiler {
source: source.lines().map(|s| s.to_string()).collect(),
chunk: Chunk::new("main"),
previous_token: &tokens[0],
current_token: &tokens[0],
@ -25,6 +30,7 @@ pub fn compile(source: &str) -> anyhow::Result<Chunk> {
}
struct Compiler<'a> {
source: Vec<String>,
chunk: Chunk,
tokens: &'a Vec<Token>,
current: usize,
@ -36,8 +42,6 @@ struct Compiler<'a> {
impl<'a> Compiler<'a> {
fn compile(mut self) -> anyhow::Result<Chunk> {
while !self.match_token(TokenType::Eof) {
self.declaration()?;
}
@ -50,8 +54,42 @@ impl<'a> Compiler<'a> {
}
fn declaration(&mut self) -> anyhow::Result<()> {
if self.match_token(TokenType::Let) {
self.let_declaration()
} else {
self.statement()
}
}
fn let_declaration(&mut self) -> anyhow::Result<()> {
let index = self.parse_variable("Expect variable name")?;
if self.match_token(TokenType::Equal) {
self.expression()?;
self.consume(TokenType::Eol, "Expect end of line")?;
self.define_variable(index)?;
} else {
return Err(anyhow!(
"You cannot declare a variable without initializing it."
));
}
Ok(())
}
fn parse_variable(&mut self, message: &str) -> anyhow::Result<usize> {
self.consume(TokenType::Identifier, message)?;
self.identifier_constant(self.previous_token)
}
fn identifier_constant(&mut self, token: &Token) -> anyhow::Result<usize> {
let name = token.lexeme.clone();
let index = self.chunk.add_constant(Value::String(name));
Ok(index)
}
fn define_variable(&mut self, index: usize) -> anyhow::Result<()> {
self.emit_bytes(OP_DEFINE, index as u16);
Ok(())
}
fn statement(&mut self) -> anyhow::Result<()> {
if self.match_token(TokenType::Print) {
@ -62,6 +100,7 @@ impl<'a> Compiler<'a> {
}
fn expression_statement(&mut self) -> anyhow::Result<()> {
debug!("expression statement");
self.expression()?;
self.emit_byte(OP_POP);
Ok(())
@ -69,7 +108,7 @@ impl<'a> Compiler<'a> {
fn print_statement(&mut self) -> anyhow::Result<()> {
self.expression()?;
// self.consume(TokenType::Semicolon, "Expect ';' after value.")?;
self.consume(TokenType::Eol, "No further expressions expected. Please continue on a new line after the first.\n")?;
self.emit_byte(OP_PRINT);
Ok(())
}
@ -97,7 +136,12 @@ impl<'a> Compiler<'a> {
if token_type == self.current_token.token_type {
self.advance()
} else {
Err(anyhow!("{}", message))
Err(anyhow!(
r#"{} at line {}: "{}""#,
message,
self.current_token.line + 1,
self.source[self.current_token.line]
))
}
}
@ -116,6 +160,7 @@ impl<'a> Compiler<'a> {
fn expression(&mut self) -> anyhow::Result<()> {
self.parse_precedence(PREC_ASSIGNMENT)?;
Ok(())
}
@ -182,14 +227,19 @@ fn number(s: &mut Compiler) -> anyhow::Result<()> {
fn literal(s: &mut Compiler) -> anyhow::Result<()> {
match s.previous_token.token_type {
TokenType::False => s.emit_byte(OP_FALSE),
TokenType::True => s.emit_byte(OP_TRUE),
TokenType::False => s.emit_constant(Value::Bool(false)),
TokenType::True => s.emit_constant(Value::Bool(true)),
TokenType::String => s.emit_constant(Value::String(s.previous_token.lexeme.clone())),
_ => {}
}
Ok(())
}
fn skip(s: &mut Compiler) -> anyhow::Result<()> {
// s.advance()
Ok(())
}
fn grouping(s: &mut Compiler) -> anyhow::Result<()> {
s.expression()?;
s.consume(TokenType::RightParen, "Expect ')' after expression.")
@ -237,6 +287,12 @@ fn binary(s: &mut Compiler) -> anyhow::Result<()> {
Ok(())
}
fn variable(s: &mut Compiler) -> anyhow::Result<()> {
let index = s.identifier_constant(s.previous_token)?;
s.emit_bytes(OP_GET, index as u16);
Ok(())
}
fn get_rule(operator_type: &TokenType) -> &'static Rule {
debug!("{:?}", operator_type);
RULES.get(operator_type).unwrap()
@ -244,32 +300,28 @@ fn get_rule(operator_type: &TokenType) -> &'static Rule {
static RULES: LazyLock<HashMap<TokenType, Rule>> = LazyLock::new(|| {
let mut rules: HashMap<TokenType, Rule> = HashMap::new();
rules.insert(
TokenType::LeftParen,
Rule::new(Some(binary), None, PREC_NONE),
);
rules.insert(TokenType::RightParen, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::LeftBrace, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::RightBrace, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::LeftBracket, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::RightBracket, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Comma, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Dot, Rule::new(None, None, PREC_NONE));
rules.insert(
TokenType::Minus,
Rule::new(Some(unary), Some(binary), PREC_TERM),
);
rules.insert(TokenType::Plus, Rule::new(None, Some(binary), PREC_TERM));
rules.insert(TokenType::Colon, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Slash, Rule::new(None, Some(binary), PREC_FACTOR));
rules.insert(TokenType::Star, Rule::new(None, Some(binary), PREC_FACTOR));
rules.insert(TokenType::Bang, Rule::new(Some(unary), None, PREC_UNARY));
rules.insert(TokenType::BangEqual, Rule::new(None, None, PREC_EQUALITY));
rules.insert(TokenType::BitOr, Rule::new(None, Some(binary), PREC_BITOR));
rules.insert(
TokenType::BitXor,
Rule::new(None, Some(binary), PREC_BITXOR),
);
rules.insert(TokenType::Colon, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Comma, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::DateType, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Dot, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Else, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Eof, Rule::new(Some(skip), None, PREC_NONE));
rules.insert(TokenType::Eol, Rule::new(Some(skip), None, PREC_NONE));
rules.insert(TokenType::Equal, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::False, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Fn, Rule::new(None, None, PREC_NONE));
rules.insert(
TokenType::EqualEqual,
Rule::new(None, Some(binary), PREC_EQUALITY),
);
rules.insert(TokenType::False, Rule::new(Some(literal), None, PREC_NONE));
rules.insert(
TokenType::Greater,
Rule::new(None, Some(binary), PREC_COMPARISON),
@ -282,6 +334,19 @@ static RULES: LazyLock<HashMap<TokenType, Rule>> = LazyLock::new(|| {
TokenType::GreaterGreater,
Rule::new(None, Some(binary), PREC_BITSHIFT),
);
rules.insert(TokenType::I32Type, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::I64Type, Rule::new(None, None, PREC_NONE));
rules.insert(
TokenType::Identifier,
Rule::new(Some(variable), None, PREC_NONE),
);
rules.insert(TokenType::Indent, Rule::new(Some(skip), None, PREC_NONE));
rules.insert(TokenType::LeftBrace, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::LeftBracket, Rule::new(None, None, PREC_NONE));
rules.insert(
TokenType::LeftParen,
Rule::new(Some(binary), None, PREC_NONE),
);
rules.insert(
TokenType::Less,
Rule::new(None, Some(binary), PREC_COMPARISON),
@ -294,41 +359,35 @@ static RULES: LazyLock<HashMap<TokenType, Rule>> = LazyLock::new(|| {
TokenType::LessLess,
Rule::new(None, Some(binary), PREC_BITSHIFT),
);
rules.insert(TokenType::Identifier, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::String, Rule::new(Some(literal), None, PREC_NONE));
rules.insert(TokenType::Number, Rule::new(Some(number), None, PREC_NONE));
rules.insert(
TokenType::LogicalAnd,
Rule::new(None, Some(binary), PREC_AND),
);
rules.insert(TokenType::LogicalOr, Rule::new(None, Some(binary), PREC_OR));
rules.insert(
TokenType::Minus,
Rule::new(Some(unary), Some(binary), PREC_TERM),
);
rules.insert(TokenType::Number, Rule::new(Some(number), None, PREC_NONE));
rules.insert(TokenType::Plus, Rule::new(None, Some(binary), PREC_TERM));
rules.insert(TokenType::Print, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Return, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::RightParen, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::RightBrace, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::RightBracket, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Slash, Rule::new(None, Some(binary), PREC_FACTOR));
rules.insert(TokenType::Star, Rule::new(None, Some(binary), PREC_FACTOR));
rules.insert(TokenType::String, Rule::new(Some(literal), None, PREC_NONE));
rules.insert(
TokenType::BitAnd,
Rule::new(None, Some(binary), PREC_BITAND),
);
rules.insert(TokenType::BitOr, Rule::new(None, Some(binary), PREC_BITOR));
rules.insert(
TokenType::BitXor,
Rule::new(None, Some(binary), PREC_BITXOR),
);
rules.insert(TokenType::Fn, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::StringType, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Struct, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Else, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::False, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::True, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::While, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Print, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Return, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::Eof, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::True, Rule::new(Some(literal), None, PREC_NONE));
rules.insert(TokenType::U32Type, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::U64Type, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::I32Type, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::I64Type, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::DateType, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::StringType, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::False, Rule::new(Some(literal), None, PREC_NONE));
rules.insert(TokenType::True, Rule::new(Some(literal), None, PREC_NONE));
rules.insert(TokenType::Indent, Rule::new(None, None, PREC_NONE));
rules.insert(TokenType::While, Rule::new(None, None, PREC_NONE));
rules
});

View file

@ -2,19 +2,28 @@ use crate::tokens::TokenType;
pub(crate) fn get_keyword(lexeme: &str) -> Option<TokenType> {
match lexeme {
"fn" => Some(TokenType::Fn),
"struct" => Some(TokenType::Struct),
"u32" => Some(TokenType::U32Type),
"string" => Some(TokenType::StringType),
"date" => Some(TokenType::DateType),
"print" => Some(TokenType::Print),
"and" => Some(TokenType::LogicalAnd),
"bool" => Some(TokenType::BoolType),
"char" => Some(TokenType::CharType),
"date" => Some(TokenType::DateType),
"else" => Some(TokenType::Else),
"false" => Some(TokenType::False),
"true" => Some(TokenType::True),
"fn" => Some(TokenType::Fn),
"for" => Some(TokenType::For),
"if" => Some(TokenType::If),
"i32" => Some(TokenType::I32Type),
"i64" => Some(TokenType::I64Type),
"let" => Some(TokenType::Let),
"list" => Some(TokenType::ListType),
"map" => Some(TokenType::MapType),
"or" => Some(TokenType::LogicalOr),
"object" => Some(TokenType::ObjectType),
"print" => Some(TokenType::Print),
"struct" => Some(TokenType::Struct),
"string" => Some(TokenType::StringType),
"true" => Some(TokenType::True),
"u32" => Some(TokenType::U32Type),
"u64" => Some(TokenType::U64Type),
"while" => Some(TokenType::While),
_ => None,

View file

@ -1,12 +1,23 @@
fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let chunk = crudlang::compiler::compile("\"hello \" + 42")?;
let chunk = crudlang::compiler::compile(
r#"let a = "hello " + 42
print a print a
print a"#,
);
match chunk {
Err(e) => {
println!("{}", e);
return Ok(());
}
Ok(chunk) => {
chunk.disassemble();
let result = crudlang::vm::interpret(chunk);
println!("{:?}", result);
Ok(())
let result = crudlang::vm::interpret(chunk)?;
println!("{}", result);
}
}
Ok(())
}

View file

@ -25,6 +25,7 @@ impl Scanner {
self.start = self.current;
self.scan_token();
}
self.add_token(TokenType::Eol);
self.add_token(TokenType::Eof);
self.tokens
}
@ -103,6 +104,7 @@ impl Scanner {
'\n' => {
self.line += 1;
self.new_line = true;
self.add_token(TokenType::Eol);
}
'&' => {
let t = if self.match_next('&') {

View file

@ -22,60 +22,66 @@ enum Value {
#[derive(Debug, PartialEq, Clone, Copy, Hash)]
pub(crate) enum TokenType {
Error,
LeftParen,
RightParen,
LeftBrace,
RightBrace,
LeftBracket,
RightBracket,
Colon,
Semicolon,
Comma,
Dot,
Star,
Slash,
Plus,
Minus,
Not,
Hash,
Bang,
BangEqual,
EqualEqual,
Equal,
Greater,
Less,
GreaterEqual,
GreaterGreater,
LessLess,
LessEqual,
Indent,
Identifier,
String,
Number,
LogicalAnd,
LogicalOr,
BitAnd,
BitOr,
BitXor,
Fn,
Struct,
BoolType,
CharType,
Colon,
Comma,
DateType,
Dot,
Else,
False,
True,
Null,
If,
While,
For,
Return,
Print,
Eof,
U32Type,
U64Type,
Eol,
Equal,
EqualEqual,
Error,
False,
Fn,
For,
Greater,
GreaterEqual,
GreaterGreater,
Hash,
I32Type,
I64Type,
DateType,
If,
Indent,
Identifier,
LeftBrace,
LeftBracket,
LeftParen,
Less,
LessEqual,
LessLess,
Let,
ListType,
MapType,
LogicalAnd,
LogicalOr,
Minus,
Not,
Number,
ObjectType,
Plus,
Print,
Return,
RightParen,
RightBrace,
RightBracket,
Semicolon,
Slash,
Star,
String,
StringType,
Struct,
True,
U32Type,
U64Type,
While,
}
impl Eq for TokenType {

View file

@ -1,13 +1,15 @@
use crate::chunk::Chunk;
use crate::value::Value;
use anyhow::anyhow;
use std::collections::HashMap;
use tracing::debug;
pub fn interpret(chunk: Chunk) -> Result {
pub fn interpret(chunk: Chunk) -> anyhow::Result<Value> {
let mut vm = Vm {
chunk,
ip: 0,
stack: vec![],
local_vars: HashMap::new(),
};
vm.run()
}
@ -16,26 +18,21 @@ pub struct Vm {
chunk: Chunk,
ip: usize,
stack: Vec<Value>,
local_vars: HashMap<String, Value>,
}
impl Vm {
fn run(&mut self) -> Result {
fn run(&mut self) -> anyhow::Result<Value> {
loop {
debug!("[");
for value in self.stack.iter() {
debug!("{:?} ", value);
}
debug!("]");
debug!("{:?}", self.stack);
let opcode = self.chunk.code[self.ip];
self.ip += 1;
match opcode {
OP_CONSTANT => {
OP_CONSTANT | OP_FALSE | OP_TRUE => {
let value = &self.chunk.constants[self.chunk.code[self.ip] as usize];
self.ip += 1;
self.push(value.clone());
}
OP_FALSE => self.push(Value::Bool(false)),
OP_TRUE => self.push(Value::Bool(true)),
OP_ADD => binary_op(self, |a, b| a + b),
OP_SUBTRACT => binary_op(self, |a, b| a - b),
OP_MULTIPLY => binary_op(self, |a, b| a * b),
@ -60,10 +57,11 @@ impl Vm {
OP_BITXOR => binary_op(self, |a, b| a ^ b),
OP_NEGATE => unary_op(self, |a| -a),
OP_RETURN => {
debug!("return {:?}", self.stack);
return if self.stack.is_empty() {
Result::Ok(Value::Void)
Ok(Value::Void)
} else {
return Result::Ok(self.pop());
Ok(self.pop())
};
}
OP_SHL => binary_op(self, |a, b| a << b),
@ -74,14 +72,32 @@ impl Vm {
OP_LESS => binary_op(self, |a, b| Ok(Value::Bool(a < b))),
OP_LESS_EQUAL => binary_op(self, |a, b| Ok(Value::Bool(a <= b))),
OP_PRINT => {
debug!("print {:?}", self.stack);
let v = self.pop();
println!("{}", v);
}
OP_DEFINE => {
let name = self.read_constant();
let value = self.pop();
self.local_vars.insert(name, value);
}
OP_GET => {
let name = self.read_constant();
let value = self.local_vars.get(&name).unwrap();
self.push(value.clone()); // not happy
debug!("after get {:?}", self.stack);
}
_ => {}
}
}
}
fn read_constant(&mut self) -> String {
let name = self.chunk.constants[self.chunk.code[self.ip] as usize].to_string();
self.ip += 1;
name
}
fn reset_stack(&mut self) {
self.stack.clear();
}
@ -117,13 +133,6 @@ fn unary_op(stack: &mut Vm, op: impl Fn(&Value) -> anyhow::Result<Value> + Copy)
}
}
#[derive(Debug)]
pub enum Result {
Ok(Value),
CompileError,
Error,
}
pub const OP_CONSTANT: u16 = 1;
pub const OP_ADD: u16 = 2;
pub const OP_SUBTRACT: u16 = 3;
@ -149,3 +158,5 @@ pub const OP_BITXOR: u16 = 22;
pub const OP_SHR: u16 = 23;
pub const OP_SHL: u16 = 24;
pub const OP_POP: u16 = 25;
pub const OP_DEFINE: u16 = 26;
pub const OP_GET: u16 = 27;