Indexing into lists/maps.

First test with literal list completed. When it's assigned to a var if fails
This commit is contained in:
Shautvast 2025-11-09 21:56:59 +01:00
parent 27fbf4b828
commit 61b9d86aa2
7 changed files with 182 additions and 50 deletions

View file

@ -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<Expression, CompilerErrorAtLine> {
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<Expression, CompilerErrorAtLine> {
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<Expression, CompilerErrorAtLine> {
//TODO?
Ok(Expression::FieldGet {
field: index.lexeme.clone(),
})
}
fn primary(&mut self) -> Result<Expression, CompilerErrorAtLine> {
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<Expression, CompilerErrorAtLine> {
fn function_call(&mut self, name: Token) -> Result<Expression, CompilerErrorAtLine> {
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<Expression>,
},
// PathMatch {
// line: usize,
// condition: Box<Expression>,
// },
NamedParameter {
line: usize,
name: Token,
value: Box<Expression>,
},
MapGet {
key: String,
},
ListGet {
list: Box<Expression>,
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,
// }
// }
}

View file

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

View file

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

View file

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

View file

@ -75,7 +75,7 @@ pub fn build(path: &str, ast: &[Statement], symbols: &mut HashMap<String, Symbol
}
}
pub fn add_types(
pub fn _add_types(
path: &str,
ast: &[Statement],
symbols: &mut HashMap<String, Symbol>,
@ -225,7 +225,10 @@ pub fn infer_type(expr: &Expression, symbols: &HashMap<String, Symbol>) -> 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,
}
}

View file

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

View file

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