From 2a8a7b347ee441ca780f55d8f771d5e1294e7ccd Mon Sep 17 00:00:00 2001 From: trimill Date: Mon, 23 Dec 2024 23:46:05 -0500 Subject: [PATCH] list and table interpolation --- talc-lang/src/chunk.rs | 4 + talc-lang/src/compiler.rs | 119 ++++++++++++++------ talc-lang/src/optimize.rs | 20 +++- talc-lang/src/parser/ast.rs | 42 +++++-- talc-lang/src/parser/parser.rs | 63 ++++++++--- talc-lang/src/vm.rs | 199 +++++++++++++++++++++------------ 6 files changed, 312 insertions(+), 135 deletions(-) diff --git a/talc-lang/src/chunk.rs b/talc-lang/src/chunk.rs index 925345e..5f994e5 100644 --- a/talc-lang/src/chunk.rs +++ b/talc-lang/src/chunk.rs @@ -134,8 +134,10 @@ pub enum Instruction { NewList(u8), GrowList(u8), + ExtendList, NewTable(u8), GrowTable(u8), + ExtendTable, Index, StoreIndex, @@ -197,8 +199,10 @@ impl std::fmt::Display for Instruction { Self::BinaryOp(o) => write!(f, "binary {o:?}"), Self::NewList(n) => write!(f, "newlist #{n}"), Self::GrowList(n) => write!(f, "growlist #{n}"), + Self::ExtendList => write!(f, "extendlist"), Self::NewTable(n) => write!(f, "newtable #{n}"), Self::GrowTable(n) => write!(f, "growtable #{n}"), + Self::ExtendTable => write!(f, "extendtable"), Self::Index => write!(f, "index"), Self::StoreIndex => write!(f, "storeindex"), Self::Jump(a) => write!(f, "jump @{}", usize::from(a)), diff --git a/talc-lang/src/compiler.rs b/talc-lang/src/compiler.rs index e579d62..10f81ce 100644 --- a/talc-lang/src/compiler.rs +++ b/talc-lang/src/compiler.rs @@ -3,11 +3,12 @@ use std::collections::HashMap; use std::rc::Rc; use crate::chunk::{Arg24, Catch, Chunk, Instruction as I}; -use crate::parser::ast::{BinaryOp, CatchBlock, Expr, ExprKind, LValue, LValueKind}; +use crate::parser::ast::{ + BinaryOp, CatchBlock, Expr, ExprKind, LValue, LValueKind, ListItem, TableItem, +}; use crate::parser::Pos; use crate::prelude::*; use crate::symbol::{Symbol, SYM_REPL, SYM_SELF}; -use crate::throw; use crate::value::function::{FuncAttrs, Function}; use crate::value::Value; @@ -460,41 +461,8 @@ impl<'a> Compiler<'a> { self.declare_global(*name); } } - ExprKind::List(xs) if xs.is_empty() => { - self.emit(I::NewList(0)); - } - ExprKind::List(xs) => { - let mut first = true; - for chunk in xs.chunks(16) { - for e in chunk { - self.expr(e)?; - } - if first { - self.emit(I::NewList(chunk.len() as u8)); - first = false; - } else { - self.emit(I::GrowList(chunk.len() as u8)); - } - } - } - ExprKind::Table(xs) if xs.is_empty() => { - self.emit(I::NewTable(0)); - } - ExprKind::Table(xs) => { - let mut first = true; - for chunk in xs.chunks(8) { - for (k, v) in chunk { - self.expr(k)?; - self.expr(v)?; - } - if first { - self.emit(I::NewTable(chunk.len() as u8)); - first = false; - } else { - self.emit(I::GrowTable(chunk.len() as u8)); - } - } - } + ExprKind::List(xs) => self.expr_list(xs)?, + ExprKind::Table(xs) => self.expr_table(xs)?, ExprKind::Index(ct, idx) => { self.expr(ct)?; self.expr(idx)?; @@ -600,6 +568,83 @@ impl<'a> Compiler<'a> { Ok(()) } + fn expr_list(&mut self, xs: &[ListItem]) -> Result<()> { + if xs.is_empty() { + self.emit(I::NewList(0)); + return Ok(()) + } + fn finish_chunk(c: &mut Compiler, len: &mut usize, first: &mut bool) { + if *first { + c.emit(I::NewList(*len as u8)); + *first = false; + } else { + if *len == 0 { + return + } + c.emit(I::GrowList(*len as u8)); + } + *len = 0; + } + let mut first = true; + let mut chlen = 0; + for (i, e) in xs.iter().enumerate() { + match e { + ListItem::Interpolate(e) => { + finish_chunk(self, &mut chlen, &mut first); + self.expr(e)?; + self.emit(I::ExtendList); + } + ListItem::Item(e) => { + self.expr(e)?; + chlen += 1; + if chlen >= 16 || i == xs.len() - 1 { + finish_chunk(self, &mut chlen, &mut first); + } + } + } + } + Ok(()) + } + + fn expr_table(&mut self, xs: &[TableItem]) -> Result<()> { + if xs.is_empty() { + self.emit(I::NewTable(0)); + return Ok(()) + } + fn finish_chunk(c: &mut Compiler, len: &mut usize, first: &mut bool) { + if *first { + c.emit(I::NewTable(*len as u8)); + *first = false; + } else { + if *len == 0 { + return + } + c.emit(I::GrowTable(*len as u8)); + } + *len = 0; + } + let mut first = true; + let mut chlen = 0; + for (i, e) in xs.iter().enumerate() { + match e { + TableItem::Pair(k, v) => { + self.expr(k)?; + self.expr(v)?; + chlen += 1; + if chlen >= 8 || i == xs.len() - 1 { + finish_chunk(self, &mut chlen, &mut first); + } + } + TableItem::Interpolate(ext) => { + finish_chunk(self, &mut chlen, &mut first); + self.expr(ext)?; + self.emit(I::ExtendTable); + } + } + } + Ok(()) + } + fn expr_infinite_loop(&mut self, body: &Expr) -> Result<()> { let start = self.ip(); self.begin_break_frame(); diff --git a/talc-lang/src/optimize.rs b/talc-lang/src/optimize.rs index 1097d49..d417c83 100644 --- a/talc-lang/src/optimize.rs +++ b/talc-lang/src/optimize.rs @@ -1,7 +1,7 @@ use core::panic; use crate::{ - parser::ast::{CatchBlock, Expr, ExprKind, LValue, LValueKind}, + parser::ast::{CatchBlock, Expr, ExprKind, LValue, LValueKind, ListItem, TableItem}, value::Value, vm, }; @@ -128,13 +128,23 @@ fn optimize_ex_with(expr: &mut Expr, state: OptState) { } ExprKind::List(xs) => { for e in xs { - optimize_ex(e); + match e { + ListItem::Item(e) => optimize_ex(e), + ListItem::Interpolate(e) => optimize_ex(e), + } } } ExprKind::Table(t) => { - for (k, v) in t { - optimize_ex(k); - optimize_ex(v); + for e in t { + match e { + TableItem::Pair(k, v) => { + optimize_ex(k); + optimize_ex(v); + } + TableItem::Interpolate(ext) => { + optimize_ex(ext); + } + } } } ExprKind::Return(e) => { diff --git a/talc-lang/src/parser/ast.rs b/talc-lang/src/parser/ast.rs index 6a8e639..b6522d1 100644 --- a/talc-lang/src/parser/ast.rs +++ b/talc-lang/src/parser/ast.rs @@ -65,8 +65,8 @@ pub enum ExprKind { Pipe(Box, Box), Block(Vec), - List(Vec), - Table(Vec<(Expr, Expr)>), + List(Vec), + Table(Vec), Return(Option>), Break(Option>), @@ -90,6 +90,18 @@ impl ExprKind { } } +#[derive(Debug)] +pub enum TableItem { + Pair(Box, Box), + Interpolate(Box), +} + +#[derive(Debug)] +pub enum ListItem { + Item(Box), + Interpolate(Box), +} + #[derive(Debug)] pub struct CatchBlock { pub span: Span, @@ -258,15 +270,31 @@ impl Expr { ExprKind::List(l) => { writeln!(w, "list")?; for e in l { - e.write_to(w, depth)?; + match e { + ListItem::Item(e) => { + e.write_to(w, depth)?; + } + ListItem::Interpolate(ext) => { + writeln!(w, "{0: >1$}interpolate", "", depth * 2)?; + ext.write_to(w, depth + 1)?; + } + } } Ok(()) } ExprKind::Table(t) => { - writeln!(w, "list")?; - for (k, v) in t { - k.write_to(w, depth)?; - v.write_to(w, depth)?; + writeln!(w, "table")?; + for e in t { + match e { + TableItem::Pair(k, v) => { + k.write_to(w, depth)?; + v.write_to(w, depth)?; + } + TableItem::Interpolate(ext) => { + writeln!(w, "{0: >1$}interpolate", "", depth * 2)?; + ext.write_to(w, depth + 1)?; + } + } } Ok(()) } diff --git a/talc-lang/src/parser/parser.rs b/talc-lang/src/parser/parser.rs index fd30bdb..9068795 100644 --- a/talc-lang/src/parser/parser.rs +++ b/talc-lang/src/parser/parser.rs @@ -1,12 +1,13 @@ use std::iter::Peekable; use crate::{ + parser::ast::TableItem, symbol::{Symbol, SYM_DOLLAR_SIGN}, value::Value, }; use super::{ - ast::{BinaryOp, CatchBlock, Expr, ExprKind, LValue, UnaryOp}, + ast::{BinaryOp, CatchBlock, Expr, ExprKind, LValue, ListItem, UnaryOp}, parse_float, parse_int_literal, parse_str_escapes, Lexer, ParserError, Span, SpanParserError, Token, TokenKind, }; @@ -204,21 +205,53 @@ impl<'s> Parser<'s> { self.lexer.peek().unwrap().clone() } - fn parse_table_items(&mut self) -> Result> { + fn parse_table_items(&mut self) -> Result> { let mut items = Vec::new(); - while self.peek()?.kind.expr_first() { - let key = if let Some(id) = try_next!(self, T::Identifier) { - E::Literal(Symbol::get(id.content).into()).span(id.span) - } else { - self.parse_term_not_ident()? - }; - - expect!(self, T::Equal); - - let value = self.parse_expr()?; - - items.push((key, value)); + loop { + let t = self.peek()?; + match t.kind { + T::Identifier => { + self.next()?; + let key = E::Literal(Symbol::get(t.content).into()).span(t.span); + expect!(self, T::Equal); + let value = self.parse_expr()?; + items.push(TableItem::Pair(b(key), b(value))); + } + T::DotDot => { + self.next()?; + let ext = self.parse_expr()?; + items.push(TableItem::Interpolate(b(ext))); + } + k if k.expr_first() => { + let key = self.parse_term_not_ident()?; + expect!(self, T::Equal); + let value = self.parse_expr()?; + items.push(TableItem::Pair(b(key), b(value))); + } + _ => break, + } + if try_next!(self, T::Comma).is_none() { + break + } + } + Ok(items) + } + fn parse_list_items(&mut self) -> Result> { + let mut items = Vec::new(); + loop { + let t = self.peek()?; + match t.kind { + T::DotDot => { + self.next()?; + let expr = self.parse_expr()?; + items.push(ListItem::Interpolate(b(expr))); + } + k if k.expr_first() => { + items.push(ListItem::Item(b(self.parse_expr()?))); + } + _ => break, + } if try_next!(self, T::Comma).is_none() { break } @@ -325,7 +358,7 @@ impl<'s> Parser<'s> { Ok(e) } T::LBrack => { - let args = self.parse_expr_list()?; + let args = self.parse_list_items()?; let end = expect!(self, T::RBrack); Ok(E::List(args).span(tok.span + end.span)) } diff --git a/talc-lang/src/vm.rs b/talc-lang/src/vm.rs index bd5e63e..dfc01c5 100644 --- a/talc-lang/src/vm.rs +++ b/talc-lang/src/vm.rs @@ -16,7 +16,7 @@ use crate::{ }, value::{ function::{FuncAttrs, Function, NativeFunc}, - Value, + HashValue, Value, }, }; @@ -240,6 +240,12 @@ impl Vm { self.stack.pop().expect("temporary stack underflow") } + #[inline] + fn take_n(&mut self, n: usize) -> Value { + let i = self.stack.len() - n - 1; + std::mem::take(&mut self.stack[i]) + } + #[inline] fn pop_n(&mut self, n: usize) -> Vec { let res = self.stack.split_off(self.stack.len() - n); @@ -397,38 +403,86 @@ impl Vm { list.borrow_mut().extend(ext); self.push(Value::List(list)); } - // [k0,v0...kn,vn] -.> [{k0=v0...kn=vn}] - I::NewTable(n) => { - let mut table = HashMap::new(); - for _ in 0..n { - let v = self.pop(); - let k = self.pop(); - if v == Value::Nil { - table.remove(&k.try_into()?); - } else { - table.insert(k.try_into()?, v); + // [l,e] -> [l ++ e] + I::ExtendList => { + let ext = self.pop(); + let list = self.pop(); // must be a list + let Value::List(list) = list else { + panic!("not a list") + }; + match ext { + Value::List(ext) => { + list.borrow_mut().extend_from_slice(ext.borrow().as_slice()); + } + _ => { + let mut list = list.borrow_mut(); + let f = ext.to_iter_function()?; + loop { + self.push(f.clone()); + self.instr_call(0, frame)?; + match self.pop().iter_unpack() { + None => break, + Some(v) => list.push(v), + } + } } } + self.push(Value::List(list)); + } + // [k0,v0...kn,vn] -.> [{k0=v0...kn=vn}] + I::NewTable(n) => { + let n = n as usize; + let mut table = HashMap::new(); + for i in 0..n { + let k: HashValue = self.take_n(2 * (n - i - 1) + 1).try_into()?; + let v = self.take_n(2 * (n - i - 1)); + if v == Value::Nil { + table.remove(&k); + } else { + table.insert(k, v); + } + } + self.stack.truncate(self.stack.len() - 2 * n); self.push(table.into()); } // [t,k0,v0...kn,vn] -> [t ++ {k0=v0...kn=vn}] I::GrowTable(n) => { - let mut ext = self.pop_n(2 * n as usize); - let table = self.pop(); + let n = n as usize; + let table = self.take_n(2 * n); let Value::Table(table) = table else { panic!("not a table") }; let mut table_ref = table.borrow_mut(); - for _ in 0..n { - // can't panic: pop_n checked that ext would have len 2*n - let v = ext.pop().unwrap(); - let k = ext.pop().unwrap(); + for i in 0..n { + let k: HashValue = self.take_n(2 * (n - i - 1) + 1).try_into()?; + let v = self.take_n(2 * (n - i - 1)); if v == Value::Nil { - table_ref.remove(&k.try_into()?); + table_ref.remove(&k); } else { - table_ref.insert(k.try_into()?, v); + table_ref.insert(k, v); } } + self.stack.truncate(self.stack.len() - 2 * n - 1); + drop(table_ref); + self.push(Value::Table(table)); + } + I::ExtendTable => { + let ext = self.pop(); + let table = self.pop(); // must be a list + let Value::Table(table) = table else { + panic!("not a table") + }; + let Value::Table(ext) = ext else { + throw!( + *SYM_TYPE_ERROR, + "cannot interpolate value {:#} into table", + ext + ) + }; + let mut table_ref = table.borrow_mut(); + for (k, v) in ext.borrow().iter() { + table_ref.insert(k.clone(), v.clone()); + } drop(table_ref); self.push(Value::Table(table)); } @@ -493,58 +547,7 @@ impl Vm { frame.try_frames.pop().expect("no try to pop"); } // [f,a0,a1...an] -> [f(a0,a1...an)] - I::Call(n) => { - self.check_interrupt()?; - let n = usize::from(n); - - let args = self.pop_n(n + 1); - - let args = match get_call_outcome(args)? { - CallOutcome::Call(args) => args, - CallOutcome::Partial(v) => { - self.push(v); - return Ok(None) - } - }; - - if let Value::NativeFunc(nf) = &args[0] { - let nf = nf.clone(); - - // safety: frame is restored immediately - // after function call ends - // ~25% performance improvement in - // code heavy on native function calls - unsafe { - let f = std::ptr::read(frame); - self.call_stack.push(f); - } - - let res = (nf.func)(self, args); - - // safety: frame was referencing invalid memory due to - // previous unsafe block, write will fix that - unsafe { - let f = self.call_stack.pop().expect("no frame to pop"); - std::ptr::write(frame, f); - } - - // make sure we restored the value of frame - // before propagating exceptions - let res = res?; - - self.push(res); - } else if let Value::Function(func) = &args[0] { - if self.call_stack.len() + 1 >= self.stack_max { - throw!(*SYM_CALL_STACK_OVERFLOW, "call stack overflow") - } - - let new_frame = CallFrame::new(func.clone(), args); - let old_frame = std::mem::replace(frame, new_frame); - self.call_stack.push(old_frame); - } else { - unreachable!("already verified by calling get_call_type"); - } - } + I::Call(n) => self.instr_call(n as usize, frame)?, // [f,a0,a1...an] -> [], return f(a0,a1...an) I::Tail(n) => { self.check_interrupt()?; @@ -594,6 +597,60 @@ impl Vm { Ok(None) } + + fn instr_call(&mut self, n: usize, frame: &mut CallFrame) -> Result<()> { + self.check_interrupt()?; + + let args = self.pop_n(n + 1); + + let args = match get_call_outcome(args)? { + CallOutcome::Call(args) => args, + CallOutcome::Partial(v) => { + self.push(v); + return Ok(()) + } + }; + + if let Value::NativeFunc(nf) = &args[0] { + let nf = nf.clone(); + + // safety: frame is restored immediately + // after function call ends + // ~25% performance improvement in + // code heavy on native function calls + unsafe { + let f = std::ptr::read(frame); + self.call_stack.push(f); + } + + let res = (nf.func)(self, args); + + // safety: frame was referencing invalid memory due to + // previous unsafe block, write will fix that + unsafe { + let f = self.call_stack.pop().expect("no frame to pop"); + std::ptr::write(frame, f); + } + + // make sure we restored the value of frame + // before propagating exceptions + let res = res?; + + self.push(res); + } else if let Value::Function(func) = &args[0] { + if self.call_stack.len() + 1 >= self.stack_max { + throw!(*SYM_CALL_STACK_OVERFLOW, "call stack overflow") + } + + let new_frame = CallFrame::new(func.clone(), args); + let old_frame = std::mem::replace(frame, new_frame); + self.call_stack.push(old_frame); + } else { + unreachable!("already verified by calling get_call_type"); + } + + Ok(()) + } } #[macro_export]