range loop working

This commit is contained in:
Shautvast 2025-11-17 07:11:38 +01:00
parent db183e95c4
commit 8c728f6acb
11 changed files with 246 additions and 47 deletions

View file

@ -1,12 +1,18 @@
use crate::ast_compiler::Expression::{
FieldGet, FunctionCall, ListGet, MapGet, MethodCall, NamedParameter, Stop, Variable,
Assignment, FieldGet, FunctionCall, ListGet, MapGet, MethodCall, NamedParameter, Stop, Variable,
};
use crate::errors::CompilerError::{
self, Expected, ParseError, TooManyParameters, UnexpectedIndent, UninitializedVariable,
};
use crate::errors::CompilerErrorAtLine;
use crate::symbol_builder::{Symbol, calculate_type, infer_type};
use crate::tokens::TokenType::{Bang, Bool, Char, Colon, DateTime, Dot, Eof, Eol, Equal, False, FloatingPoint, Fn, Greater, GreaterEqual, GreaterGreater, Identifier, If, Indent, Integer, LeftBrace, LeftBracket, LeftParen, Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus, Print, RightBrace, RightBracket, RightParen, SingleRightArrow, Slash, Star, StringType, True, U32, U64, Unknown, Else};
use crate::tokens::TokenType::{
Bang, Bool, Char, Colon, DateTime, Dot, Else, Eof, Eol, Equal, False, FloatingPoint, Fn, For,
Greater, GreaterEqual, GreaterGreater, Identifier, If, In, Indent, Integer, LeftBrace,
LeftBracket, LeftParen, Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus,
Print, Range, RightBrace, RightBracket, RightParen, SingleRightArrow, Slash, Star, StringType,
True, U32, U64, Unknown,
};
use crate::tokens::{Token, TokenType};
use crate::value::Value;
use crate::{Expr, Stmt, SymbolTable};
@ -66,6 +72,9 @@ impl AstCompiler {
if !self.had_error {
let mut statements = vec![];
while !self.is_at_end() {
if self.match_token(&[Eol]) {
continue;
}
let statement = self.indent(symbol_table)?;
if let Some(statement) = statement {
statements.push(statement);
@ -316,11 +325,29 @@ impl AstCompiler {
self.print_statement(symbol_table)
} else if self.match_token(&[If]) {
self.if_statement(symbol_table)
} else if self.match_token(&[For]) {
self.for_statement(symbol_table)
} else {
self.expr_statement(symbol_table)
}
}
fn for_statement(&mut self, symbol_table: &mut SymbolTable) -> Stmt {
let loop_var = self.consume(&Identifier, Expected("loop variable name."))?;
self.consume(&In, Expected("'in' after loop variable name."))?;
let range = self.expression(symbol_table)?;
self.consume(&Colon, Expected("colon after range expression"))?;
self.consume(&Eol, Expected("end of line after for expression."))?;
self.inc_indent();
let body = self.compile(symbol_table)?;
Ok(Statement::ForStatement {
loop_var,
range,
body,
})
}
fn if_statement(&mut self, symbol_table: &mut SymbolTable) -> Stmt {
let condition = self.expression(symbol_table)?;
self.consume(&Colon, Expected("':' after if condition."))?;
@ -337,7 +364,11 @@ impl AstCompiler {
} else {
None
};
Ok(Statement::IfStatement {condition, then_branch, else_branch})
Ok(Statement::IfStatement {
condition,
then_branch,
else_branch,
})
}
fn inc_indent(&mut self) {
@ -389,7 +420,21 @@ impl AstCompiler {
fn assignment(&mut self, symbol_table: &mut SymbolTable) -> Expr {
let expr = self.equality(symbol_table)?;
self.binary(&[TokenType::Equal], expr, symbol_table)
if self.match_token(&[Equal]) {
let operator = self.previous().clone();
let right = self.comparison(symbol_table)?;
if let Variable { name, .. } = expr {
Ok(Assignment {
line: operator.line,
variable_name: name.to_string(),
value: Box::new(right),
})
} else {
Err(self.raise(CompilerError::Failure))
}
} else {
Ok(expr)
}
}
fn equality(&mut self, symbol_table: &mut SymbolTable) -> Expr {
@ -402,7 +447,7 @@ impl AstCompiler {
}
fn comparison(&mut self, symbol_table: &mut SymbolTable) -> Expr {
let expr = self.bitshift(symbol_table)?;
let expr = self.range(symbol_table)?;
self.binary(
&[Greater, GreaterEqual, Less, LessEqual],
expr,
@ -410,6 +455,20 @@ impl AstCompiler {
)
}
fn range(&mut self, symbol_table: &mut SymbolTable) -> Expr {
let mut expr = self.bitshift(symbol_table)?;
if self.match_token(&[Range]) {
let operator = self.previous().clone();
let right = self.expression(symbol_table)?;
expr = Expression::Range {
line: operator.line,
lower: Box::new(expr),
upper: Box::new(right),
};
}
Ok(expr)
}
fn bitshift(&mut self, symbol_table: &mut SymbolTable) -> Expr {
let expr = self.term(symbol_table)?;
self.binary(&[GreaterGreater, LessLess], expr, symbol_table)
@ -808,8 +867,13 @@ pub enum Statement {
IfStatement {
condition: Expression,
then_branch: Vec<Statement>,
else_branch: Option<Vec<Statement>>
}
else_branch: Option<Vec<Statement>>,
},
ForStatement {
loop_var: Token,
range: Expression,
body: Vec<Statement>,
},
}
impl Statement {
@ -822,6 +886,7 @@ impl Statement {
Statement::ObjectStmt { name, .. } => name.line,
Statement::GuardStatement { if_expr, .. } => if_expr.line(),
Statement::IfStatement { condition, .. } => condition.line(),
Statement::ForStatement { loop_var, .. } => loop_var.line,
}
}
}
@ -867,6 +932,11 @@ pub enum Expression {
literaltype: TokenType,
value: Value,
},
Range {
line: usize,
lower: Box<Expression>,
upper: Box<Expression>,
},
List {
line: usize,
literaltype: TokenType,
@ -882,6 +952,11 @@ pub enum Expression {
name: String,
var_type: TokenType,
},
Assignment {
line: usize,
variable_name: String,
value: Box<Expression>,
},
FunctionCall {
line: usize,
name: String,
@ -922,9 +997,11 @@ impl Expression {
Self::Unary { line, .. } => *line,
Self::Grouping { line, .. } => *line,
Self::Literal { line, .. } => *line,
Self::Range { line, .. } => *line,
Self::List { line, .. } => *line,
Self::Map { line, .. } => *line,
Variable { line, .. } => *line,
Assignment { line, .. } => *line,
FunctionCall { line, .. } => *line,
MethodCall { line, .. } => *line,
Stop { line } => *line,

View file

@ -7,10 +7,10 @@ use crate::errors::{CompilerError, CompilerErrorAtLine};
use crate::symbol_builder::{Symbol, calculate_type, infer_type};
use crate::tokens::TokenType;
use crate::tokens::TokenType::Unknown;
use crate::value::{Value};
use crate::value::Value;
use crate::vm::{
OP_ADD, OP_AND, OP_ASSIGN, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CALL, OP_CALL_BUILTIN,
OP_CONSTANT, OP_DEF_LIST, OP_DEF_MAP, OP_DIVIDE, OP_EQUAL, OP_GET, OP_GREATER,
OP_CONSTANT, OP_DEF_LIST, OP_DEF_MAP, OP_DIVIDE, OP_EQUAL, OP_GET, OP_GOTO_IF, OP_GREATER,
OP_GREATER_EQUAL, OP_IF, OP_IF_ELSE, OP_LESS, OP_LESS_EQUAL, OP_LIST_GET, OP_MULTIPLY,
OP_NEGATE, OP_NOT, OP_OR, OP_POP, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT,
};
@ -76,6 +76,7 @@ impl Compiler {
}
}
/// compile the entire AST into a chunk, adding a RETURN OP
pub(crate) fn compile(
&mut self,
ast: &Vec<Statement>,
@ -83,20 +84,28 @@ impl Compiler {
registry: &mut Registry,
namespace: &str,
) -> Result<Chunk, CompilerErrorAtLine> {
for statement in ast {
self.compile_statement(statement, symbols, registry, namespace)?;
}
self.compile_statements(ast, symbols, registry, namespace)?;
self.emit_byte(OP_RETURN);
let chunk = self.chunk.clone();
self.chunk.code.clear(); // in case the compiler is reused, clear it for the next compilation. This is for the REPL
Ok(chunk)
}
fn raise(&self, error: CompilerError) -> CompilerErrorAtLine {
CompilerErrorAtLine::raise(error, self.current_line)
/// compile the entire AST into a chunk
fn compile_statements(
&mut self,
ast: &Vec<Statement>,
symbols: &SymbolTable,
registry: &mut Registry,
namespace: &str,
) -> Result<(), CompilerErrorAtLine> {
for statement in ast {
self.compile_statement(statement, symbols, registry, namespace)?;
}
Ok(())
}
/// compile a single statement
fn compile_statement(
&mut self,
statement: &Statement,
@ -174,6 +183,38 @@ impl Compiler {
)?;
}
}
Statement::ForStatement {
loop_var,
range,
body,
} => {
// 1. step var index
let step_const_index = self.emit_constant(Value::I64(1));
// 2. range expression
self.compile_expression(namespace, range, symbols, registry)?;
//save the constants for lower and upper bounds of the range
let start_index = self.chunk.constants.len() - 1;
let end_index = self.chunk.constants.len() - 2;
let name = loop_var.lexeme.as_str();
let loop_var_name_index = self.chunk.add_var(&loop_var.token_type, name);
self.vars.insert(name.to_string(), loop_var_name_index);
// 3. start index
self.emit_bytes(OP_CONSTANT, start_index as u16);
self.emit_bytes(OP_ASSIGN, loop_var_name_index as u16);
let return_addr = self.chunk.code.len();
self.compile_statements(body, symbols, registry, namespace)?;
self.emit_bytes(OP_GET, loop_var_name_index as u16);
self.emit_bytes(OP_CONSTANT, step_const_index);
self.emit_byte(OP_ADD);
self.emit_bytes(OP_ASSIGN, loop_var_name_index as u16);
self.emit_bytes(OP_CONSTANT, end_index as u16);
self.emit_bytes(OP_GET, loop_var_name_index as u16);
self.emit_byte(OP_GREATER_EQUAL);
self.emit_bytes(OP_GOTO_IF, return_addr as u16);
}
}
Ok(())
}
@ -262,6 +303,15 @@ impl Compiler {
return Err(self.raise(UndeclaredVariable(name.to_string())));
}
}
Expression::Assignment { variable_name, value, .. } => {
self.compile_expression(namespace, value, symbols, registry)?;
let name_index = self.vars.get(variable_name);
if let Some(name_index) = name_index {
self.emit_bytes(OP_ASSIGN, *name_index as u16);
} else {
return Err(self.raise(UndeclaredVariable(variable_name.to_string())));
}
}
Expression::Literal { value, .. } => {
self.emit_constant(value.clone());
}
@ -343,6 +393,11 @@ impl Compiler {
}
Expression::MapGet { .. } => {}
Expression::FieldGet { .. } => {}
Expression::Range { lower, upper, .. } => {
// opposite order, because we have to assign last one first to the loop variable
self.compile_expression(namespace, upper, symbols, registry)?;
self.compile_expression(namespace, lower, symbols, registry)?;
}
}
Ok(())
}
@ -393,4 +448,8 @@ impl Compiler {
self.emit_bytes(OP_CONSTANT, index);
index
}
fn raise(&self, error: CompilerError) -> CompilerErrorAtLine {
CompilerErrorAtLine::raise(error, self.current_line)
}
}

View file

@ -5,7 +5,7 @@ use crate::vm::{
OP_ADD, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CALL, OP_CONSTANT, OP_DEF_BOOL, OP_DEF_F32,
OP_DEF_F64, OP_DEF_I32, OP_DEF_I64, OP_DEF_LIST, OP_DEF_MAP, OP_DEF_STRING, OP_DEFINE,
OP_DIVIDE, OP_EQUAL, 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_NEGATE, OP_NOT, OP_POP, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT,OP_ASSIGN,OP_GOTO_IF
};
use std::collections::HashMap;
@ -117,9 +117,11 @@ impl Chunk {
OP_DEF_F64 => self.constant_inst("DEFF64", offset),
OP_DEF_BOOL => self.constant_inst("DEFBOOL", offset),
OP_CALL => self.call_inst("CALL", offset),
OP_GET => self.constant_inst("GET", offset),
OP_GET => self.assign_inst("GET", offset),
OP_DEF_LIST => self.new_inst("DEFLIST", offset),
OP_DEF_MAP => self.new_inst("DEFMAP", offset),
OP_ASSIGN => self.assign_inst("ASSIGN", offset),
OP_GOTO_IF => self.constant_inst("GOTO_IF", offset),
_ => {
println!("Unknown instruction {}", instruction);
offset + 1
@ -132,6 +134,13 @@ impl Chunk {
offset + 1
}
fn assign_inst(&self, op: &str, offset: usize) -> usize {
let constant = self.code[offset + 1];
print!("{} {}:", op, constant);
println!("{}",self.vars.get(constant as usize).unwrap().1);
offset + 2
}
fn call_inst(&self, op: &str, offset: usize) -> usize {
let constant = self.code[offset + 1];
let num_args = self.code[offset + 2];

View file

@ -156,7 +156,7 @@ object Person:
let p = Person(name: 0x42)
p"#);
assert!(r.is_err());
// assert!(r.is_err());
assert_eq!(
r#"Compilation failed: error at line 5, Expected string, found integer"#,
format!("{}", r.unwrap_err().to_string())
@ -386,6 +386,24 @@ else:
);
}
#[test]
fn inline_comment(){
assert_eq!(run(r#"// this is a comment"#), Ok(Value::Void));
}
#[test]
fn range_loop() {
assert_eq!(
run(r#"
let sum=0
for a in 1..4:
sum = sum + a
sum
"#),
Ok(Value::I64(10))
);
}
// #[test]
// fn package() {
// assert_eq!(run(r#"a.b.c()"#), Ok(Value::U32(48)));

View file

@ -13,6 +13,7 @@ pub(crate) fn get_keyword(lexeme: &str) -> Option<TokenType> {
"fn" => Some(TokenType::Fn),
"for" => Some(TokenType::For),
"if" => Some(TokenType::If),
"in" => Some(TokenType::In),
"i32" => Some(TokenType::I32),
"i64" => Some(TokenType::I64),
"let" => Some(TokenType::Let),

View file

@ -7,6 +7,7 @@ use crate::symbol_builder::Symbol;
use std::collections::HashMap;
use std::fs;
use walkdir::WalkDir;
use crate::value::Value::Void;
pub mod ast_compiler;
mod builtins;
@ -92,6 +93,7 @@ pub(crate) fn run(src: &str) -> Result<value::Value, CrudLangError> {
symbol_builder::build("", &ast, &mut symbol_table);
let mut registry = HashMap::new();
bytecode_compiler::compile(None, &ast, &symbol_table, &mut registry)?;
let registry = arc_swap::ArcSwap::from(std::sync::Arc::new(registry));
vm::interpret(registry.load(), "main").map_err(CrudLangError::from)
}

View file

@ -2,7 +2,7 @@ use crate::chunk::Chunk;
use crate::errors::CrudLangError;
use crate::scanner::scan;
use crate::vm::Vm;
use crate::{ast_compiler, bytecode_compiler, map_underlying, recompile, symbol_builder};
use crate::{ast_compiler, bytecode_compiler, map_underlying, symbol_builder};
use arc_swap::ArcSwap;
use std::collections::HashMap;
use std::io;

View file

@ -142,7 +142,7 @@ impl Scanner {
if c == '0' && self.peek() == 'x' {
self.hex_number()?;
} else if c.is_ascii_digit() {
self.number();
self.number_or_range();
} else if is_alpha(c) {
self.identifier();
} else {
@ -181,7 +181,7 @@ impl Scanner {
Ok(())
}
fn number(&mut self) {
fn number_or_range(&mut self) {
while self.peek().is_ascii_digit() {
self.advance();
}
@ -190,13 +190,30 @@ impl Scanner {
has_dot = true;
self.advance();
}
if self.peek() == '.' && self.peek_next() == '.' {
self.range_expression()
} else {
while is_digit_or_scientific(self.peek()) {
self.advance();
}
let value: String = self.chars[self.start..self.current].iter().collect();
self.add_token_with_value(if has_dot { FloatingPoint } else { Integer }, value);
}
}
fn range_expression(&mut self) {
let lower: String = self.chars[self.start..self.current].iter().collect();
self.match_next('.');
self.match_next('.');
self.add_token_with_value(Integer, lower);
self.add_token(TokenType::Range);
self.start = self.current;
while self.peek().is_ascii_digit() {
self.advance();
}
let upper: String = self.chars[self.start..self.current].iter().collect();
self.add_token_with_value(Integer, upper);
}
fn char(&mut self) -> Result<(), CompilerErrorAtLine> {
while self.peek() != '\'' && !self.is_at_end() {
@ -265,8 +282,12 @@ impl Scanner {
}
fn peek_next(&self) -> char {
if self.current + 1 >= self.chars.len() {
'\0'
} else {
self.chars[self.current + 1]
}
}
fn match_next(&mut self, expected: char) -> bool {
if self.is_at_end() || self.chars[self.current] != expected {
@ -304,7 +325,6 @@ struct Scanner {
new_line: bool,
}
fn is_digit_or_scientific(c: char) -> bool {
c.is_ascii_digit() || c == 'e' || c == 'E'
}

View file

@ -1,5 +1,5 @@
use crate::ast_compiler::{Expression, Parameter, Statement};
use crate::builtins::{Signature, lookup};
use crate::builtins::lookup;
use crate::errors::CompilerError;
use crate::errors::CompilerError::IncompatibleTypes;
use crate::tokens::TokenType::{
@ -186,6 +186,7 @@ pub fn infer_type(expr: &Expression, symbols: &HashMap<String, Symbol>) -> Token
}
}
Expression::Variable { var_type, .. } => var_type.clone(),
Expression::Assignment { value, .. } => infer_type(value, symbols),
Expression::FunctionCall { name, .. } => {
let symbol = symbols.get(name);
match symbol {
@ -215,5 +216,6 @@ pub fn infer_type(expr: &Expression, symbols: &HashMap<String, Symbol>) -> Token
Expression::ListGet { .. } => TokenType::Unknown,
Expression::MapGet { .. } => TokenType::Unknown,
Expression::FieldGet { .. } => TokenType::Unknown,
Expression::Range { lower, .. } => infer_type(lower, symbols),
}
}

View file

@ -51,6 +51,7 @@ pub enum TokenType {
I64,
Identifier,
If,
In,
Indent,
Integer,
SignedInteger,
@ -73,6 +74,7 @@ pub enum TokenType {
Plus,
Print,
Question,
Range,
Return,
RightParen,
RightBrace,
@ -132,6 +134,7 @@ impl fmt::Display for TokenType {
TokenType::Hex => write!(f, "0x"),
TokenType::If => write!(f, "if"),
TokenType::Identifier => write!(f, "identifier"),
TokenType::In => write!(f, "in"),
TokenType::Indent => write!(f, "indent"),
TokenType::Integer => write!(f, "integer"),
TokenType::LeftBrace => write!(f, "{{"),
@ -150,6 +153,7 @@ impl fmt::Display for TokenType {
TokenType::Plus => write!(f, "+"),
TokenType::Print => write!(f, "print"),
TokenType::Question => write!(f, "?"),
TokenType::Range => write!(f, ".."),
TokenType::Return => write!(f, "return"),
TokenType::RightParen => write!(f, ")"),
TokenType::RightBrace => write!(f, "}}"),

View file

@ -1,3 +1,4 @@
use crate::Registry;
use crate::chunk::Chunk;
use crate::errors::RuntimeError::Something;
use crate::errors::{RuntimeError, ValueError};
@ -7,7 +8,6 @@ use arc_swap::Guard;
use std::collections::HashMap;
use std::sync::Arc;
use tracing::debug;
use crate::Registry;
pub struct Vm {
ip: usize,
@ -17,14 +17,15 @@ pub struct Vm {
pub(crate) registry: Arc<HashMap<String, Chunk>>,
}
pub fn interpret(
registry: Guard<Arc<HashMap<String, Chunk>>>,
function: &str,
) -> Result<Value, RuntimeError> {
let chunk = registry.get(function).unwrap().clone();
// chunk.disassemble();
let mut vm = Vm::new(&registry);
vm.run(&get_context(function), &chunk)
// Ok(Value::Void)
}
pub async fn interpret_async(
@ -157,7 +158,7 @@ impl Vm {
let (var_type, name) = chunk.vars.get(index).unwrap();
let value = self.pop();
let value = Self::number(var_type, value)?;
self.local_vars.insert(name.to_string(), value);
self.local_vars.insert(name.to_string(), value); //insert or update
}
OP_DEF_MAP => {
let len = self.read(chunk);
@ -172,7 +173,7 @@ impl Vm {
OP_GET => {
let var_index = self.read(chunk);
let (_, name_index) = chunk.vars.get(var_index).unwrap();
let value = self.local_vars.remove(name_index).unwrap();
let value = self.local_vars.get(name_index).unwrap().clone();
self.push(value);
}
OP_LIST_GET => {
@ -196,7 +197,8 @@ impl Vm {
}
args.reverse();
let receiver = self.pop();
let return_value = crate::builtins::call(&receiver_type_name, &function_name, receiver, args)?;
let return_value =
crate::builtins::call(&receiver_type_name, &function_name, receiver, args)?;
self.push(return_value);
}
OP_POP => {
@ -232,10 +234,7 @@ impl Vm {
}
let mut fields = vec![];
params
.iter()
.zip(args)
.for_each(|(param, arg)| {
params.iter().zip(args).for_each(|(param, arg)| {
fields.push((param.name.lexeme.clone(), arg))
});
let new_instance = Value::ObjectType(Box::new(Object {
@ -276,6 +275,13 @@ impl Vm {
}
});
}
OP_GOTO_IF => {
let b = self.pop();
let goto_addr = self.read(chunk);
if b == Value::Bool(true) {
self.ip = goto_addr;
}
}
_ => {}
}
}
@ -389,3 +395,4 @@ pub const OP_LIST_GET: u16 = 42;
pub const OP_CALL_BUILTIN: u16 = 43;
pub const OP_IF: u16 = 44;
pub const OP_IF_ELSE: u16 = 45;
pub const OP_GOTO_IF: u16 = 46;