From 61b9d86aa2ac74d6a288f9d66e2fe4fc921ba331 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Sun, 9 Nov 2025 21:56:59 +0100 Subject: [PATCH] Indexing into lists/maps. First test with literal list completed. When it's assigned to a var if fails --- src/ast_compiler.rs | 159 ++++++++++++++++++++++++++++++--------- src/bytecode_compiler.rs | 22 ++++-- src/compiler_tests.rs | 25 +++++- src/errors.rs | 8 +- src/symbol_builder.rs | 7 +- src/tokens.rs | 1 - src/vm.rs | 10 +++ 7 files changed, 182 insertions(+), 50 deletions(-) diff --git a/src/ast_compiler.rs b/src/ast_compiler.rs index adeeb1b..bc40a04 100644 --- a/src/ast_compiler.rs +++ b/src/ast_compiler.rs @@ -1,12 +1,14 @@ -use crate::ast_compiler::Expression::{FunctionCall, NamedParameter, Stop, Variable}; +use crate::ast_compiler::Expression::{ + FieldGet, FunctionCall, ListGet, MapGet, NamedParameter, Stop, Variable, +}; use crate::errors::CompilerError::{ self, Expected, IncompatibleTypes, ParseError, TooManyParameters, TypeError, UndeclaredVariable, UnexpectedIndent, UninitializedVariable, }; use crate::errors::CompilerErrorAtLine; use crate::tokens::TokenType::{ - Bang, Bool, Char, Colon, DateTime, Dot, Eof, Eol, Equal, F32, F64, False, FloatingPoint, - Fn, Greater, GreaterEqual, GreaterGreater, I32, I64, Identifier, Indent, Integer, LeftBrace, + Bang, Bool, Char, Colon, DateTime, Dot, Eof, Eol, Equal, F32, F64, False, FloatingPoint, Fn, + Greater, GreaterEqual, GreaterGreater, I32, I64, Identifier, Indent, Integer, LeftBrace, LeftBracket, LeftParen, Less, LessEqual, LessLess, Let, ListType, MapType, Minus, Object, Plus, Print, RightBrace, RightBracket, RightParen, SignedInteger, SingleRightArrow, Slash, Star, StringType, True, U32, U64, UnsignedInteger, @@ -14,6 +16,7 @@ use crate::tokens::TokenType::{ use crate::tokens::{Token, TokenType}; use crate::value::Value; use log::debug; +use tokio_postgres::fallible_iterator::FallibleIterator; pub fn compile( path: Option<&str>, @@ -401,10 +404,63 @@ impl AstCompiler { right: Box::new(right), }) } else { - self.primary() + let expr = self.get(); + expr } } + fn get(&mut self) -> Result { + let expr = self.primary()?; + + if self.match_token(vec![LeftParen]) { + let name = self.peek().clone(); + self.advance(); + self.function_call(name) + } else if self.match_token(vec![LeftBracket]) { + let name = self.peek().clone(); + self.advance(); + self.index(expr, name) + } else if self.match_token(vec![Dot]){ + let name = self.peek().clone(); + self.advance(); + self.field(expr, name) + } else { + Ok(expr) + } + } + + fn index( + &mut self, + operand: Expression, + index: Token, + ) -> Result { + let get = (match operand { + Expression::Map { .. } => MapGet { + key: index.lexeme.clone(), + }, + Expression::List { .. } => ListGet { + list: Box::new(operand), + index: index.lexeme.clone().parse().map_err(|_| { + self.raise(CompilerError::IllegalTypeToIndex(index.lexeme.clone())) + })?, + }, + _ => return Err(self.raise(CompilerError::IllegalTypeToIndex("".to_string()))), + }); + self.consume(RightBracket, Expected("']' after index."))?; + Ok(get) + } + + fn field( + &mut self, + operand: Expression, + index: Token, + ) -> Result { + //TODO? + Ok(Expression::FieldGet { + field: index.lexeme.clone(), + }) + } + fn primary(&mut self) -> Result { debug!("primary {:?}", self.peek()); Ok(if self.match_token(vec![LeftBracket]) { @@ -500,34 +556,35 @@ impl AstCompiler { debug!("{:?}", token); // function call? if self.match_token(vec![LeftParen]) { - self.function_call(token.lexeme)? + self.function_call(token.clone())? } else if self.match_token(vec![Colon]) { self.named_parameter(&token)? - } else if self.check(Dot) { - // chain of variable or function lookups? - let mut name = "/".to_string(); - name.push_str(&self.previous().lexeme); - while self.match_token(vec![Dot]) { - name.push_str("/"); - name.push_str(&self.peek().lexeme); - self.advance(); - } - // chained function call? - if self.match_token(vec![LeftParen]) { - self.function_call(name)? - } else { - // empty line - return if self.match_token(vec![Eol, Eof]) { - Ok(Expression::Literal { - value: Value::Void, - literaltype: Object, - line: token.line, - }) - } else { - Err(self.raise(UndeclaredVariable(token.lexeme.clone()))) - }; - } } else { + // } else if self.check(Dot) { + // chain of variable or function lookups? + // let mut name = "/".to_string(); + // name.push_str(&self.previous().lexeme); + // while self.match_token(vec![Dot]) { + // name.push_str("/"); + // name.push_str(&self.peek().lexeme); + // self.advance(); + // } + // chained function call? + // if self.match_token(vec![LeftParen]) { + // self.function_call(name())? + // } else { + // empty line + // return if self.match_token(vec![Eol, Eof]) { + // Ok(Expression::Literal { + // value: Value::Void, + // literaltype: Object, + // line: token.line, + // }) + // } else { + // Err(self.raise(UndeclaredVariable(token.lexeme.clone()))) + // }; + // } + // } else { // none of the above, must be a variable lookup self.variable_lookup(&token)? } @@ -591,7 +648,7 @@ impl AstCompiler { }) } - fn function_call(&mut self, name: String) -> Result { + fn function_call(&mut self, name: Token) -> Result { let mut arguments = vec![]; while !self.match_token(vec![RightParen]) { if arguments.len() >= 25 { @@ -608,7 +665,7 @@ impl AstCompiler { } Ok(FunctionCall { line: self.peek().line, - name, + name: name.lexeme.to_string(), arguments, }) } @@ -759,15 +816,25 @@ pub enum Expression { Stop { line: usize, }, - PathMatch { - line: usize, - condition: Box, - }, + // PathMatch { + // line: usize, + // condition: Box, + // }, NamedParameter { line: usize, name: Token, value: Box, }, + MapGet { + key: String, + }, + ListGet { + list: Box, + index: usize, + }, + FieldGet { + field: String, + }, } impl Expression { @@ -782,8 +849,28 @@ impl Expression { Variable { line, .. } => *line, FunctionCall { line, .. } => *line, Stop { line } => *line, - Expression::PathMatch { line, .. } => *line, + // Expression::PathMatch { line, .. } => *line, NamedParameter { line, .. } => *line, + MapGet { .. } => 0, + ListGet { .. } => 0, + FieldGet { .. } => 0, } } + + // pub fn get_type(&self) -> &str { + // match self { + // Expression::Binary { .. } => "binary", + // Expression::Unary { .. } => TokenType::Unknown, + // Expression::Grouping { .. } => TokenType::Unknown, + // Expression::Literal { literaltype, .. } => literaltype.clone(), + // Expression::List { literaltype, .. } => literaltype.clone(), + // Expression::Map { literaltype, .. } => literaltype.clone(), + // Expression::Variable { var_type, .. } => var_type.clone(), + // Expression::FunctionCall { .. } => TokenType::Unknown, + // Expression::Stop { .. } => TokenType::Unknown, + // Expression::NamedParameter { .. } => TokenType::Unknown, + // Expression::MapGet { .. } => TokenType::Unknown, + // Expression::ListGet { .. } => TokenType::Unknown, + // } + // } } diff --git a/src/bytecode_compiler.rs b/src/bytecode_compiler.rs index ec87905..64b0f5d 100644 --- a/src/bytecode_compiler.rs +++ b/src/bytecode_compiler.rs @@ -1,7 +1,7 @@ use crate::ast_compiler::Expression::NamedParameter; -use crate::ast_compiler::{Expression, Function, Parameter, Statement}; +use crate::ast_compiler::{Expression, Function, Statement}; use crate::chunk::Chunk; -use crate::errors::{CompilerError, CompilerErrorAtLine, RuntimeError}; +use crate::errors::{CompilerError, CompilerErrorAtLine}; use crate::symbol_builder::{Symbol, calculate_type, infer_type}; use crate::tokens::TokenType; use crate::tokens::TokenType::Unknown; @@ -9,7 +9,8 @@ use crate::value::Value; use crate::vm::{ OP_ADD, OP_AND, OP_ASSIGN, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CALL, OP_CONSTANT, OP_DEF_LIST, OP_DEF_MAP, OP_DIVIDE, OP_EQUAL, OP_GET, OP_GREATER, OP_GREATER_EQUAL, OP_LESS, OP_LESS_EQUAL, - OP_MULTIPLY, OP_NEGATE, OP_NOT, OP_OR, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT, + OP_LIST_GET, OP_MULTIPLY, OP_NEGATE, OP_NOT, OP_OR, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, + OP_SUBTRACT, }; use std::collections::HashMap; @@ -220,7 +221,7 @@ impl Compiler { )); } } - Expression::Variable { name, line,.. } => { + Expression::Variable { name, line, .. } => { let name_index = self.vars.get(name); if let Some(name_index) = name_index { self.emit_bytes(OP_GET, *name_index as u16); @@ -292,9 +293,16 @@ impl Compiler { _ => unimplemented!("binary other than plus, minus, star, slash"), } } - Expression::Stop { line } => {} - Expression::PathMatch { line, .. } => {} - Expression::NamedParameter { line, .. } => {} + Expression::Stop { .. } => {} + // Expression::PathMatch { line, .. } => {} + NamedParameter { .. } => {} + Expression::ListGet { index, list} => { + self.compile_expression(namespace, list, symbols, registry)?; + self.emit_byte(OP_LIST_GET); + self.emit_bytes((index >> 16) as u16, *index as u16); + } + Expression::MapGet { .. } => {} + Expression::FieldGet { .. } => {} } Ok(()) } diff --git a/src/compiler_tests.rs b/src/compiler_tests.rs index f6c8172..d5ce06d 100644 --- a/src/compiler_tests.rs +++ b/src/compiler_tests.rs @@ -2,7 +2,7 @@ mod tests { use crate::value::Value; use crate::{compile, run}; - use chrono::{DateTime, FixedOffset, NaiveDate, TimeZone}; + use chrono::{DateTime}; #[test] fn literal_int() { @@ -35,6 +35,17 @@ mod tests { ); } + #[test] + fn index_in_list_literal() { + assert_eq!(run(r#"["abc","def"][0]"#), Ok(Value::String("abc".into()))) + } + + #[test] + fn index_in_list_as_var() { + assert_eq!(run(r#"let a:list = ["abc","def"] +a[1]"#), Ok(Value::String("def".into()))) + } + #[test] fn infer_type() { assert_eq!( @@ -209,11 +220,21 @@ m"#); run(r#"let date:datetime = d"2025-11-09 16:44:28.000 +0100" date"#), Ok(Value::DateTime( - DateTime::parse_from_str("2025-11-09 16:44:28.000 +0100", "%Y-%m-%d %H:%M:%S%.3f %z").unwrap().into() + DateTime::parse_from_str( + "2025-11-09 16:44:28.000 +0100", + "%Y-%m-%d %H:%M:%S%.3f %z" + ) + .unwrap() + .into() )) ); } + // #[test] + // fn string_reverse(){ + // assert_eq!(run(r#""abc".reverse()"#), Ok(Value::String("cba".into()))); + // } + // #[test] // fn package() { // assert_eq!(run(r#"a.b.c()"#), Ok(Value::U32(48))); diff --git a/src/errors.rs b/src/errors.rs index f0b3686..24eceee 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,5 +1,5 @@ +use crate::tokens::TokenType; use std::fmt::Display; -use crate::tokens::{Token, TokenType}; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -64,7 +64,11 @@ pub enum CompilerError { #[error("Crud does not support numbers above 2^64")] Overflow, #[error("Undeclared function: '{0}'")] - FunctionNotFound(String) + FunctionNotFound(String), + #[error("Illegal argument: '{0}' cannot be used as an index into a list")] + IllegalIndexArgument(TokenType), + #[error("Illegal argument: '{0}' cannot be indexed")] + IllegalTypeToIndex(String), } #[derive(Error, Debug, PartialEq)] diff --git a/src/symbol_builder.rs b/src/symbol_builder.rs index 0dfaf0b..646f153 100644 --- a/src/symbol_builder.rs +++ b/src/symbol_builder.rs @@ -75,7 +75,7 @@ pub fn build(path: &str, ast: &[Statement], symbols: &mut HashMap, @@ -225,7 +225,10 @@ pub fn infer_type(expr: &Expression, symbols: &HashMap) -> Token } } Expression::Stop { .. } => TokenType::Unknown, - Expression::PathMatch { .. } => TokenType::Unknown, + // Expression::PathMatch { .. } => TokenType::Unknown, Expression::NamedParameter { .. } => TokenType::Unknown, + Expression::ListGet { .. } => TokenType::Unknown, + Expression::MapGet { .. } => TokenType::Unknown, + Expression::FieldGet { .. } => TokenType::Unknown, } } diff --git a/src/tokens.rs b/src/tokens.rs index e9e9f10..3af70ac 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -156,7 +156,6 @@ impl fmt::Display for TokenType { TokenType::SingleRightArrow => write!(f, "->"), TokenType::Slash => write!(f, "/"), TokenType::Star => write!(f, "*"), - TokenType::DateTime => write!(f, "t\""), TokenType::True => write!(f, "true"), TokenType::Unknown => write!(f, "?"), TokenType::Void => write!(f, "()"), diff --git a/src/vm.rs b/src/vm.rs index 6fb54c2..a475b92 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -190,6 +190,15 @@ impl Vm { let value = self.local_vars.get(name_index).unwrap(); self.push(value.clone()); // not happy , take ownership, no clone } + OP_LIST_GET => { + let index_high = self.read(chunk); + let index_low = self.read(chunk); + let index = index_high <<16 + index_low; + let list = self.pop(); + if let Value::List(list) = list { + self.push(list.get(index).cloned().unwrap()) + } + } OP_CALL => { let function_name_index = self.read(chunk); let num_args = self.read(chunk); @@ -309,3 +318,4 @@ pub const OP_DEF_STRUCT: u16 = 38; pub const OP_DEF_F32: u16 = 39; pub const OP_DEF_F64: u16 = 40; pub const OP_ASSIGN: u16 = 41; +pub const OP_LIST_GET: u16 = 42;