use std::sync::Arc;
use super::Bytecode;
use super::Instr;
use super::Result;
use super::UpvalueDesc;
use super::error::Error;
use super::error::ErrorKind;
use super::error::SyntaxError;
use super::exp_desc::ExpDesc;
use super::exp_desc::PlaceExp;
use super::exp_desc::PrefixExp;
use super::lexer::TokenStream;
use super::token::Token;
use super::token::TokenType;
use crate::instr::{ArgCount, Builtin, RetCount};
use std::borrow::Borrow;
use std::cmp::Ordering;
#[derive(Debug)]
struct Parser<'a> {
input: TokenStream<'a>,
chunk: Bytecode,
nest_level: i32,
locals: Vec<(String, i32)>,
outer_chunks: Vec<Bytecode>,
loop_breaks: Vec<Vec<usize>>,
upvalues: Vec<(String, UpvalueDesc)>,
outer_locals: Vec<Vec<(String, i32)>>,
outer_upvalues: Vec<Vec<(String, UpvalueDesc)>>,
current_line: u32,
}
pub(super) fn parse_str(source: &str) -> Result<Bytecode> {
parse_str_named(source, None)
}
#[hotpath::measure]
pub(super) fn parse_str_named(source: &str, source_name: Option<String>) -> Result<Bytecode> {
let chunk = Bytecode {
source: source_name,
..Default::default()
};
let parser = Parser {
input: TokenStream::new(source),
chunk,
nest_level: 0,
locals: Vec::new(),
outer_chunks: Vec::new(),
loop_breaks: Vec::new(),
upvalues: Vec::new(),
outer_locals: Vec::new(),
outer_upvalues: Vec::new(),
current_line: 1,
};
parser.parse_all()
}
impl<'a> Parser<'a> {
fn add_local(&mut self, name: &str) -> Result<()> {
if self.locals.len() == u8::MAX as usize {
Err(self.error(SyntaxError::TooManyLocals))
} else {
self.locals.push((name.to_string(), self.nest_level));
if self.locals.len() > self.chunk.num_locals as usize {
self.chunk.num_locals += 1;
}
Ok(())
}
}
#[must_use]
fn error(&self, kind: impl Into<ErrorKind>) -> Error {
let pos = self.input.pos();
self.error_at(kind, pos)
}
#[must_use]
fn error_at(&self, kind: impl Into<ErrorKind>, pos: usize) -> Error {
let (line, column) = self.input.line_and_column(pos);
Error::new(kind, line, column)
}
#[must_use]
fn err_unexpected(&self, token: Token, expected: TokenType) -> Error {
let error_kind = if token.typ == TokenType::EndOfFile {
SyntaxError::UnexpectedEof
} else {
let got = self.describe_token(&token);
let exp = Self::describe_token_type(expected);
SyntaxError::UnexpectedTok(format!("'{exp}' expected near {got}"))
};
self.error_at(error_kind, token.start)
}
fn describe_token(&self, token: &Token) -> String {
match token.typ {
TokenType::Identifier
| TokenType::LiteralNumber
| TokenType::LiteralHexNumber
| TokenType::LiteralString => {
let text = self
.input
.substring(token.start..token.start + token.len as usize);
format!("'{text}'")
}
TokenType::EndOfFile => "<eof>".to_string(),
other => format!("'{}'", Self::describe_token_type(other)),
}
}
fn describe_token_type(typ: TokenType) -> &'static str {
match typ {
TokenType::And => "and",
TokenType::Break => "break",
TokenType::Do => "do",
TokenType::Else => "else",
TokenType::ElseIf => "elseif",
TokenType::End => "end",
TokenType::False => "false",
TokenType::For => "for",
TokenType::Function => "function",
TokenType::If => "if",
TokenType::In => "in",
TokenType::Local => "local",
TokenType::Nil => "nil",
TokenType::Not => "not",
TokenType::Or => "or",
TokenType::Repeat => "repeat",
TokenType::Return => "return",
TokenType::Then => "then",
TokenType::True => "true",
TokenType::Until => "until",
TokenType::While => "while",
TokenType::Plus => "+",
TokenType::Minus => "-",
TokenType::Star => "*",
TokenType::Slash => "/",
TokenType::Mod => "%",
TokenType::Caret => "^",
TokenType::Hash => "#",
TokenType::Equal => "==",
TokenType::NotEqual => "~=",
TokenType::LessEqual => "<=",
TokenType::GreaterEqual => ">=",
TokenType::Less => "<",
TokenType::Greater => ">",
TokenType::LParen | TokenType::LParenLineStart => "(",
TokenType::RParen => ")",
TokenType::LCurly => "{",
TokenType::RCurly => "}",
TokenType::LSquare => "[",
TokenType::RSquare => "]",
TokenType::Semi => ";",
TokenType::Colon => ":",
TokenType::Comma => ",",
TokenType::Dot => ".",
TokenType::DotDot => "..",
TokenType::DotDotDot => "...",
TokenType::Assign => "=",
TokenType::Identifier => "<name>",
TokenType::LiteralNumber | TokenType::LiteralHexNumber => "<number>",
TokenType::LiteralString => "<string>",
TokenType::EndOfFile => "<eof>",
}
}
fn expect(&mut self, expected: TokenType) -> Result<Token> {
let token = self.input.next()?;
self.update_line(token.start);
if token.typ == expected {
Ok(token)
} else {
Err(self.err_unexpected(token, expected))
}
}
fn expect_identifier(&mut self) -> Result<&'a str> {
let token = self.expect(TokenType::Identifier)?;
let name = self.get_text(token);
Ok(name)
}
fn expect_identifier_id(&mut self) -> Result<u8> {
let name = self.expect_identifier()?;
self.find_or_add_string(name)
}
fn find_or_add_string(&mut self, string: &str) -> Result<u8> {
self.find_or_add_string_bytes(string.as_bytes())
}
fn find_or_add_string_bytes(&mut self, bytes: &[u8]) -> Result<u8> {
match self
.chunk
.string_literals
.iter()
.position(|existing| existing.as_slice() == bytes)
{
Some(i) => Ok(i as u8),
None => {
let i = self.chunk.string_literals.len();
if i == u8::MAX as usize {
Err(self.error(SyntaxError::TooManyStrings))
} else {
self.chunk.string_literals.push(bytes.to_vec());
Ok(i as u8)
}
}
}
}
fn find_or_add_number(&mut self, num: f64) -> Result<u8> {
find_or_add(&mut self.chunk.number_literals, &num)
.ok_or_else(|| self.error(SyntaxError::TooManyNumbers))
}
#[must_use]
fn get_literal_string_contents(&self, tok: Token) -> Vec<u8> {
let Token { start, len, typ } = tok;
assert_eq!(typ, TokenType::LiteralString);
assert!(len >= 2);
let range = (start + 1)..(start + len as usize - 1);
let raw = self.input.substring(range);
let mut result = Vec::with_capacity(raw.len());
let mut chars = raw.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
if let Some(&next) = chars.peek() {
chars.next();
match next {
'n' | '\n' => result.push(b'\n'), 't' => result.push(b'\t'),
'r' => result.push(b'\r'),
'\\' => result.push(b'\\'),
'"' => result.push(b'"'),
'\'' => result.push(b'\''),
'0' => result.push(b'\0'),
'a' => result.push(b'\x07'), 'b' => result.push(b'\x08'), 'f' => result.push(b'\x0C'), 'v' => result.push(b'\x0B'), _ => {
result.push(b'\\');
push_char_bytes(&mut result, next);
}
}
} else {
result.push(b'\\');
}
} else {
push_char_bytes(&mut result, c);
}
}
result
}
#[must_use]
fn get_text(&self, token: Token) -> &'a str {
self.input.substring(token.range())
}
fn level_down(&mut self) {
while let Some((_, lvl)) = self.locals.last() {
if *lvl == self.nest_level {
self.locals.pop();
} else {
break;
}
}
self.nest_level -= 1;
}
fn push(&mut self, instr: Instr) {
self.chunk.code.push(instr);
self.chunk.line_info.push(self.current_line);
}
fn update_line(&mut self, pos: usize) {
let (line, _) = self.input.line_and_column(pos);
self.current_line = line as u32;
}
fn enter_loop(&mut self) {
self.loop_breaks.push(Vec::new());
}
fn exit_loop(&mut self) {
let breaks = self
.loop_breaks
.pop()
.expect("exit_loop called without enter_loop");
let loop_end = self.chunk.code.len();
for break_idx in breaks {
let offset = (loop_end - break_idx - 1) as i16;
self.chunk.code[break_idx] = Instr::jump(offset);
}
}
fn add_break(&mut self) -> Result<()> {
if let Some(breaks) = self.loop_breaks.last_mut() {
let idx = self.chunk.code.len();
breaks.push(idx);
self.push(Instr::jump(0));
Ok(())
} else {
Err(self.error(SyntaxError::BreakOutsideLoop))
}
}
#[hotpath::measure]
fn parse_all(mut self) -> Result<Bytecode> {
let c = self.parse_chunk(&[], true)?;
let token = self.input.next()?;
assert_eq!(self.nest_level, 0);
if let TokenType::EndOfFile = token.typ {
Ok(c)
} else {
Err(self.err_unexpected(token, TokenType::EndOfFile))
}
}
#[hotpath::measure]
fn parse_chunk(&mut self, params: &[&str], is_vararg: bool) -> Result<Bytecode> {
let source = self.chunk.source.clone();
self.outer_chunks.push(self.chunk.clone());
self.chunk = Bytecode::default();
self.chunk.source = source;
self.chunk.is_vararg = is_vararg;
let saved_locals = std::mem::take(&mut self.locals);
self.outer_locals.push(saved_locals);
let saved_upvalues = std::mem::take(&mut self.upvalues);
self.outer_upvalues.push(saved_upvalues);
self.chunk.num_params = params.len() as u8;
for ¶m in params {
self.locals.push((param.into(), self.nest_level));
}
self.parse_statements()?;
self.push(Instr::ret(RetCount::Fixed(0)));
self.chunk.upvalues = self.upvalues.iter().map(|(_, desc)| *desc).collect();
let tmp_chunk = self.chunk.clone();
self.chunk = self.outer_chunks.pop().ok_or_else(|| {
self.error_at(
ErrorKind::InternalError("compiler: outer chunk stack empty".into()),
0,
)
})?;
self.locals = self.outer_locals.pop().ok_or_else(|| {
self.error_at(
ErrorKind::InternalError("compiler: outer locals stack empty".into()),
0,
)
})?;
self.upvalues = self.outer_upvalues.pop().ok_or_else(|| {
self.error_at(
ErrorKind::InternalError("compiler: outer upvalues stack empty".into()),
0,
)
})?;
#[cfg(feature = "debug_parser")]
println!("Compiled chunk: {tmp_chunk:#?}");
Ok(tmp_chunk)
}
#[hotpath::measure]
fn parse_statements(&mut self) -> Result<()> {
loop {
let stmt_start = self.input.peek()?.start;
self.update_line(stmt_start);
match self.input.peek_type()? {
TokenType::Identifier | TokenType::LParen | TokenType::LParenLineStart => {
self.parse_assign_or_call()?;
}
TokenType::If => self.parse_if()?,
TokenType::While => self.parse_while()?,
TokenType::Repeat => self.parse_repeat()?,
TokenType::Do => self.parse_do()?,
TokenType::Local => self.parse_locals()?,
TokenType::For => self.parse_for()?,
TokenType::Function => self.parse_fndecl()?,
TokenType::Semi => {
self.input.next()?;
}
TokenType::Return => break self.parse_return(),
TokenType::Break => {
self.input.next()?; self.add_break()?;
break Ok(());
}
_ => break Ok(()),
}
}
}
fn parse_fndecl(&mut self) -> Result<()> {
self.input.next()?; let name = self.expect_identifier()?;
match self.input.peek_type()? {
TokenType::Dot => self.parse_fndecl_table(name),
TokenType::Colon => self.parse_fndecl_method(name),
_ => self.parse_fndecl_basic(name),
}
}
fn parse_fndecl_basic(&mut self, name: &'a str) -> Result<()> {
let place_exp = self.parse_prefix_identifier(name)?;
let instr = match place_exp {
PlaceExp::Local(i) => Instr::set_local(i),
PlaceExp::Upvalue(i) => Instr::set_upvalue(i),
PlaceExp::Global(i) => Instr::set_global(i),
PlaceExp::Builtin(b) => Instr::set_builtin(b),
_ => unreachable!("place expression was not a local, upvalue, or global variable"),
};
self.parse_fndef_named(Some(name.to_string()))?;
self.push(instr);
Ok(())
}
fn parse_fndecl_table(&mut self, table_name: &'a str) -> Result<()> {
let table_instr = match self.parse_prefix_identifier(table_name)? {
PlaceExp::Local(i) => Instr::get_local(i),
PlaceExp::Upvalue(i) => Instr::get_upvalue(i),
PlaceExp::Global(i) => Instr::get_global(i),
PlaceExp::Builtin(b) => Instr::get_builtin(b),
_ => unreachable!("place expression was not a local, upvalue, or global variable"),
};
self.push(table_instr);
let mut full_name = table_name.to_string();
self.expect(TokenType::Dot)?;
let mut last_field = self.expect_identifier()?;
full_name.push('.');
full_name.push_str(last_field);
let mut last_field_id = self.find_or_add_string(last_field)?;
while self.input.try_pop(TokenType::Dot)?.is_some() {
self.push(Instr::get_field(last_field_id));
last_field = self.expect_identifier()?;
full_name.push('.');
full_name.push_str(last_field);
last_field_id = self.find_or_add_string(last_field)?;
}
self.parse_fndef_named(Some(full_name))?;
self.push(Instr::set_field(0, last_field_id));
Ok(())
}
fn parse_fndecl_method(&mut self, table_name: &'a str) -> Result<()> {
let table_instr = match self.parse_prefix_identifier(table_name)? {
PlaceExp::Local(i) => Instr::get_local(i),
PlaceExp::Upvalue(i) => Instr::get_upvalue(i),
PlaceExp::Global(i) => Instr::get_global(i),
PlaceExp::Builtin(b) => Instr::get_builtin(b),
_ => unreachable!("place expression was not a local, upvalue, or global variable"),
};
self.push(table_instr);
self.expect(TokenType::Colon)?;
let method_name = self.expect_identifier()?;
let full_name = format!("{table_name}:{method_name}");
let method_name_id = self.find_or_add_string(method_name)?;
self.parse_fndef_method(Some(full_name))?;
self.push(Instr::set_field(0, method_name_id));
Ok(())
}
fn parse_return(&mut self) -> Result<()> {
self.input.next()?;
let n = if self.is_expr_start()? {
let (n, last_exp) = self.parse_explist()?;
match last_exp {
ExpDesc::Prefix(PrefixExp::FunctionCall(num_args)) => {
self.chunk.code.pop(); self.push(Instr::call(ArgCount::Fixed(num_args), RetCount::All)); u8::MAX
}
ExpDesc::Vararg => {
self.chunk.code.pop(); self.push(Instr::vararg(u8::MAX)); u8::MAX
}
_ => n,
}
} else {
0
};
self.push(Instr::ret(RetCount::Fixed(n)));
self.input.try_pop(TokenType::Semi)?;
Ok(())
}
fn is_expr_start(&mut self) -> Result<bool> {
let ok = matches!(
self.input.peek_type()?,
TokenType::Identifier
| TokenType::LParen
| TokenType::LParenLineStart
| TokenType::LCurly
| TokenType::LiteralNumber
| TokenType::LiteralHexNumber
| TokenType::LiteralString
| TokenType::Function
| TokenType::Nil
| TokenType::False
| TokenType::True
| TokenType::Not
| TokenType::Hash
| TokenType::Minus
| TokenType::DotDotDot
);
Ok(ok)
}
fn parse_assign_or_call(&mut self) -> Result<()> {
match self.parse_prefix_exp()? {
PrefixExp::Parenthesized => {
let tok = self.input.next()?;
Err(self.err_unexpected(tok, TokenType::Assign))
}
PrefixExp::FunctionCall(num_args) => {
self.push(Instr::call(ArgCount::Fixed(num_args), RetCount::Fixed(0)));
Ok(())
}
PrefixExp::Place(first_place) => self.parse_assign(first_place),
}
}
fn parse_assign(&mut self, first_exp: PlaceExp) -> Result<()> {
let mut places = vec![first_exp];
while self.input.try_pop(TokenType::Comma)?.is_some() {
places.push(self.parse_place_exp()?);
}
self.expect(TokenType::Assign)?;
let num_lvals = places.len() as isize;
let (num_rvals, last_exp) = self.parse_explist()?;
let num_rvals = num_rvals as isize;
let diff = num_lvals - num_rvals;
if diff > 0 {
if let ExpDesc::Prefix(PrefixExp::FunctionCall(_)) = last_exp {
let num_args = match self.chunk.code.pop() {
Some(instr) if instr.opcode() == Instr::OP_CALL => instr.a(),
i => unreachable!("PrefixExp::FunctionCall but last instruction was {:?}", i),
};
self.push(Instr::call(
ArgCount::Fixed(num_args),
RetCount::Fixed(1 + diff as u8),
));
} else {
for _ in 0..diff {
self.push(Instr::push_nil());
}
}
} else {
for _ in diff..0 {
self.push(Instr::pop());
}
}
places.reverse();
for (i, place_exp) in places.into_iter().enumerate() {
let instr = match place_exp {
PlaceExp::Local(i) => Instr::set_local(i),
PlaceExp::Upvalue(i) => Instr::set_upvalue(i),
PlaceExp::Global(i) => Instr::set_global(i),
PlaceExp::Builtin(b) => Instr::set_builtin(b),
PlaceExp::FieldAccess(literal_id) => {
let stack_offset = num_lvals as u8 - i as u8 - 1;
Instr::set_field(stack_offset, literal_id)
}
PlaceExp::TableIndex => {
let stack_offset = num_lvals as u8 - i as u8 - 1;
Instr::set_table(stack_offset)
}
};
self.push(instr);
}
Ok(())
}
fn parse_place_exp(&mut self) -> Result<PlaceExp> {
match self.parse_prefix_exp()? {
PrefixExp::Parenthesized | PrefixExp::FunctionCall(_) => {
let tok = self.input.next()?;
Err(self.err_unexpected(tok, TokenType::Assign))
}
PrefixExp::Place(place) => Ok(place),
}
}
fn eval_prefix_exp(&mut self, exp: &PrefixExp) {
match exp {
PrefixExp::FunctionCall(num_args) => {
self.push(Instr::call(ArgCount::Fixed(*num_args), RetCount::Fixed(1)));
}
PrefixExp::Parenthesized => (),
PrefixExp::Place(place) => {
let instr = match place {
PlaceExp::Local(i) => Instr::get_local(*i),
PlaceExp::Upvalue(i) => Instr::get_upvalue(*i),
PlaceExp::Global(i) => Instr::get_global(*i),
PlaceExp::Builtin(b) => Instr::get_builtin(*b),
PlaceExp::FieldAccess(i) => Instr::get_field(*i),
PlaceExp::TableIndex => Instr::get_table(),
};
self.push(instr);
}
}
}
fn parse_prefix_identifier(&mut self, name: &str) -> Result<PlaceExp> {
if let Some(i) = find_last_local(&self.locals, name) {
return Ok(PlaceExp::Local(i as u8));
}
if let Some(i) = self.find_upvalue(name) {
return Ok(PlaceExp::Upvalue(i));
}
if let Some(i) = self.resolve_upvalue(name) {
return Ok(PlaceExp::Upvalue(i));
}
if let Some(builtin) = Builtin::from_name(name) {
return Ok(PlaceExp::Builtin(builtin));
}
let i = self.find_or_add_string(name)?;
Ok(PlaceExp::Global(i))
}
fn find_upvalue(&self, name: &str) -> Option<u8> {
self.upvalues
.iter()
.position(|(n, _)| n == name)
.map(|i| i as u8)
}
fn resolve_upvalue(&mut self, name: &str) -> Option<u8> {
self.resolve_upvalue_recursive(name, self.outer_locals.len())
}
fn resolve_upvalue_recursive(&mut self, name: &str, depth: usize) -> Option<u8> {
if depth == 0 {
return None;
}
let parent_idx = depth - 1;
let parent_locals = &self.outer_locals[parent_idx];
if let Some(local_idx) = find_last_local(parent_locals, name) {
let idx = self.add_upvalue(name, UpvalueDesc::Local(local_idx as u8));
return Some(idx);
}
let parent_upvalues = &self.outer_upvalues[parent_idx];
if let Some(upvalue_idx) = parent_upvalues.iter().position(|(n, _)| n == name) {
let idx = self.add_upvalue(name, UpvalueDesc::Upvalue(upvalue_idx as u8));
return Some(idx);
}
if parent_idx > 0 {
if let Some(parent_upvalue_idx) = self.create_parent_upvalue(name, parent_idx) {
let idx = self.add_upvalue(name, UpvalueDesc::Upvalue(parent_upvalue_idx));
return Some(idx);
}
}
None
}
fn create_parent_upvalue(&mut self, name: &str, parent_idx: usize) -> Option<u8> {
if parent_idx > 0 {
let grandparent_idx = parent_idx - 1;
let grandparent_locals = &self.outer_locals[grandparent_idx];
if let Some(local_idx) = find_last_local(grandparent_locals, name) {
let upvalue_idx = self.outer_upvalues[parent_idx].len() as u8;
self.outer_upvalues[parent_idx]
.push((name.to_string(), UpvalueDesc::Local(local_idx as u8)));
return Some(upvalue_idx);
}
let grandparent_upvalues = &self.outer_upvalues[grandparent_idx];
if let Some(gp_upvalue_idx) = grandparent_upvalues.iter().position(|(n, _)| n == name) {
let upvalue_idx = self.outer_upvalues[parent_idx].len() as u8;
self.outer_upvalues[parent_idx]
.push((name.to_string(), UpvalueDesc::Upvalue(gp_upvalue_idx as u8)));
return Some(upvalue_idx);
}
if grandparent_idx > 0
&& let Some(gp_upvalue_idx) = self.create_parent_upvalue(name, grandparent_idx)
{
let upvalue_idx = self.outer_upvalues[parent_idx].len() as u8;
self.outer_upvalues[parent_idx]
.push((name.to_string(), UpvalueDesc::Upvalue(gp_upvalue_idx)));
return Some(upvalue_idx);
}
}
None
}
fn add_upvalue(&mut self, name: &str, desc: UpvalueDesc) -> u8 {
let idx = self.upvalues.len() as u8;
self.upvalues.push((name.to_string(), desc));
idx
}
fn parse_locals(&mut self) -> Result<()> {
self.input.next()?;
if self.input.check_type(TokenType::Function)? {
return self.parse_local_function();
}
let old_local_count = self.locals.len() as u8;
let names = self.parse_namelist()?;
let num_names = names.len() as u8;
if self.input.try_pop(TokenType::Assign)?.is_some() {
let (num_rvalues, last_exp) = self.parse_explist()?;
match num_names.cmp(&num_rvalues) {
Ordering::Less => {
for _ in num_names..num_rvalues {
self.push(Instr::pop());
}
}
Ordering::Greater => {
if let ExpDesc::Prefix(PrefixExp::FunctionCall(num_args)) = last_exp {
self.chunk.code.pop(); self.push(Instr::call(
ArgCount::Fixed(num_args),
RetCount::Fixed(1 + num_names - num_rvalues),
));
} else if let ExpDesc::Vararg = last_exp {
self.chunk.code.pop(); self.push(Instr::vararg(1 + num_names - num_rvalues));
} else {
for _ in num_rvalues..num_names {
self.push(Instr::push_nil());
}
}
}
Ordering::Equal => (),
}
} else {
for _ in &names {
self.push(Instr::push_nil());
}
}
for i in (0..num_names).rev() {
self.push(Instr::set_local(i + old_local_count));
}
for name in names {
self.add_local(name)?;
}
Ok(())
}
fn parse_local_function(&mut self) -> Result<()> {
self.input.next()?; let name = self.expect_identifier()?;
let local_slot = self.locals.len() as u8;
self.add_local(name)?;
self.parse_fndef_named(Some(name.to_string()))?;
self.push(Instr::set_local(local_slot));
Ok(())
}
fn parse_namelist(&mut self) -> Result<Vec<&'a str>> {
let mut names = vec![self.expect_identifier()?];
while self.input.try_pop(TokenType::Comma)?.is_some() {
names.push(self.expect_identifier()?);
}
Ok(names)
}
fn parse_for(&mut self) -> Result<()> {
self.input.next()?; let first_name = self.expect_identifier()?;
self.nest_level += 1;
match self.input.peek_type()? {
TokenType::Assign => {
self.input.next()?; self.parse_numeric_for(first_name)?;
}
TokenType::Comma | TokenType::In => {
self.parse_generic_for(first_name)?;
}
_ => {
let tok = self.input.next()?;
return Err(self.err_unexpected(tok, TokenType::Assign));
}
}
self.level_down();
Ok(())
}
fn parse_numeric_for(&mut self, name: &str) -> Result<()> {
let current_local_slot = self.locals.len() as u8;
self.add_local("")?;
self.add_local("")?;
self.add_local("")?;
self.add_local(name)?;
let body_locals_start = self.locals.len() as u8;
self.parse_expr()?;
self.expect(TokenType::Comma)?;
self.parse_expr()?;
self.parse_numeric_for_step()?;
let loop_start_instr_index = self.chunk.code.len();
self.push(Instr::for_prep(current_local_slot, -1));
self.enter_loop();
self.parse_statements()?;
self.expect(TokenType::End)?;
if self.locals.len() as u8 > body_locals_start {
self.push(Instr::close_upvalues(body_locals_start));
}
let body_length = (self.chunk.code.len() - loop_start_instr_index) as i16;
self.push(Instr::for_loop(current_local_slot, -body_length));
self.chunk.code[loop_start_instr_index] = Instr::for_prep(current_local_slot, body_length);
self.exit_loop();
Ok(())
}
fn parse_numeric_for_step(&mut self) -> Result<()> {
let next_token = self.input.next()?;
match next_token.typ {
TokenType::Comma => {
self.parse_expr()?;
self.expect(TokenType::Do)?;
Ok(())
}
TokenType::Do => {
let i = self.find_or_add_number(1.0)?;
self.push(Instr::push_num(i));
Ok(())
}
_ => Err(self.err_unexpected(next_token, TokenType::Do)),
}
}
fn parse_generic_for(&mut self, first_name: &str) -> Result<()> {
let mut names = vec![first_name.to_string()];
while self.input.try_pop(TokenType::Comma)?.is_some() {
names.push(self.expect_identifier()?.to_string());
}
self.expect(TokenType::In)?;
let base_slot = self.locals.len() as u8;
self.add_local("")?; self.add_local("")?; self.add_local("")?;
let num_loop_vars = names.len() as u8;
for name in &names {
self.add_local(name)?;
}
let body_locals_start = self.locals.len() as u8;
let (num_exprs, last_exp) = self.parse_explist()?;
if num_exprs < 3 {
if let ExpDesc::Prefix(PrefixExp::FunctionCall(_)) = last_exp {
let num_args = match self.chunk.code.pop() {
Some(instr) if instr.opcode() == Instr::OP_CALL => instr.a(),
i => unreachable!("PrefixExp::FunctionCall but last instruction was {:?}", i),
};
self.push(Instr::call(
ArgCount::Fixed(num_args),
RetCount::Fixed(3 - num_exprs + 1),
));
} else {
for _ in num_exprs..3 {
self.push(Instr::push_nil());
}
}
} else if num_exprs > 3 {
for _ in 3..num_exprs {
self.push(Instr::pop());
}
}
self.expect(TokenType::Do)?;
self.push(Instr::tfor_prep(base_slot));
let loop_start = self.chunk.code.len();
self.push(Instr::tfor_call(base_slot, num_loop_vars));
let tforloop_index = self.chunk.code.len();
self.push(Instr::tfor_loop(base_slot, 0));
self.enter_loop();
self.parse_statements()?;
self.expect(TokenType::End)?;
if self.locals.len() as u8 > body_locals_start {
self.push(Instr::close_upvalues(body_locals_start));
}
let body_end = self.chunk.code.len();
self.push(Instr::jump(-((body_end + 1 - loop_start) as i16)));
let exit_offset = (self.chunk.code.len() - tforloop_index - 1) as i16;
self.chunk.code[tforloop_index] = Instr::tfor_loop(base_slot, exit_offset);
self.exit_loop();
Ok(())
}
fn parse_do(&mut self) -> Result<()> {
self.input.next()?; self.nest_level += 1;
self.parse_statements()?;
self.expect(TokenType::End)?;
self.level_down();
Ok(())
}
fn parse_repeat(&mut self) -> Result<()> {
self.input.next()?; self.nest_level += 1;
let body_locals_start = self.locals.len() as u8;
let body_start = self.chunk.code.len() as i16;
self.enter_loop();
self.parse_statements()?;
self.expect(TokenType::Until)?;
self.parse_expr()?;
if self.locals.len() as u8 > body_locals_start {
self.push(Instr::close_upvalues(body_locals_start));
}
let expr_end = self.chunk.code.len() as i16;
self.push(Instr::branch_false(body_start - (expr_end + 1)));
self.exit_loop();
self.level_down();
Ok(())
}
fn parse_while(&mut self) -> Result<()> {
self.input.next()?;
self.nest_level += 1;
let condition_start = self.chunk.code.len();
self.parse_expr()?;
self.expect(TokenType::Do)?;
let test_position = self.chunk.code.len();
self.push(Instr::branch_false(0));
let body_locals_start = self.locals.len() as u8;
self.enter_loop();
self.parse_statements()?;
self.expect(TokenType::End)?;
if self.locals.len() as u8 > body_locals_start {
self.push(Instr::close_upvalues(body_locals_start));
}
let body_end = self.chunk.code.len();
self.push(Instr::jump(-((body_end + 1 - condition_start) as i16)));
let body_len = body_end - test_position;
self.chunk.code[test_position] = Instr::branch_false(body_len as i16);
self.exit_loop();
self.level_down();
Ok(())
}
fn parse_if(&mut self) -> Result<()> {
self.parse_if_arm()
}
fn parse_if_arm(&mut self) -> Result<()> {
self.input.next()?; self.parse_expr()?;
self.expect(TokenType::Then)?;
self.nest_level += 1;
let branch_instr_index = self.chunk.code.len();
self.push(Instr::branch_false(0));
self.parse_statements()?;
let mut branch_target = self.chunk.code.len();
self.close_if_arm()?;
if self.chunk.code.len() > branch_target {
branch_target += 1;
}
let branch_offset = (branch_target - branch_instr_index - 1) as i16;
self.chunk.code[branch_instr_index] = Instr::branch_false(branch_offset);
Ok(())
}
fn close_if_arm(&mut self) -> Result<()> {
self.level_down();
match self.input.peek_type()? {
TokenType::ElseIf => self.parse_else_or_elseif(true),
TokenType::Else => self.parse_else_or_elseif(false),
_ => {
self.expect(TokenType::End)?;
Ok(())
}
}
}
fn parse_else_or_elseif(&mut self, elseif: bool) -> Result<()> {
let jump_instr_index = self.chunk.code.len();
self.push(Instr::jump(0));
if elseif {
self.parse_if_arm()?;
} else {
self.parse_else()?;
}
let new_len = self.chunk.code.len();
let jump_len = new_len - jump_instr_index - 1;
self.chunk.code[jump_instr_index] = Instr::jump(jump_len as i16);
Ok(())
}
fn parse_else(&mut self) -> Result<()> {
self.nest_level += 1;
self.input.next()?; self.parse_statements()?;
self.expect(TokenType::End)?;
self.level_down();
Ok(())
}
#[hotpath::measure]
fn parse_explist(&mut self) -> Result<(u8, ExpDesc)> {
let mut last_exp_desc = self.parse_expr()?;
let mut num_expressions = 1;
while let Some(token) = self.input.try_pop(TokenType::Comma)? {
if num_expressions == u8::MAX {
return Err(self.error_at(SyntaxError::TooManyExpressions, token.start));
}
last_exp_desc = self.parse_expr()?;
num_expressions += 1;
}
Ok((num_expressions, last_exp_desc))
}
#[hotpath::measure]
fn parse_expr(&mut self) -> Result<ExpDesc> {
self.parse_or()
}
fn parse_or(&mut self) -> Result<ExpDesc> {
let mut exp_desc = self.parse_and()?;
while self.input.try_pop(TokenType::Or)?.is_some() {
exp_desc = ExpDesc::Other;
let branch_instr_index = self.chunk.code.len();
self.push(Instr::branch_true_keep(0));
self.push(Instr::pop());
self.parse_and()?;
let branch_offset = (self.chunk.code.len() - branch_instr_index - 1) as i16;
self.chunk.code[branch_instr_index] = Instr::branch_true_keep(branch_offset);
}
Ok(exp_desc)
}
fn parse_and(&mut self) -> Result<ExpDesc> {
let mut exp_desc = self.parse_comparison()?;
while self.input.try_pop(TokenType::And)?.is_some() {
exp_desc = ExpDesc::Other;
let branch_instr_index = self.chunk.code.len();
self.push(Instr::branch_false_keep(0));
self.push(Instr::pop());
self.parse_comparison()?;
let branch_offset = (self.chunk.code.len() - branch_instr_index - 1) as i16;
self.chunk.code[branch_instr_index] = Instr::branch_false_keep(branch_offset);
}
Ok(exp_desc)
}
fn parse_comparison(&mut self) -> Result<ExpDesc> {
let mut exp_desc = self.parse_concat()?;
loop {
let instr = match self.input.peek_type()? {
TokenType::Less => Instr::less(),
TokenType::LessEqual => Instr::less_equal(),
TokenType::Greater => Instr::greater(),
TokenType::GreaterEqual => Instr::greater_equal(),
TokenType::Equal => Instr::equal(),
TokenType::NotEqual => Instr::not_equal(),
_ => break,
};
exp_desc = ExpDesc::Other;
self.input.next()?;
self.parse_concat()?;
self.push(instr);
}
Ok(exp_desc)
}
fn parse_concat(&mut self) -> Result<ExpDesc> {
let mut exp_desc = self.parse_addition()?;
let mut count: u32 = 1;
while self.input.try_pop(TokenType::DotDot)?.is_some() {
exp_desc = ExpDesc::Other;
self.parse_addition()?;
count += 1;
}
if count > 1 {
let n = u8::try_from(count).map_err(|_| self.error(SyntaxError::TooManyExpressions))?;
self.push(Instr::concat(n));
}
Ok(exp_desc)
}
fn parse_addition(&mut self) -> Result<ExpDesc> {
let mut exp_desc = self.parse_multiplication()?;
loop {
let instr = match self.input.peek_type()? {
TokenType::Plus => Instr::add(),
TokenType::Minus => Instr::subtract(),
_ => break,
};
exp_desc = ExpDesc::Other;
self.input.next()?;
self.parse_multiplication()?;
self.push(instr);
}
Ok(exp_desc)
}
fn parse_multiplication(&mut self) -> Result<ExpDesc> {
let mut exp_desc = self.parse_unary()?;
loop {
let instr = match self.input.peek_type()? {
TokenType::Star => Instr::multiply(),
TokenType::Slash => Instr::divide(),
TokenType::Mod => Instr::modulo(),
_ => break,
};
exp_desc = ExpDesc::Other;
self.input.next()?;
self.parse_unary()?;
self.push(instr);
}
Ok(exp_desc)
}
fn parse_unary(&mut self) -> Result<ExpDesc> {
let instr = match self.input.peek_type()? {
TokenType::Not => Instr::not(),
TokenType::Hash => Instr::length(),
TokenType::Minus => Instr::negate(),
_ => {
return self.parse_pow();
}
};
self.input.next()?;
self.parse_unary()?;
self.push(instr);
Ok(ExpDesc::Other)
}
fn parse_pow(&mut self) -> Result<ExpDesc> {
let mut exp_desc = self.parse_primary()?;
if self.input.try_pop(TokenType::Caret)?.is_some() {
exp_desc = ExpDesc::Other;
self.parse_unary()?;
self.push(Instr::pow());
}
Ok(exp_desc)
}
fn parse_primary(&mut self) -> Result<ExpDesc> {
match self.input.peek_type()? {
TokenType::Identifier | TokenType::LParen | TokenType::LParenLineStart => {
let prefix = self.parse_prefix_exp()?;
self.eval_prefix_exp(&prefix);
Ok(prefix.into())
}
_ => self.parse_expr_base(),
}
}
#[hotpath::measure]
fn parse_prefix_exp(&mut self) -> Result<PrefixExp> {
let tok = self.input.next()?;
let prefix = match tok.typ {
TokenType::Identifier => {
let text = self.get_text(tok);
let place = self.parse_prefix_identifier(text)?;
place.into()
}
TokenType::LParen | TokenType::LParenLineStart => {
self.parse_expr()?;
self.expect(TokenType::RParen)?;
PrefixExp::Parenthesized
}
_ => {
return Err(self.err_unexpected(tok, TokenType::Identifier));
}
};
self.parse_prefix_extension(prefix)
}
#[hotpath::measure]
fn parse_prefix_extension(&mut self, base_expr: PrefixExp) -> Result<PrefixExp> {
match self.input.peek_type()? {
TokenType::Dot => {
self.eval_prefix_exp(&base_expr);
self.input.next()?;
let field_name = self.expect_identifier()?;
let name_idx = self.find_or_add_string(field_name)?;
let prefix = PlaceExp::FieldAccess(name_idx).into();
self.parse_prefix_extension(prefix)
}
TokenType::LSquare => {
self.eval_prefix_exp(&base_expr);
self.input.next()?;
self.parse_expr()?;
self.expect(TokenType::RSquare)?;
let prefix = PlaceExp::TableIndex.into();
self.parse_prefix_extension(prefix)
}
TokenType::LParen => {
let adjustment = match &base_expr {
PrefixExp::Place(PlaceExp::FieldAccess(_) | PlaceExp::TableIndex) => 1,
_ => 0,
};
let mark_idx = self.chunk.code.len();
self.push(Instr::mark_call_base(adjustment));
self.eval_prefix_exp(&base_expr);
self.input.next()?;
let (num_args, last_exp) = self.parse_call()?;
let num_args = if let ExpDesc::Vararg = last_exp {
self.chunk.code.pop(); self.push(Instr::vararg(u8::MAX)); u8::MAX } else if let ExpDesc::Prefix(PrefixExp::FunctionCall(_)) = last_exp {
let inner_num_args = match self.chunk.code.pop() {
Some(instr) if instr.opcode() == Instr::OP_CALL => instr.a(),
i => {
unreachable!("PrefixExp::FunctionCall but last instruction was {:?}", i)
}
};
self.push(Instr::call(ArgCount::Fixed(inner_num_args), RetCount::All)); u8::MAX } else {
self.chunk.code.remove(mark_idx);
num_args
};
let prefix = PrefixExp::FunctionCall(num_args);
self.parse_prefix_extension(prefix)
}
TokenType::LParenLineStart => {
let pos = self.input.next()?.start;
Err(self.error_at(SyntaxError::LParenLineStart, pos))
}
TokenType::Colon => {
let adjustment = match &base_expr {
PrefixExp::Place(PlaceExp::FieldAccess(_) | PlaceExp::TableIndex) => 1,
_ => 0,
};
let mark_idx = self.chunk.code.len();
self.push(Instr::mark_call_base(adjustment));
self.eval_prefix_exp(&base_expr);
self.input.next()?; let method_name = self.expect_identifier()?;
let name_idx = self.find_or_add_string(method_name)?;
self.push(Instr::dup());
self.push(Instr::get_field(name_idx));
self.push(Instr::swap());
self.expect(TokenType::LParen)?;
let (num_args, last_exp) = self.parse_call()?;
let num_args = if let ExpDesc::Vararg = last_exp {
self.chunk.code.pop(); self.push(Instr::vararg(u8::MAX)); u8::MAX } else if let ExpDesc::Prefix(PrefixExp::FunctionCall(_)) = last_exp {
let inner_num_args = match self.chunk.code.pop() {
Some(instr) if instr.opcode() == Instr::OP_CALL => instr.a(),
i => {
unreachable!("PrefixExp::FunctionCall but last instruction was {:?}", i)
}
};
self.push(Instr::call(ArgCount::Fixed(inner_num_args), RetCount::All)); u8::MAX } else {
self.chunk.code.remove(mark_idx);
num_args + 1 };
let prefix = PrefixExp::FunctionCall(num_args);
self.parse_prefix_extension(prefix)
}
TokenType::LiteralString | TokenType::LCurly => {
panic!("Unparenthesized function calls unsupported")
}
_ => Ok(base_expr),
}
}
#[hotpath::measure]
fn parse_expr_base(&mut self) -> Result<ExpDesc> {
let tok = self.input.next()?;
match tok.typ {
TokenType::LCurly => self.parse_table()?,
TokenType::LiteralNumber => {
let text = self.get_text(tok);
let number: f64 = text
.parse()
.map_err(|_| self.error_at(SyntaxError::BadNumber, tok.start))?;
let idx = self.find_or_add_number(number)?;
self.push(Instr::push_num(idx));
}
TokenType::LiteralHexNumber => {
let text = &self.get_text(tok)[2..];
let number = u128::from_str_radix(text, 16)
.map_err(|_| self.error_at(SyntaxError::BadNumber, tok.start))?
as f64;
let idx = self.find_or_add_number(number)?;
self.push(Instr::push_num(idx));
}
TokenType::LiteralString => {
let text = self.get_literal_string_contents(tok);
let idx = self.find_or_add_string_bytes(&text)?;
self.push(Instr::push_string(idx));
}
TokenType::Function => self.parse_fndef()?,
TokenType::Nil => self.push(Instr::push_nil()),
TokenType::False => self.push(Instr::push_bool(false)),
TokenType::True => self.push(Instr::push_bool(true)),
TokenType::DotDotDot => {
if !self.chunk.is_vararg {
return Err(self.error(SyntaxError::UnexpectedTok(
"cannot use '...' outside a vararg function".to_string(),
)));
}
self.push(Instr::vararg(1));
return Ok(ExpDesc::Vararg);
}
_ => {
return Err(self.err_unexpected(tok, TokenType::Nil));
}
}
Ok(ExpDesc::Other)
}
fn parse_params(&mut self) -> Result<(Vec<&'a str>, bool)> {
let lparen_tok = self.input.next()?;
match lparen_tok.typ {
TokenType::LParen | TokenType::LParenLineStart => (),
_ => return Err(self.err_unexpected(lparen_tok, TokenType::LParen)),
}
let mut args = Vec::new();
let mut is_vararg = false;
if self.input.try_pop(TokenType::RParen)?.is_some() {
return Ok((args, is_vararg));
}
if self.input.try_pop(TokenType::DotDotDot)?.is_some() {
is_vararg = true;
self.expect(TokenType::RParen)?;
return Ok((args, is_vararg));
}
args.push(self.expect_identifier()?);
while self.input.try_pop(TokenType::Comma)?.is_some() {
if self.input.try_pop(TokenType::DotDotDot)?.is_some() {
is_vararg = true;
break;
}
args.push(self.expect_identifier()?);
}
self.expect(TokenType::RParen)?;
Ok((args, is_vararg))
}
fn parse_fndef(&mut self) -> Result<()> {
self.parse_fndef_named(None)
}
fn parse_fndef_named(&mut self, name: Option<String>) -> Result<()> {
let (params, is_vararg) = self.parse_params()?;
if self.chunk.nested.len() >= u8::MAX as usize {
return Err(self.error(SyntaxError::TooManyNestedFunctions));
}
self.nest_level += 1;
let mut new_chunk = self.parse_chunk(¶ms, is_vararg)?;
new_chunk.name = name;
self.level_down();
self.chunk.nested.push(Arc::new(new_chunk));
self.push(Instr::closure(self.chunk.nested.len() as u8 - 1));
self.expect(TokenType::End)?;
Ok(())
}
fn parse_fndef_method(&mut self, name: Option<String>) -> Result<()> {
let (mut params, is_vararg) = self.parse_params()?;
params.insert(0, "self");
if self.chunk.nested.len() >= u8::MAX as usize {
return Err(self.error(SyntaxError::TooManyNestedFunctions));
}
self.nest_level += 1;
let mut new_chunk = self.parse_chunk(¶ms, is_vararg)?;
new_chunk.name = name;
self.level_down();
self.chunk.nested.push(Arc::new(new_chunk));
self.push(Instr::closure(self.chunk.nested.len() as u8 - 1));
self.expect(TokenType::End)?;
Ok(())
}
#[hotpath::measure]
fn parse_table(&mut self) -> Result<()> {
self.push(Instr::new_table());
while let TokenType::Comma | TokenType::Semi = self.input.peek_type()? {
self.input.next()?;
}
if self.input.try_pop(TokenType::RCurly)?.is_none() {
let mut i = 0;
let mut has_vararg = false;
let (new_i, is_vararg) = self.parse_table_entry(i)?;
i = new_i;
has_vararg = has_vararg || is_vararg;
while let TokenType::Comma | TokenType::Semi = self.input.peek_type()? {
self.input.next()?;
if self.input.check_type(TokenType::RCurly)? {
break;
}
let (new_i, is_vararg) = self.parse_table_entry(i)?;
i = new_i;
has_vararg = has_vararg || is_vararg;
}
self.expect(TokenType::RCurly)?;
if has_vararg {
self.push(Instr::set_list(0));
} else if i > 0 {
self.push(Instr::set_list(i));
}
}
Ok(())
}
#[hotpath::measure]
fn parse_table_entry(&mut self, counter: u8) -> Result<(u8, bool)> {
match self.input.peek_type()? {
TokenType::Identifier => {
let index = self.expect_identifier_id()?;
self.expect(TokenType::Assign)?;
self.parse_expr()?;
self.push(Instr::init_field(counter, index));
Ok((counter, false))
}
TokenType::LSquare => {
self.input.next()?;
self.parse_expr()?;
self.expect(TokenType::RSquare)?;
self.expect(TokenType::Assign)?;
self.parse_expr()?;
self.push(Instr::init_index(counter));
Ok((counter, false))
}
TokenType::DotDotDot => {
self.input.next()?;
if !self.chunk.is_vararg {
return Err(self.error(SyntaxError::UnexpectedTok(
"cannot use '...' outside a vararg function".to_string(),
)));
}
self.push(Instr::vararg(u8::MAX));
Ok((counter, true))
}
_ => {
if counter == u8::MAX {
return Err(self.error(SyntaxError::TooManyTableFields));
}
self.parse_expr()?;
Ok((counter + 1, false))
}
}
}
#[hotpath::measure]
fn parse_call(&mut self) -> Result<(u8, ExpDesc)> {
let tup = if self.input.check_type(TokenType::RParen)? {
(0, ExpDesc::Other)
} else {
self.parse_explist()?
};
self.expect(TokenType::RParen)?;
Ok(tup)
}
}
#[must_use]
fn find_last_local(locals: &[(String, i32)], name: &str) -> Option<usize> {
let mut i = locals.len();
while i > 0 {
i -= 1;
if locals[i].0 == name {
return Some(i);
}
}
None
}
fn push_char_bytes(out: &mut Vec<u8>, c: char) {
let mut buf = [0; 4];
out.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
}
fn find_or_add<T, E>(queue: &mut Vec<T>, x: &E) -> Option<u8>
where
T: Borrow<E> + PartialEq<E>,
E: PartialEq<T> + ToOwned<Owned = T> + ?Sized,
{
match queue.iter().position(|y| y == x) {
Some(i) => Some(i as u8),
None => {
let i = queue.len();
if i == u8::MAX as usize {
None
} else {
queue.push(x.to_owned());
Some(i as u8)
}
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::Bytecode;
use super::Instr;
use super::parse_str;
use crate::instr::{ArgCount, Builtin, RetCount};
fn clear_line_info(chunk: &mut Bytecode) {
chunk.line_info.clear();
for nested in &mut chunk.nested {
let inner = Arc::get_mut(nested).expect("test fixture should own its nested chunks");
clear_line_info(inner);
}
}
fn check_it(input: &str, mut output: Bytecode) {
output.is_vararg = true;
let mut actual = parse_str(input).unwrap();
clear_line_info(&mut actual);
assert_eq!(actual, output);
}
#[test]
fn test01() {
let text = "x = 5 + 6";
let out = Bytecode {
code: vec![
Instr::push_num(0),
Instr::push_num(1),
Instr::add(),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
],
number_literals: vec![5.0, 6.0],
string_literals: vec!["x".into()],
..Bytecode::default()
};
check_it(text, out);
}
#[test]
fn test02() {
let text = "x = -5^2";
let out = Bytecode {
code: vec![
Instr::push_num(0),
Instr::push_num(1),
Instr::pow(),
Instr::negate(),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
],
number_literals: vec![5.0, 2.0],
string_literals: vec!["x".into()],
..Bytecode::default()
};
check_it(text, out);
}
#[test]
fn test03() {
let text = "x = 5 + true .. 'hi'";
let out = Bytecode {
code: vec![
Instr::push_num(0),
Instr::push_bool(true),
Instr::add(),
Instr::push_string(1),
Instr::concat(2),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
],
number_literals: vec![5.0],
string_literals: vec!["x".into(), "hi".into()],
..Bytecode::default()
};
check_it(text, out);
}
#[test]
fn test04() {
let text = "x = 1 .. 2 + 3";
let output = Bytecode {
code: vec![
Instr::push_num(0),
Instr::push_num(1),
Instr::push_num(2),
Instr::add(),
Instr::concat(2),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
],
number_literals: vec![1.0, 2.0, 3.0],
string_literals: vec!["x".into()],
..Bytecode::default()
};
check_it(text, output);
}
#[test]
fn concat_chain_emits_single_n_ary_concat() {
let text = r#"x = "a" .. "b" .. "c" .. "d""#;
let output = Bytecode {
code: vec![
Instr::push_string(1),
Instr::push_string(2),
Instr::push_string(3),
Instr::push_string(4),
Instr::concat(4),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
],
string_literals: vec!["x".into(), "a".into(), "b".into(), "c".into(), "d".into()],
..Bytecode::default()
};
check_it(text, output);
}
#[test]
fn test05() {
let text = "x = 2^-3";
let output = Bytecode {
code: vec![
Instr::push_num(0),
Instr::push_num(1),
Instr::negate(),
Instr::pow(),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
],
number_literals: vec![2.0, 3.0],
string_literals: vec!["x".into()],
..Bytecode::default()
};
check_it(text, output);
}
#[test]
fn test06() {
let text = "x= not not 1";
let output = Bytecode {
code: vec![
Instr::push_num(0),
Instr::not(),
Instr::not(),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
],
number_literals: vec![1.0],
string_literals: vec!["x".into()],
..Bytecode::default()
};
check_it(text, output);
}
#[test]
fn test07() {
let text = "a = 5";
let output = Bytecode {
code: vec![
Instr::push_num(0),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
],
number_literals: vec![5.0],
string_literals: vec!["a".into()],
..Bytecode::default()
};
check_it(text, output);
}
#[test]
fn test08() {
let text = "x = true and false";
let output = Bytecode {
code: vec![
Instr::push_bool(true),
Instr::branch_false_keep(2),
Instr::pop(),
Instr::push_bool(false),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
],
string_literals: vec!["x".into()],
..Bytecode::default()
};
check_it(text, output);
}
#[test]
fn test09() {
let text = "x = 5 or nil and true";
let code = vec![
Instr::push_num(0),
Instr::branch_true_keep(5),
Instr::pop(),
Instr::push_nil(),
Instr::branch_false_keep(2),
Instr::pop(),
Instr::push_bool(true),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
];
let output = Bytecode {
code,
number_literals: vec![5.0],
string_literals: vec!["x".into()],
..Bytecode::default()
};
check_it(text, output);
}
#[test]
fn test10() {
let text = "if true then a = 5 end";
let code = vec![
Instr::push_bool(true),
Instr::branch_false(2),
Instr::push_num(0),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
number_literals: vec![5.0],
string_literals: vec!["a".into()],
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test11() {
let text = "if true then a = 5 if true then b = 4 end end";
let code = vec![
Instr::push_bool(true),
Instr::branch_false(6),
Instr::push_num(0),
Instr::set_global(0),
Instr::push_bool(true),
Instr::branch_false(2),
Instr::push_num(1),
Instr::set_global(1),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
number_literals: vec![5.0, 4.0],
string_literals: vec!["a".into(), "b".into()],
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test12() {
let text = "if true then a = 5 else a = 4 end";
let code = vec![
Instr::push_bool(true),
Instr::branch_false(3),
Instr::push_num(0),
Instr::set_global(0),
Instr::jump(2),
Instr::push_num(1),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
number_literals: vec![5.0, 4.0],
string_literals: vec!["a".into()],
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test13() {
let text = "if true then a = 5 elseif 6 == 7 then a = 3 else a = 4 end";
let code = vec![
Instr::push_bool(true),
Instr::branch_false(3),
Instr::push_num(0),
Instr::set_global(0),
Instr::jump(9),
Instr::push_num(1),
Instr::push_num(2),
Instr::equal(),
Instr::branch_false(3),
Instr::push_num(3),
Instr::set_global(0),
Instr::jump(2),
Instr::push_num(4),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
number_literals: vec![5.0, 6.0, 7.0, 3.0, 4.0],
string_literals: vec!["a".into()],
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test14() {
let text = "while a < 10 do a = a + 1 end";
let code = vec![
Instr::get_global(0),
Instr::push_num(0),
Instr::less(),
Instr::branch_false(5),
Instr::get_global(0),
Instr::push_num(1),
Instr::add(),
Instr::set_global(0),
Instr::jump(-9),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
number_literals: vec![10.0, 1.0],
string_literals: vec!["a".into()],
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test15() {
let text = "repeat local x = 5 until a == b y = 4";
let code = vec![
Instr::push_num(0),
Instr::set_local(0),
Instr::get_global(0),
Instr::get_global(1),
Instr::equal(),
Instr::close_upvalues(0), Instr::branch_false(-7),
Instr::push_num(1),
Instr::set_global(2),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
number_literals: vec![5.0, 4.0],
string_literals: vec!["a".into(), "b".into(), "y".into()],
num_locals: 1,
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test16() {
let text = "local i i = 2";
let code = vec![
Instr::push_nil(),
Instr::set_local(0),
Instr::push_num(0),
Instr::set_local(0),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
number_literals: vec![2.0],
num_locals: 1,
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test17() {
let text = "local i, j print(j)";
let code = vec![
Instr::push_nil(),
Instr::push_nil(),
Instr::set_local(1),
Instr::set_local(0),
Instr::get_builtin(Builtin::Print),
Instr::get_local(1),
Instr::call(ArgCount::Fixed(1), RetCount::Fixed(0)),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
num_locals: 2,
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test18() {
let text = "local i do local i x = i end x = i";
let code = vec![
Instr::push_nil(),
Instr::set_local(0),
Instr::push_nil(),
Instr::set_local(1),
Instr::get_local(1),
Instr::set_global(0),
Instr::get_local(0),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
string_literals: vec!["x".into()],
num_locals: 2,
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test19() {
let text = "do local i x = i end x = i";
let code = vec![
Instr::push_nil(),
Instr::set_local(0),
Instr::get_local(0),
Instr::set_global(0),
Instr::get_global(1),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
string_literals: vec!["x".into(), "i".into()],
num_locals: 1,
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test20() {
let text = "local i if false then local i else x = i end";
let code = vec![
Instr::push_nil(),
Instr::set_local(0),
Instr::push_bool(false),
Instr::branch_false(3),
Instr::push_nil(),
Instr::set_local(1),
Instr::jump(2),
Instr::get_local(0),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
string_literals: vec!["x".into()],
num_locals: 2,
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test21() {
let text = "for i = 1,5 do x = i end";
let code = vec![
Instr::push_num(0),
Instr::push_num(1),
Instr::push_num(0),
Instr::for_prep(0, 3),
Instr::get_local(3),
Instr::set_global(0),
Instr::for_loop(0, -3),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
number_literals: vec![1.0, 5.0],
string_literals: vec!["x".into()],
num_locals: 4,
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test22() {
let text = "a, b = 1";
let code = vec![
Instr::push_num(0),
Instr::push_nil(),
Instr::set_global(1),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
number_literals: vec![1.0],
string_literals: vec!["a".into(), "b".into()],
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test23() {
let text = "a, b = 1, 2";
let code = vec![
Instr::push_num(0),
Instr::push_num(1),
Instr::set_global(1),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
number_literals: vec![1.0, 2.0],
string_literals: vec!["a".into(), "b".into()],
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test24() {
let text = "a, b = 1, 2, 3";
let code = vec![
Instr::push_num(0),
Instr::push_num(1),
Instr::push_num(2),
Instr::pop(),
Instr::set_global(1),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
number_literals: vec![1.0, 2.0, 3.0],
string_literals: vec!["a".into(), "b".into()],
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test25() {
let text = "puts()";
let code = vec![
Instr::get_global(0),
Instr::call(ArgCount::Fixed(0), RetCount::Fixed(0)),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
string_literals: vec!["puts".into()],
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test26() {
let text = "y = {x = 5,}";
let code = vec![
Instr::new_table(),
Instr::push_num(0),
Instr::init_field(0, 1),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
number_literals: vec![5.0],
string_literals: vec!["y".into(), "x".into()],
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test27() {
let text = "local x = t.x.y";
let code = vec![
Instr::get_global(0),
Instr::get_field(1),
Instr::get_field(2),
Instr::set_local(0),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
string_literals: vec!["t".into(), "x".into(), "y".into()],
num_locals: 1,
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test28() {
let text = "x = function () end";
let code = vec![
Instr::closure(0),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
];
let string_literals = vec!["x".into()];
let nested = vec![Arc::new(Bytecode {
code: vec![Instr::ret(RetCount::Fixed(0))],
..Bytecode::default()
})];
let chunk = Bytecode {
code,
string_literals,
nested,
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test29() {
let text = "x = function () local y = 7 end";
let inner_chunk = Bytecode {
code: vec![
Instr::push_num(0),
Instr::set_local(0),
Instr::ret(RetCount::Fixed(0)),
],
number_literals: vec![7.0],
num_locals: 1,
..Bytecode::default()
};
let outer_chunk = Bytecode {
code: vec![
Instr::closure(0),
Instr::set_global(0),
Instr::ret(RetCount::Fixed(0)),
],
string_literals: vec!["x".into()],
nested: vec![Arc::new(inner_chunk)],
..Bytecode::default()
};
check_it(text, outer_chunk);
}
#[test]
fn test30() {
let text = "
z = function () local z = 21 end
x = function ()
local y = function () end
print(y)
end";
let z = Bytecode {
code: vec![
Instr::push_num(0),
Instr::set_local(0),
Instr::ret(RetCount::Fixed(0)),
],
number_literals: vec![21.0],
num_locals: 1,
..Bytecode::default()
};
let y = Bytecode {
code: vec![Instr::ret(RetCount::Fixed(0))],
..Bytecode::default()
};
let x = Bytecode {
code: vec![
Instr::closure(0),
Instr::set_local(0),
Instr::get_builtin(Builtin::Print),
Instr::get_local(0),
Instr::call(ArgCount::Fixed(1), RetCount::Fixed(0)),
Instr::ret(RetCount::Fixed(0)),
],
nested: vec![Arc::new(y)],
num_locals: 1,
..Bytecode::default()
};
let outer_chunk = Bytecode {
code: vec![
Instr::closure(0),
Instr::set_global(0),
Instr::closure(1),
Instr::set_global(1),
Instr::ret(RetCount::Fixed(0)),
],
nested: vec![Arc::new(z), Arc::new(x)],
string_literals: vec!["z".into(), "x".into()],
..Bytecode::default()
};
check_it(text, outer_chunk);
}
#[test]
fn test31() {
let text = "local s = type(4)";
let code = vec![
Instr::get_builtin(Builtin::Type),
Instr::push_num(0),
Instr::call(ArgCount::Fixed(1), RetCount::Fixed(1)),
Instr::set_local(0),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
num_locals: 1,
number_literals: vec![4.0],
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test32() {
let text = "local type, print print(type(nil))";
let code = vec![
Instr::push_nil(),
Instr::push_nil(),
Instr::set_local(1),
Instr::set_local(0),
Instr::mark_call_base(0), Instr::get_local(1), Instr::get_local(0), Instr::push_nil(), Instr::call(ArgCount::Fixed(1), RetCount::All), Instr::call(ArgCount::Dynamic, RetCount::Fixed(0)), Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
num_locals: 2,
..Bytecode::default()
};
check_it(text, chunk);
}
#[test]
fn test33() {
use super::*;
let text = "print()\n(foo)()\n";
match parse_str(text) {
Err(Error {
kind: ErrorKind::SyntaxError(SyntaxError::LParenLineStart),
line_num,
column,
..
}) => {
assert_eq!(line_num, 2);
assert_eq!(column, 1);
}
_ => panic!("Should detect ambiguous function call because of linebreak"),
}
}
#[test]
fn test34() {
let text = "while false do local b end b()";
let code = vec![
Instr::push_bool(false),
Instr::branch_false(4),
Instr::push_nil(),
Instr::set_local(0),
Instr::close_upvalues(0), Instr::jump(-6),
Instr::get_global(0),
Instr::call(ArgCount::Fixed(0), RetCount::Fixed(0)),
Instr::ret(RetCount::Fixed(0)),
];
let chunk = Bytecode {
code,
num_locals: 1,
string_literals: vec!["b".into()],
..Bytecode::default()
};
check_it(text, chunk);
}
}