list and table interpolation
This commit is contained in:
parent
11b07c7b0d
commit
2a8a7b347e
6 changed files with 312 additions and 135 deletions
|
@ -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)),
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -65,8 +65,8 @@ pub enum ExprKind {
|
|||
Pipe(Box<Expr>, Box<Expr>),
|
||||
|
||||
Block(Vec<Expr>),
|
||||
List(Vec<Expr>),
|
||||
Table(Vec<(Expr, Expr)>),
|
||||
List(Vec<ListItem>),
|
||||
Table(Vec<TableItem>),
|
||||
|
||||
Return(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)]
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -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<Vec<(Expr, Expr)>> {
|
||||
fn parse_table_items(&mut self) -> Result<Vec<TableItem>> {
|
||||
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<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() {
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -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<Value> {
|
||||
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]
|
||||
|
|
Loading…
Reference in a new issue