list and table interpolation

This commit is contained in:
trimill 2024-12-23 23:46:05 -05:00
parent 11b07c7b0d
commit 2a8a7b347e
6 changed files with 312 additions and 135 deletions

View file

@ -134,8 +134,10 @@ pub enum Instruction {
NewList(u8), NewList(u8),
GrowList(u8), GrowList(u8),
ExtendList,
NewTable(u8), NewTable(u8),
GrowTable(u8), GrowTable(u8),
ExtendTable,
Index, Index,
StoreIndex, StoreIndex,
@ -197,8 +199,10 @@ impl std::fmt::Display for Instruction {
Self::BinaryOp(o) => write!(f, "binary {o:?}"), Self::BinaryOp(o) => write!(f, "binary {o:?}"),
Self::NewList(n) => write!(f, "newlist #{n}"), Self::NewList(n) => write!(f, "newlist #{n}"),
Self::GrowList(n) => write!(f, "growlist #{n}"), Self::GrowList(n) => write!(f, "growlist #{n}"),
Self::ExtendList => write!(f, "extendlist"),
Self::NewTable(n) => write!(f, "newtable #{n}"), Self::NewTable(n) => write!(f, "newtable #{n}"),
Self::GrowTable(n) => write!(f, "growtable #{n}"), Self::GrowTable(n) => write!(f, "growtable #{n}"),
Self::ExtendTable => write!(f, "extendtable"),
Self::Index => write!(f, "index"), Self::Index => write!(f, "index"),
Self::StoreIndex => write!(f, "storeindex"), Self::StoreIndex => write!(f, "storeindex"),
Self::Jump(a) => write!(f, "jump @{}", usize::from(a)), Self::Jump(a) => write!(f, "jump @{}", usize::from(a)),

View file

@ -3,11 +3,12 @@ use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use crate::chunk::{Arg24, Catch, Chunk, Instruction as I}; 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::parser::Pos;
use crate::prelude::*; use crate::prelude::*;
use crate::symbol::{Symbol, SYM_REPL, SYM_SELF}; use crate::symbol::{Symbol, SYM_REPL, SYM_SELF};
use crate::throw;
use crate::value::function::{FuncAttrs, Function}; use crate::value::function::{FuncAttrs, Function};
use crate::value::Value; use crate::value::Value;
@ -460,41 +461,8 @@ impl<'a> Compiler<'a> {
self.declare_global(*name); self.declare_global(*name);
} }
} }
ExprKind::List(xs) if xs.is_empty() => { ExprKind::List(xs) => self.expr_list(xs)?,
self.emit(I::NewList(0)); ExprKind::Table(xs) => self.expr_table(xs)?,
}
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::Index(ct, idx) => { ExprKind::Index(ct, idx) => {
self.expr(ct)?; self.expr(ct)?;
self.expr(idx)?; self.expr(idx)?;
@ -600,6 +568,83 @@ impl<'a> Compiler<'a> {
Ok(()) 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<()> { fn expr_infinite_loop(&mut self, body: &Expr) -> Result<()> {
let start = self.ip(); let start = self.ip();
self.begin_break_frame(); self.begin_break_frame();

View file

@ -1,7 +1,7 @@
use core::panic; use core::panic;
use crate::{ use crate::{
parser::ast::{CatchBlock, Expr, ExprKind, LValue, LValueKind}, parser::ast::{CatchBlock, Expr, ExprKind, LValue, LValueKind, ListItem, TableItem},
value::Value, value::Value,
vm, vm,
}; };
@ -128,13 +128,23 @@ fn optimize_ex_with(expr: &mut Expr, state: OptState) {
} }
ExprKind::List(xs) => { ExprKind::List(xs) => {
for e in 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) => { ExprKind::Table(t) => {
for (k, v) in t { for e in t {
optimize_ex(k); match e {
optimize_ex(v); TableItem::Pair(k, v) => {
optimize_ex(k);
optimize_ex(v);
}
TableItem::Interpolate(ext) => {
optimize_ex(ext);
}
}
} }
} }
ExprKind::Return(e) => { ExprKind::Return(e) => {

View file

@ -65,8 +65,8 @@ pub enum ExprKind {
Pipe(Box<Expr>, Box<Expr>), Pipe(Box<Expr>, Box<Expr>),
Block(Vec<Expr>), Block(Vec<Expr>),
List(Vec<Expr>), List(Vec<ListItem>),
Table(Vec<(Expr, Expr)>), Table(Vec<TableItem>),
Return(Option<Box<Expr>>), Return(Option<Box<Expr>>),
Break(Option<Box<Expr>>), Break(Option<Box<Expr>>),
@ -90,6 +90,18 @@ impl ExprKind {
} }
} }
#[derive(Debug)]
pub enum TableItem {
Pair(Box<Expr>, Box<Expr>),
Interpolate(Box<Expr>),
}
#[derive(Debug)]
pub enum ListItem {
Item(Box<Expr>),
Interpolate(Box<Expr>),
}
#[derive(Debug)] #[derive(Debug)]
pub struct CatchBlock { pub struct CatchBlock {
pub span: Span, pub span: Span,
@ -258,15 +270,31 @@ impl Expr {
ExprKind::List(l) => { ExprKind::List(l) => {
writeln!(w, "list")?; writeln!(w, "list")?;
for e in l { 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(()) Ok(())
} }
ExprKind::Table(t) => { ExprKind::Table(t) => {
writeln!(w, "list")?; writeln!(w, "table")?;
for (k, v) in t { for e in t {
k.write_to(w, depth)?; match e {
v.write_to(w, depth)?; 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(()) Ok(())
} }

View file

@ -1,12 +1,13 @@
use std::iter::Peekable; use std::iter::Peekable;
use crate::{ use crate::{
parser::ast::TableItem,
symbol::{Symbol, SYM_DOLLAR_SIGN}, symbol::{Symbol, SYM_DOLLAR_SIGN},
value::Value, value::Value,
}; };
use super::{ 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, parse_float, parse_int_literal, parse_str_escapes, Lexer, ParserError, Span,
SpanParserError, Token, TokenKind, SpanParserError, Token, TokenKind,
}; };
@ -204,21 +205,53 @@ impl<'s> Parser<'s> {
self.lexer.peek().unwrap().clone() self.lexer.peek().unwrap().clone()
} }
fn parse_table_items(&mut self) -> Result<Vec<(Expr, Expr)>> { fn parse_table_items(&mut self) -> Result<Vec<TableItem>> {
let mut items = Vec::new(); let mut items = Vec::new();
while self.peek()?.kind.expr_first() { loop {
let key = if let Some(id) = try_next!(self, T::Identifier) { let t = self.peek()?;
E::Literal(Symbol::get(id.content).into()).span(id.span) match t.kind {
} else { T::Identifier => {
self.parse_term_not_ident()? self.next()?;
}; let key = E::Literal(Symbol::get(t.content).into()).span(t.span);
expect!(self, T::Equal);
expect!(self, T::Equal); let value = self.parse_expr()?;
items.push(TableItem::Pair(b(key), b(value)));
let value = self.parse_expr()?; }
T::DotDot => {
items.push((key, value)); 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<Vec<ListItem>> {
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() { if try_next!(self, T::Comma).is_none() {
break break
} }
@ -325,7 +358,7 @@ impl<'s> Parser<'s> {
Ok(e) Ok(e)
} }
T::LBrack => { T::LBrack => {
let args = self.parse_expr_list()?; let args = self.parse_list_items()?;
let end = expect!(self, T::RBrack); let end = expect!(self, T::RBrack);
Ok(E::List(args).span(tok.span + end.span)) Ok(E::List(args).span(tok.span + end.span))
} }

View file

@ -16,7 +16,7 @@ use crate::{
}, },
value::{ value::{
function::{FuncAttrs, Function, NativeFunc}, function::{FuncAttrs, Function, NativeFunc},
Value, HashValue, Value,
}, },
}; };
@ -240,6 +240,12 @@ impl Vm {
self.stack.pop().expect("temporary stack underflow") 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] #[inline]
fn pop_n(&mut self, n: usize) -> Vec<Value> { fn pop_n(&mut self, n: usize) -> Vec<Value> {
let res = self.stack.split_off(self.stack.len() - n); let res = self.stack.split_off(self.stack.len() - n);
@ -397,38 +403,86 @@ impl Vm {
list.borrow_mut().extend(ext); list.borrow_mut().extend(ext);
self.push(Value::List(list)); self.push(Value::List(list));
} }
// [k0,v0...kn,vn] -.> [{k0=v0...kn=vn}] // [l,e] -> [l ++ e]
I::NewTable(n) => { I::ExtendList => {
let mut table = HashMap::new(); let ext = self.pop();
for _ in 0..n { let list = self.pop(); // must be a list
let v = self.pop(); let Value::List(list) = list else {
let k = self.pop(); panic!("not a list")
if v == Value::Nil { };
table.remove(&k.try_into()?); match ext {
} else { Value::List(ext) => {
table.insert(k.try_into()?, v); 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()); self.push(table.into());
} }
// [t,k0,v0...kn,vn] -> [t ++ {k0=v0...kn=vn}] // [t,k0,v0...kn,vn] -> [t ++ {k0=v0...kn=vn}]
I::GrowTable(n) => { I::GrowTable(n) => {
let mut ext = self.pop_n(2 * n as usize); let n = n as usize;
let table = self.pop(); let table = self.take_n(2 * n);
let Value::Table(table) = table else { let Value::Table(table) = table else {
panic!("not a table") panic!("not a table")
}; };
let mut table_ref = table.borrow_mut(); let mut table_ref = table.borrow_mut();
for _ in 0..n { for i in 0..n {
// can't panic: pop_n checked that ext would have len 2*n let k: HashValue = self.take_n(2 * (n - i - 1) + 1).try_into()?;
let v = ext.pop().unwrap(); let v = self.take_n(2 * (n - i - 1));
let k = ext.pop().unwrap();
if v == Value::Nil { if v == Value::Nil {
table_ref.remove(&k.try_into()?); table_ref.remove(&k);
} else { } 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); drop(table_ref);
self.push(Value::Table(table)); self.push(Value::Table(table));
} }
@ -493,58 +547,7 @@ impl Vm {
frame.try_frames.pop().expect("no try to pop"); frame.try_frames.pop().expect("no try to pop");
} }
// [f,a0,a1...an] -> [f(a0,a1...an)] // [f,a0,a1...an] -> [f(a0,a1...an)]
I::Call(n) => { I::Call(n) => self.instr_call(n as usize, frame)?,
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");
}
}
// [f,a0,a1...an] -> [], return f(a0,a1...an) // [f,a0,a1...an] -> [], return f(a0,a1...an)
I::Tail(n) => { I::Tail(n) => {
self.check_interrupt()?; self.check_interrupt()?;
@ -594,6 +597,60 @@ impl Vm {
Ok(None) 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] #[macro_export]