diff --git a/Cargo.toml b/Cargo.toml index 954aa98..6491be9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "luah" -version = "0.1.0" +version = "1.1.0" edition = "2021" -description = "An unofficial lua impl by HydroRoll-Team." +description = "Unofficial lua impl by HydroRoll-Team." license = "AGPL-3.0-or-later" documentation = "https://luah.hydroroll.team/" homepage = "https://hydroroll.team/" diff --git a/luah/__main__.py b/luah/__main__.py new file mode 100644 index 0000000..dfbcec1 --- /dev/null +++ b/luah/__main__.py @@ -0,0 +1,50 @@ +import argparse +import sys +from .LibCore import raw_input + +class Cli: + def __init__(self): + self.parser = argparse.ArgumentParser(description="luah") + self._setup_arguments() + self.args = self.parser.parse_args() + + if self.args.file: + self.process_file(self.args.file) + elif len(sys.argv) > 1 and sys.argv[1].endswith(".lua"): + self.process_file(sys.argv[1]) + + def _setup_arguments(self): + self.parser.add_argument("--version", action="version", version="luah 0.1.0") + self.parser.add_argument("--verbose", action="store_true", help="verbose mode") + self.parser.add_argument("--debug", action="store_true", help="debug mode") + self.parser.add_argument("--quiet", action="store_true", help="quiet mode") + self.parser.add_argument("--config", help="config file") + self.parser.add_argument("--file", help="file to process") + self.parser.add_argument("--log", help="log file") + self.parser.add_argument("--log-level", help="log level") + self.parser.add_argument("--log-format", help="log format") + self.parser.add_argument("--log-date-format", help="log date format") + self.parser.add_argument("--log-file-max-size", help="log file max size") + self.parser.add_argument("--log-file-backup-count", help="log file backup count") + self.parser.add_argument("--log-file-rotation-count", help="log file rotation count") + self.parser.add_argument("--log-file-rotation-interval", help="log file rotation interval") + self.parser.add_argument("--log-file-rotation-backup-count", help="log file rotation backup count") + self.parser.add_argument("--log-file-rotation-backup-interval", help="log file rotation backup interval") + + def process_file(self, file_name): + try: + raw_input(file_name) + except Exception as e: + print(f"Error processing file {file_name}: {e}") + + def get_args(self): + return self.args + + def get_help(self): + return self.parser.format_help() + +if __name__ == "__main__": + cli = Cli() + args = cli.get_args() + if args.verbose: + print("Verbose mode is enabled.") diff --git a/pyproject.toml b/pyproject.toml index dbbfba5..05e8408 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ repository = "https://github.com/HydroRoll-Team/luah" documentation = "https://luah.hydroroll.team/" [project.scripts] -luah = "luah.cli:main" +luah = "luah.__main__:Cli" [tool.maturin] features = ["pyo3/extension-module"] diff --git a/src/bytecode.rs b/src/bytecode.rs index 1716fde..6b14520 100644 --- a/src/bytecode.rs +++ b/src/bytecode.rs @@ -1,13 +1,122 @@ #[derive(Debug)] pub enum ByteCode { - GetGlobal(u8, u8), - SetGlobal(u8, u8), - SetGlobalConst(u8, u8), // TODO u8? - SetGlobalGlobal(u8, u8), + // local variable LoadConst(u8, u16), - LoadNil(u8), + LoadNil(u8, u8), LoadBool(u8, bool), LoadInt(u8, i16), Move(u8, u8), - Call(u8, u8), + + // upvalues + GetUpvalue(u8, u8), + SetUpvalue(u8, u8), + SetUpvalueConst(u8, u8), + Close(u8), + + // table + NewTable(u8, u8, u8), + SetTable(u8, u8, u8), + SetField(u8, u8, u8), + SetInt(u8, u8, u8), + SetTableConst(u8, u8, u8), + SetFieldConst(u8, u8, u8), + SetIntConst(u8, u8, u8), + SetList(u8, u8), + GetTable(u8, u8, u8), + GetField(u8, u8, u8), + GetInt(u8, u8, u8), + GetFieldSelf(u8, u8, u8), + + // upvalue table, covers global variables + SetUpField(u8, u8, u8), + SetUpFieldConst(u8, u8, u8), + GetUpField(u8, u8, u8), + + // condition structures + Jump(i16), + TestAndJump(u8, i16), + TestOrJump(u8, i16), + TestAndSetJump(u8, u8, u8), + TestOrSetJump(u8, u8, u8), + + // for-loop + ForPrepare(u8, u16), + ForLoop(u8, u16), + ForCallLoop(u8, u8, u8), + + // function call + Closure(u8, u16), + Call(u8, u8, u8), + CallSet(u8, u8, u8), + TailCall(u8, u8), + Return0, + Return(u8, u8), + VarArgs(u8, u8), + + // unops + Neg(u8, u8), + Not(u8, u8), + BitNot(u8, u8), + Len(u8, u8), + + // binops + Add(u8, u8, u8), + AddConst(u8, u8, u8), + AddInt(u8, u8, u8), + Sub(u8, u8, u8), + SubInt(u8, u8, u8), + SubConst(u8, u8, u8), + Mul(u8, u8, u8), + MulInt(u8, u8, u8), + MulConst(u8, u8, u8), + Mod(u8, u8, u8), + ModInt(u8, u8, u8), + ModConst(u8, u8, u8), + Div(u8, u8, u8), + DivInt(u8, u8, u8), + DivConst(u8, u8, u8), + Idiv(u8, u8, u8), + IdivInt(u8, u8, u8), + IdivConst(u8, u8, u8), + Pow(u8, u8, u8), + PowInt(u8, u8, u8), + PowConst(u8, u8, u8), + BitAnd(u8, u8, u8), + BitAndInt(u8, u8, u8), + BitAndConst(u8, u8, u8), + BitXor(u8, u8, u8), + BitXorInt(u8, u8, u8), + BitXorConst(u8, u8, u8), + BitOr(u8, u8, u8), + BitOrInt(u8, u8, u8), + BitOrConst(u8, u8, u8), + ShiftL(u8, u8, u8), + ShiftLInt(u8, u8, u8), + ShiftLConst(u8, u8, u8), + ShiftR(u8, u8, u8), + ShiftRInt(u8, u8, u8), + ShiftRConst(u8, u8, u8), + + Equal(u8, u8, bool), + EqualInt(u8, u8, bool), + EqualConst(u8, u8, bool), + NotEq(u8, u8, bool), + NotEqInt(u8, u8, bool), + NotEqConst(u8, u8, bool), + LesEq(u8, u8, bool), + LesEqInt(u8, u8, bool), + LesEqConst(u8, u8, bool), + GreEq(u8, u8, bool), + GreEqInt(u8, u8, bool), + GreEqConst(u8, u8, bool), + Less(u8, u8, bool), + LessInt(u8, u8, bool), + LessConst(u8, u8, bool), + Greater(u8, u8, bool), + GreaterInt(u8, u8, bool), + GreaterConst(u8, u8, bool), + + SetFalseSkip(u8), + + Concat(u8, u8, u8), } \ No newline at end of file diff --git a/src/lex.rs b/src/lex.rs index 5ba26e8..cdf6826 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -1,8 +1,7 @@ use std::mem; -use std::fs::File; -use std::io::{Read, Seek, SeekFrom}; +use std::io::{Read, Bytes}; +use std::iter::Peekable; -// ANCHOR: token #[derive(Debug, PartialEq)] pub enum Token { // keywords @@ -14,7 +13,7 @@ pub enum Token { // + - * / % ^ # Add, Sub, Mul, Div, Mod, Pow, Len, // & ~ | << >> // - BitAnd, BitXor, BitOr, ShiftL, ShiftR, Idiv, + BitAnd, BitNot, BitOr, ShiftL, ShiftR, Idiv, // == ~= <= >= < > = Equal, NotEq, LesEq, GreEq, Less, Greater, Assign, // ( ) { } [ ] :: @@ -25,7 +24,7 @@ pub enum Token { // constant values Integer(i64), Float(f64), - String(String), + String(Vec), // name of variables or table keys Name(String), @@ -33,25 +32,21 @@ pub enum Token { // end Eos, } -// ANCHOR_END: token #[derive(Debug)] -// ANCHOR: lex -pub struct Lex { - input: File, +pub struct Lex { + input: Peekable::>, ahead: Token, } -// ANCHOR_END: lex -impl Lex { - pub fn new(input: File) -> Self { +impl Lex { + pub fn new(input: R) -> Self { Lex { - input, + input: input.bytes().peekable(), ahead: Token::Eos, } } -// ANCHOR: peek_next pub fn next(&mut self) -> Token { if self.ahead == Token::Eos { self.do_next() @@ -66,178 +61,180 @@ impl Lex { } &self.ahead } -// ANCHOR_END: peek_next +// + pub fn expect(&mut self, t: Token) { + assert_eq!(self.next(), t); + } fn do_next(&mut self) -> Token { - let ch = self.read_char(); - match ch { - '\n' | '\r' | '\t' | ' ' => self.do_next(), - '+' => Token::Add, - '*' => Token::Mul, - '%' => Token::Mod, - '^' => Token::Pow, - '#' => Token::Len, - '&' => Token::BitAnd, - '|' => Token::BitOr, - '(' => Token::ParL, - ')' => Token::ParR, - '{' => Token::CurlyL, - '}' => Token::CurlyR, - '[' => Token::SqurL, - ']' => Token::SqurR, - ';' => Token::SemiColon, - ',' => Token::Comma, - '/' => self.check_ahead('/', Token::Idiv, Token::Div), - '=' => self.check_ahead('=', Token::Equal, Token::Assign), - '~' => self.check_ahead('=', Token::NotEq, Token::BitXor), - ':' => self.check_ahead(':', Token::DoubColon, Token::Colon), - '<' => self.check_ahead2('=', Token::LesEq, '<', Token::ShiftL, Token::Less), - '>' => self.check_ahead2('=', Token::GreEq, '>', Token::ShiftR, Token::Greater), - '\'' | '"' => self.read_string(ch), - '.' => match self.read_char() { - '.' => { - if self.read_char() == '.' { - Token::Dots + if let Some(byt) = self.next_byte() { + match byt { + b'\n' | b'\r' | b'\t' | b' ' => self.do_next(), + b'+' => Token::Add, + b'*' => Token::Mul, + b'%' => Token::Mod, + b'^' => Token::Pow, + b'#' => Token::Len, + b'&' => Token::BitAnd, + b'|' => Token::BitOr, + b'(' => Token::ParL, + b')' => Token::ParR, + b'{' => Token::CurlyL, + b'}' => Token::CurlyR, + b'[' => Token::SqurL, + b']' => Token::SqurR, + b';' => Token::SemiColon, + b',' => Token::Comma, + b'/' => self.check_ahead(b'/', Token::Idiv, Token::Div), + b'=' => self.check_ahead(b'=', Token::Equal, Token::Assign), + b'~' => self.check_ahead(b'=', Token::NotEq, Token::BitNot), + b':' => self.check_ahead(b':', Token::DoubColon, Token::Colon), + b'<' => self.check_ahead2(b'=', Token::LesEq, b'<', Token::ShiftL, Token::Less), + b'>' => self.check_ahead2(b'=', Token::GreEq, b'>', Token::ShiftR, Token::Greater), + b'\'' | b'"' => self.read_string(byt), + b'.' => match self.peek_byte() { + b'.' => { + self.next_byte(); + if self.peek_byte() == b'.' { + self.next_byte(); + Token::Dots + } else { + Token::Concat + } + } + b'0'..=b'9' => self.read_decimal('.'), + _ => Token::Dot, + } + b'-' => { + if self.peek_byte() == b'-' { + self.next_byte(); + self.read_comment(); + self.do_next() } else { - self.putback_char(); - Token::Concat + Token::Sub } - }, - '0'..='9' => { - self.putback_char(); - self.read_number_fraction(0) - }, - _ => { - self.putback_char(); - Token::Dot - }, - }, - '-' => { - if self.read_char() == '-' { - self.read_comment(); - self.do_next() - } else { - self.putback_char(); - Token::Sub } - }, - '0'..='9' => self.read_number(ch), - 'A'..='Z' | 'a'..='z' | '_' => self.read_name(ch), - '\0' => Token::Eos, - _ => panic!("invalid char {ch}"), + ch@b'0'..=b'9' => self.read_decimal(ch as char), + b'A'..=b'Z' | b'a'..=b'z' | b'_' => self.read_name(byt), + _ => panic!("invalid char {byt}"), + } + } else { + Token::Eos } } - #[allow(clippy::unused_io_amount)] - fn read_char(&mut self) -> char { - let mut buf: [u8; 1] = [0]; - self.input.read(&mut buf).unwrap(); - buf[0] as char + fn peek_byte(&mut self) -> u8 { + match self.input.peek() { + Some(Ok(byt)) => *byt, + Some(_) => panic!("lex peek error"), + None => b'\0', // good for usage + } } - fn putback_char(&mut self) { - self.input.seek(SeekFrom::Current(-1)).unwrap(); + fn next_byte(&mut self) -> Option { + self.input.next().map(|r| r.unwrap()) } - fn check_ahead(&mut self, ahead: char, long: Token, short: Token) -> Token { - if self.read_char() == ahead { + fn check_ahead(&mut self, ahead: u8, long: Token, short: Token) -> Token { + if self.peek_byte() == ahead { + self.next_byte(); long } else { - self.putback_char(); short } } - fn check_ahead2(&mut self, ahead1: char, long1: Token, ahead2: char, long2: Token, short: Token) -> Token { - let ch = self.read_char(); - if ch == ahead1 { + fn check_ahead2(&mut self, ahead1: u8, long1: Token, ahead2: u8, long2: Token, short: Token) -> Token { + let byt = self.peek_byte(); + if byt == ahead1 { + self.next_byte(); long1 - } else if ch == ahead2 { + } else if byt == ahead2 { + self.next_byte(); long2 } else { - self.putback_char(); short } } - fn read_number(&mut self, first: char) -> Token { - // heximal - if first == '0' { - let second = self.read_char(); - if second == 'x' || second == 'X' { - return self.read_heximal(); - } - self.putback_char(); - } - - // decimal - let mut n = char::to_digit(first, 10).unwrap() as i64; + fn read_decimal(&mut self, ahead: char) -> Token { + // TODO heximal + let mut is_float = ahead == '.'; + let mut buf = String::new(); + buf.push(ahead); loop { - let ch = self.read_char(); - if let Some(d) = char::to_digit(ch, 10) { - n = n * 10 + d as i64; - } else if ch == '.' { - return self.read_number_fraction(n); - } else if ch == 'e' || ch == 'E' { - return self.read_number_exp(n as f64); - } else { - self.putback_char(); - break; + let byt = self.peek_byte(); + match byt { + b'0' ..= b'9' => buf.push(byt as char), + b'.' | b'e' | b'E' | b'+' | b'-' => { + buf.push(byt as char); + is_float = true; + } + _ => break, } + self.next_byte(); } - // check following - let fch = self.read_char(); - if fch.is_alphabetic() || fch == '.' { - panic!("malformat number"); + if is_float { + Token::Float(buf.parse::().unwrap()) } else { - self.putback_char(); + Token::Integer(buf.parse::().unwrap()) } - - Token::Integer(n) } - fn read_number_fraction(&mut self, i: i64) -> Token { - let mut n: i64 = 0; - let mut x: f64 = 1.0; + + fn read_string(&mut self, quote: u8) -> Token { + let mut s = Vec::new(); loop { - let ch = self.read_char(); - if let Some(d) = char::to_digit(ch, 10) { - n = n * 10 + d as i64; - x *= 10.0; - } else { - self.putback_char(); - break; + match self.next_byte().expect("unfinished string") { + b'\n' => panic!("unfinished string"), + b'\\' => s.push(self.read_escape()), + byt if byt == quote => break, + byt => s.push(byt), } } - Token::Float(i as f64 + n as f64 / x) - } - fn read_number_exp(&mut self, _: f64) -> Token { - todo!("lex number exp") - } - fn read_heximal(&mut self) -> Token { - todo!("lex heximal") + Token::String(s) } - - fn read_string(&mut self, quote: char) -> Token { - let mut s = String::new(); - loop { - match self.read_char() { - '\n' | '\0' => panic!("unfinished string"), - '\\' => todo!("escape"), - ch if ch == quote => break, - ch => s.push(ch), + fn read_escape(&mut self) -> u8 { + match self.next_byte().expect("string escape") { + b'a' => 0x07, + b'b' => 0x08, + b'f' => 0x0c, + b'v' => 0x0b, + b'n' => b'\n', + b'r' => b'\r', + b't' => b'\t', + b'\\' => b'\\', + b'"' => b'"', + b'\'' => b'\'', + b'x' => { // format: \xXX + let n1 = char::to_digit(self.next_byte().unwrap() as char, 16).unwrap(); + let n2 = char::to_digit(self.next_byte().unwrap() as char, 16).unwrap(); + (n1 * 16 + n2) as u8 + } + ch@b'0'..=b'9' => { // format: \d[d[d]] + let mut n = char::to_digit(ch as char, 10).unwrap(); // TODO no unwrap + if let Some(d) = char::to_digit(self.peek_byte() as char, 10) { + self.next_byte(); + n = n * 10 + d; + if let Some(d) = char::to_digit(self.peek_byte() as char, 10) { + self.next_byte(); + n = n * 10 + d; + } + } + u8::try_from(n).expect("decimal escape too large") } + _ => panic!("invalid string escape") } - Token::String(s) } - fn read_name(&mut self, first: char) -> Token { - let mut s = first.to_string(); + fn read_name(&mut self, first: u8) -> Token { + let mut s = String::new(); + s.push(first as char); loop { - let ch = self.read_char(); + let ch = self.peek_byte() as char; if ch.is_alphanumeric() || ch == '_' { + self.next_byte(); s.push(ch); } else { - self.putback_char(); break; } } @@ -271,12 +268,12 @@ impl Lex { // '--' has been read fn read_comment(&mut self) { - match self.read_char() { - '[' => todo!("long comment"), - _ => { // line comment - loop { - let ch = self.read_char(); - if ch == '\n' || ch == '\0' { + match self.next_byte() { + None => (), + Some(b'[') => todo!("long comment"), + Some(_) => { // line comment + while let Some(byt) = self.next_byte() { + if byt == b'\n' { break; } } diff --git a/src/lib.rs b/src/lib.rs index f94c3e8..b455490 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,34 @@ +use std::env; +use std::fs::File; +use std::io::BufReader; + use pyo3::prelude::*; +pub mod lex; +pub mod parse; +pub mod bytecode; +pub mod vm; +pub mod value; +pub mod utils; + #[pyfunction] fn sum_as_string(a: usize, b: usize) -> PyResult { Ok((a + b).to_string()) } +#[pyfunction] +fn raw_input(prompt: String) -> PyResult<()> { + let file = File::open(&prompt).unwrap(); + + let proto = parse::load(BufReader::new(file)); + vm::ExeState::new().execute(&proto, &Vec::new()); + Ok(()) +} + #[pymodule] #[pyo3(name = "LibCore")] fn libcore(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; + m.add_function(wrap_pyfunction!(raw_input, m)?)?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index 7fc844c..8f17ca8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,13 @@ use std::env; use std::fs::File; +use std::io::BufReader; mod value; mod bytecode; mod lex; mod parse; mod vm; +mod utils; fn main() { let args: Vec = env::args().collect(); @@ -15,6 +17,6 @@ fn main() { } let file = File::open(&args[1]).unwrap(); - let proto = parse::ParseProto::load(file); - vm::ExeState::new().execute(&proto); + let proto = parse::load(BufReader::new(file)); + vm::ExeState::new().execute(&proto, &Vec::new()); } \ No newline at end of file diff --git a/src/parse.rs b/src/parse.rs index 7433c62..b939c1e 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,182 +1,1773 @@ -use std::fs::File; +use std::rc::Rc; +use std::io::Read; +use std::cmp::Ordering; use crate::lex::{Lex, Token}; use crate::bytecode::ByteCode; use crate::value::Value; +use crate::utils::ftoi; -// ANCHOR: proto +type FnBc2u8 = fn(u8, u8) -> ByteCode; +type FnBc3u8 = fn(u8, u8, u8) -> ByteCode; +type FnBcBool = fn(u8, u8, bool) -> ByteCode; + +// expression description, inner layer between source code and byte code +#[derive(Debug, PartialEq)] +enum ExpDesc { + // constants + Nil, + Boolean(bool), + Integer(i64), + Float(f64), + String(Vec), + + // variables + Local(usize), // including temprary variables on stack + Upvalue(usize), + + // table index + Index(usize, usize), + IndexField(usize, usize), + IndexInt(usize, u8), + IndexUpField(usize, usize), // covers global variables + + // function call + Function(usize), + Closure(usize), + Call(usize, usize), + VarArgs, + + // arithmetic operators + UnaryOp(FnBc2u8, usize), // (opcode, operand) + BinaryOp(FnBc3u8, usize, usize), // (opcode, left-operand, right-operand) + + // binaray logical operators: 'and', 'or' + Test(Box, Vec, Vec), // (condition, true-list, false-list) + + // relational operators, e.g. '==', '<=' + Compare(FnBcBool, usize, usize, Vec, Vec), +} + +// see discharge_const() +enum ConstStack { + Const(usize), + Stack(usize), +} + +// mark both goto and label #[derive(Debug)] -pub struct ParseProto { - pub constants: Vec::, - pub byte_codes: Vec::, +struct GotoLabel { + name: String, + icode: usize, + nvar: usize, +} - locals: Vec::, - lex: Lex, +// index of locals/upvalues in upper functions +#[derive(Debug)] +pub enum UpIndex { + Local(usize), + Upvalue(usize), } -// ANCHOR_END: proto -impl ParseProto { - pub fn load(input: File) -> ParseProto { - let mut proto = ParseProto { - constants: Vec::new(), - byte_codes: Vec::new(), - locals: Vec::new(), - lex: Lex::new(input), - }; +// core struct, generated in parse phase and executed in VM +#[derive(Debug, Default)] +pub struct FuncProto { + pub has_varargs: bool, + pub nparam: usize, + pub constants: Vec, + pub upindexes: Vec, + pub byte_codes: Vec, +} - proto.chunk(); +// level of inner functions, used for matching upvalue +#[derive(Debug, Default)] +struct Level { + locals: Vec<(String, bool)>, // (name, referred-as-upvalue) + upvalues: Vec<(String, UpIndex)>, +} - println!("constants: {:?}", &proto.constants); - println!("byte_codes:"); - for c in proto.byte_codes.iter() { - println!(" {:?}", c); - } +#[derive(Debug)] +struct ParseContext { + levels: Vec, + lex: Lex, +} + +#[derive(Debug)] +struct ParseProto<'a, R: Read> { + // return to VM to execute + fp: FuncProto, + + // internal stuff for parsing + sp: usize, + break_blocks: Vec>, + continue_blocks: Vec>, + gotos: Vec, + labels: Vec, + ctx: &'a mut ParseContext, +} - proto +impl<'a, R: Read> ParseProto<'a, R> { + // BNF: + // block ::= {stat} [retstat] + // stat ::= `;` | + // varlist `=` explist | + // functioncall | + // label | + // break | + // goto Name | + // do block end | + // while exp do block end | + // repeat block until exp | + // if exp then block {elseif exp then block} [else block] end | + // for Name `=` exp `,` exp [`,` exp] do block end | + // for namelist in explist do block end | + // function funcname funcbody | + // local function Name funcbody | + // local attnamelist [`=` explist] + fn block(&mut self) -> Token { + let nvar = self.local_num(); + let end_token = self.block_scope(); + self.local_expire(nvar); + end_token } - fn chunk(&mut self) { + // same with block() but without expiring internal local variables + fn block_scope(&mut self) -> Token { + let igoto = self.gotos.len(); + let ilabel = self.labels.len(); loop { - match self.lex.next() { - Token::Name(name) => { - if self.lex.peek() == &Token::Assign { - self.assignment(name); + // reset sp before each statement + self.sp = self.local_num(); + + match self.ctx.lex.next() { + Token::SemiColon => (), + t@Token::Name(_) | t@Token::ParL => { + // this is not standard! + if self.try_continue_stat(&t) { + continue; + } + + // functioncall and var-assignment both begin with + // `prefixexp` which begins with `Name` or `(`. + let desc = self.prefixexp(t); + if let ExpDesc::Call(ifunc, narg_plus) = desc { + // prefixexp() matches the whole functioncall statement. + let code = ByteCode::Call(ifunc as u8, narg_plus as u8, 0); + self.fp.byte_codes.push(code); + } else { + // prefixexp() matches only the first variable, so we + // continue the statement + self.assignment(desc); + } + } + Token::Local => + if self.ctx.lex.peek() == &Token::Function { + self.local_function() } else { - self.function_call(name); + self.local_variables() } + Token::Function => self.function_stat(), + Token::If => self.if_stat(), + Token::While => self.while_stat(), + Token::Repeat => self.repeat_stat(), + Token::For => self.for_stat(), + Token::Break => self.break_stat(), + Token::Do => self.do_stat(), + Token::DoubColon => self.label_stat(igoto), + Token::Goto => self.goto_stat(), + Token::Return => self.ret_stat(), + t => { + self.labels.truncate(ilabel); + break t; } - Token::Local => self.local(), - Token::Eos => break, - t => panic!("unexpected token: {t:?}"), } } } - // Name LiteralString - // Name ( exp ) - fn function_call(&mut self, name: String) { - let ifunc = self.locals.len(); - let iarg = ifunc + 1; + // BNF: + // local attnamelist [`=` explist] + // attnamelist ::= Name attrib {`,` Name attrib} + fn local_variables(&mut self) { + // variable names + let mut vars = vec![self.read_name()]; + while self.ctx.lex.peek() == &Token::Comma { + self.ctx.lex.next(); + vars.push(self.read_name()); + } + + if self.ctx.lex.peek() == &Token::Assign { + // explist + self.ctx.lex.next(); + self.explist_want(vars.len()); + } else { + // no exp, load nils + let code = ByteCode::LoadNil(self.sp as u8, vars.len() as u8); + self.fp.byte_codes.push(code); + } + + // append vars into self.locals after evaluating explist + for var in vars.into_iter() { + self.local_new(var); + } + } - // function, variable - let code = self.load_var(ifunc, name); - self.byte_codes.push(code); + // BNF: + // local function Name funcbody + fn local_function(&mut self) { + self.ctx.lex.next(); + let name = self.read_name(); + println!("== function: {name}"); - // argument, (exp) or "string" - match self.lex.next() { - Token::ParL => { // '(' - self.load_exp(iarg); + // create `name` local variable before parsing funcbody(), + // so the function can be called in body as recursion. + self.local_new(name); - if self.lex.next() != Token::ParR { // ')' - panic!("expected `)`"); + let f = self.funcbody(false); + self.discharge(self.sp, f); + } + + // BNF: + // function funcname funcbody + // funcname = Name {`.` Name} [`:` Name] + fn function_stat(&mut self) { + let name = self.read_name(); + let mut desc = self.simple_name(name); + + let with_self = loop { + match self.ctx.lex.peek() { + Token::Dot => { // `.` Name + self.ctx.lex.next(); + let name = self.read_name(); + let t = self.discharge_any(desc); + desc = ExpDesc::IndexField(t, self.add_const(name)); + } + Token::Colon => { // `:` Name + self.ctx.lex.next(); + let name = self.read_name(); + let t = self.discharge_any(desc); + desc = ExpDesc::IndexField(t, self.add_const(name)); + + break true; + } + _ => { + break false; } } - Token::String(s) => { - let code = self.load_const(iarg, Value::String(s)); - self.byte_codes.push(code); + }; + + let body = self.funcbody(with_self); + self.assign_var(desc, body); + } + + // BNF: + // funcbody ::= `(` [parlist] `)` block end + // parlist ::= namelist [`,` `...`] | `...` + // namelist ::= Name {`,` Name} + fn funcbody(&mut self, with_self: bool) -> ExpDesc { + // parameter list + let mut has_varargs = false; + let mut params = Vec::new(); + if with_self { + params.push(String::from("self")); + } + self.ctx.lex.expect(Token::ParL); + loop { + match self.ctx.lex.next() { + Token::Name(name) => { + params.push(name); + match self.ctx.lex.next() { + Token::Comma => (), + Token::ParR => break, + t => panic!("invalid parameter {t:?}"), + } + } + Token::Dots => { + has_varargs = true; + self.ctx.lex.expect(Token::ParR); + break; + }, + Token::ParR => break, + t => panic!("invalid parameter {t:?}"), } - _ => panic!("expected string"), } - self.byte_codes.push(ByteCode::Call(ifunc as u8, 1)); + // body + let proto = chunk(self.ctx, has_varargs, params, Token::End); + + let no_upvalue = proto.upindexes.is_empty(); + let iconst = self.add_const(Value::LuaFunction(Rc::new(proto))); + if no_upvalue { + ExpDesc::Function(iconst) + } else { + ExpDesc::Closure(iconst) + } } - // local Name = exp - fn local(&mut self) { - let var = if let Token::Name(var) = self.lex.next() { - var + // BNF: + // varlist = explist + // varlist ::= var {`,` var} + fn assignment(&mut self, first_var: ExpDesc) { + // read varlist into @vars + let mut vars = vec![first_var]; + loop { + match self.ctx.lex.next() { + Token::Comma => { // more variable + let token = self.ctx.lex.next(); + vars.push(self.prefixexp(token)); + } + Token::Assign => break, + t => panic!("invalid assign {t:?}"), + } + } + + let sp0 = self.sp; + let (mut nexp, last_exp) = self.explist(); + + // assignment last variable + match (nexp + 1).cmp(&vars.len()) { + Ordering::Equal => { + // assign last variable directly to avoid potential discharging + let last_var = vars.pop().unwrap(); + self.assign_var(last_var, last_exp); + } + Ordering::Less => { + // expand last expressions + self.discharge_expand_want(last_exp, vars.len() - nexp); + nexp = vars.len(); + } + Ordering::Greater => { + // drop extra exps + nexp = vars.len(); + } + } + + // assign previous variables from tmp registers, in reverse order + while let Some(var) = vars.pop() { + nexp -= 1; + self.assign_from_stack(var, sp0 + nexp); + } + } + + // BNF: + // if exp then block {elseif exp then block} [else block] end + fn if_stat(&mut self) { + let mut jmp_ends = Vec::new(); + + // == if exp then block + let mut end_token = self.do_if_block(&mut jmp_ends); + + // == {elseif exp then block} + while end_token == Token::Elseif { + end_token = self.do_if_block(&mut jmp_ends); + } + + // == [else block] + if end_token == Token::Else { + end_token = self.block(); + } + + assert_eq!(end_token, Token::End); + + let iend = self.fp.byte_codes.len() - 1; + for i in jmp_ends.into_iter() { + self.fp.byte_codes[i] = ByteCode::Jump((iend - i) as i16); + } + } + + fn do_if_block(&mut self, jmp_ends: &mut Vec) -> Token { + let condition = self.exp(); + let false_list = self.test_or_jump(condition); + + self.ctx.lex.expect(Token::Then); + + let end_token = self.block(); + + // If there are following 'elseif' or 'else' blocks, + // jump to the very end of this whole if-statment at the + // end of this block. + // Make a fake byte-code to hold the place, and fix it + // at the end of whole if-statment. + if matches!(end_token, Token::Elseif | Token::Else) { + self.fp.byte_codes.push(ByteCode::Jump(0)); + jmp_ends.push(self.fp.byte_codes.len() - 1); + } + + self.fix_test_list(false_list); + + end_token + } + + // BNF: + // while exp do block end + fn while_stat(&mut self) { + let istart = self.fp.byte_codes.len(); + + let condition = self.exp(); + let false_list = self.test_or_jump(condition); + + self.ctx.lex.expect(Token::Do); + + self.push_loop_block(); + + assert_eq!(self.block(), Token::End); + + // jump back + let iend = self.fp.byte_codes.len(); + self.fp.byte_codes.push(ByteCode::Jump(-((iend - istart) as i16) - 1)); + + self.pop_loop_block(istart); + + self.fix_test_list(false_list); + } + + // BNF: + // repeat block until exp + fn repeat_stat(&mut self) { + let istart = self.fp.byte_codes.len(); + + self.push_loop_block(); + + let nvar = self.local_num(); + + assert_eq!(self.block_scope(), Token::Until); + let iend = self.fp.byte_codes.len(); + + let condition = self.exp(); + let false_list = self.test_or_jump(condition); + self.fix_test_list_to(false_list, istart); + + self.pop_loop_block(iend); + + // expire internal local variables AFTER reading condition exp + // and pop_loop_block() + self.local_expire(nvar); + } + + // * numerical: for Name `=` ... + // * generic: for Name {, Name} in ... + fn for_stat(&mut self) { + let name = self.read_name(); + if self.ctx.lex.peek() == &Token::Assign { + self.numerical_for(name); } else { - panic!("expected variable"); + self.generic_for(name); + } + } + + // BNF: + // for Name `=` exp `,` exp [`,` exp] do block end + fn numerical_for(&mut self, name: String) { + self.ctx.lex.next(); // skip `=` + + // 2 or 3 exps + let (nexp, last_exp) = self.explist(); + self.discharge(self.sp, last_exp); + + match nexp + 1 { + 2 => self.discharge(self.sp, ExpDesc::Integer(1)), + 3 => (), + _ => panic!("invalid numerical for exp"), + } + + // create 3 local variables: the first is iterator, + // and the other two to keep stack positions. + self.local_new(name); + self.local_new(String::from("")); + self.local_new(String::from("")); + + self.ctx.lex.expect(Token::Do); + + // ByteCode::ForPrepare, without argument + self.fp.byte_codes.push(ByteCode::ForPrepare(0, 0)); + let iprepare = self.fp.byte_codes.len() - 1; + let iname = self.sp - 3; + + self.push_loop_block(); + + // parse block! + assert_eq!(self.block(), Token::End); + + // expire 3 local variables above, before ByteCode::ForLoop + self.local_expire(self.local_num() - 3); + + // ByteCode::ForLoop, and fix ByteCode::ForPrepare above + let d = self.fp.byte_codes.len() - iprepare; + self.fp.byte_codes.push(ByteCode::ForLoop(iname as u8, d as u16)); + self.fp.byte_codes[iprepare] = ByteCode::ForPrepare(iname as u8, d as u16); + + self.pop_loop_block(self.fp.byte_codes.len() - 1); + } + + // BNF: + // stat ::= for namelist in explist do block end + // namelist ::= Name {`,` Name} + fn generic_for(&mut self, name: String) { + // namelist + let mut vars = vec![name]; + loop { + match self.ctx.lex.next() { + Token::Comma => continue, + Token::In => break, + Token::Name(name) => vars.push(name), + _ => panic!("invalid generic_for namelist"), + } + } + + // explist + let iter = self.sp; + self.explist_want(3); + + let nvar = vars.len(); + self.local_new(String::from("")); // iterator function + self.local_new(String::from("")); // immutable state + self.local_new(String::from("")); // control variable + for var in vars.into_iter() { + self.local_new(var); + } + + self.ctx.lex.expect(Token::Do); + + // jump to ByteCode::ForCallLoop at end of block + self.fp.byte_codes.push(ByteCode::Jump(0)); + let ijump = self.fp.byte_codes.len() - 1; + + self.push_loop_block(); + + // parse block! + assert_eq!(self.block(), Token::End); + + // expire local variables above, before ByteCode::Jump + self.local_expire(self.local_num() - 3 - nvar); + + // ByteCode::ForCallLoop + // call the iter function and check the control variable + let d = self.fp.byte_codes.len() - ijump; + self.fp.byte_codes[ijump] = ByteCode::Jump(d as i16 - 1); + if let Ok(d) = u8::try_from(d) { + self.fp.byte_codes.push(ByteCode::ForCallLoop(iter as u8, nvar as u8, d as u8)); + } else { + self.fp.byte_codes.push(ByteCode::ForCallLoop(iter as u8, nvar as u8, 0)); + self.fp.byte_codes.push(ByteCode::Jump(-(d as i16) - 1)); + } + + self.pop_loop_block(self.fp.byte_codes.len() - 1); + } + + fn break_stat(&mut self) { + let Some(breaks) = self.break_blocks.last_mut() else { + panic!("break outside loop"); }; + self.fp.byte_codes.push(ByteCode::Jump(0)); + breaks.push(self.fp.byte_codes.len() - 1); + } - if self.lex.next() != Token::Assign { - panic!("expected `=`"); + fn try_continue_stat(&mut self, name: &Token) -> bool { + let Token::Name(name) = name else { return false; }; + if name.as_str() != "continue" { + return false; + } + if !matches!(self.ctx.lex.peek(), Token::End | Token::Elseif | Token::Else) { + return false; } - self.load_exp(self.locals.len()); + let nvar = self.local_num(); + let Some(continues) = self.continue_blocks.last_mut() else { + panic!("continue outside loop"); + }; + self.fp.byte_codes.push(ByteCode::Jump(0)); + continues.push((self.fp.byte_codes.len() - 1, nvar)); + true + } - // add to locals after load_exp() - self.locals.push(var); + // before entering loop block + fn push_loop_block(&mut self) { + self.break_blocks.push(Vec::new()); + self.continue_blocks.push(Vec::new()); } + // after leaving loop block, fix `break` and `continue` Jumps + fn pop_loop_block(&mut self, icontinue: usize) { + // breaks + let iend = self.fp.byte_codes.len() - 1; + for i in self.break_blocks.pop().unwrap().into_iter() { + self.fp.byte_codes[i] = ByteCode::Jump((iend - i) as i16); + } + + // continues + let end_nvar = self.local_num(); + for (i, i_nvar) in self.continue_blocks.pop().unwrap().into_iter() { + if i_nvar < end_nvar { + panic!("continue jump into local scope"); + } + self.fp.byte_codes[i] = ByteCode::Jump((icontinue as isize - i as isize) as i16 - 1); + } + } + + // BNF: + // do block end + fn do_stat(&mut self) { + assert_eq!(self.block(), Token::End); + } + + // BNF: + // label ::= `::` Name `::` + fn label_stat(&mut self, igoto: usize) { + let name = self.read_name(); + self.ctx.lex.expect(Token::DoubColon); + + // check if this label is at the end of block. + // ignore void statments: `;` and label. + let is_last = loop { + match self.ctx.lex.peek() { + Token::SemiColon => { + self.ctx.lex.next(); + } + Token::DoubColon => { + self.ctx.lex.next(); + self.label_stat(igoto); + } + t => break is_block_end(t), + } + }; + + // check duplicate + if self.labels.iter().any(|l|l.name == name) { + panic!("duplicate label {name}"); + } + + let icode = self.fp.byte_codes.len(); + let nvar = self.local_num(); + + // match previous gotos + let mut no_dsts = Vec::new(); + for goto in self.gotos.drain(igoto..) { + if goto.name == name { + if !is_last && goto.nvar < nvar { + panic!("goto jump into scope {}", goto.name); + } + let dist = icode - goto.icode; + self.fp.byte_codes[goto.icode] = ByteCode::Jump(dist as i16 - 1); + } else { + // no matched label + no_dsts.push(goto); + } + } + self.gotos.append(&mut no_dsts); + + // save the label for following gotos + self.labels.push(GotoLabel { name, icode, nvar }); + } + + // BNF: + // goto Name + fn goto_stat(&mut self) { + let name = self.read_name(); + + // match previous label + if let Some(label) = self.labels.iter().rev().find(|l|l.name == name) { + // find label + let dist = self.fp.byte_codes.len() - label.icode; + self.local_check_close(label.nvar); + self.fp.byte_codes.push(ByteCode::Jump(-(dist as i16) - 1)); + + } else { + // not find label, push a fake byte code and save the goto + self.fp.byte_codes.push(ByteCode::Jump(0)); + + self.gotos.push(GotoLabel { + name, + icode: self.fp.byte_codes.len() - 1, + nvar: self.local_num(), + }); + } + } + + // BNF: + // retstat ::= return [explist] [‘;’] + fn ret_stat(&mut self) { + let code = match self.ctx.lex.peek() { + Token::SemiColon => { + self.ctx.lex.next(); + ByteCode::Return0 + } + t if is_block_end(t) => { + ByteCode::Return0 + } + _ => { // return values + let iret = self.sp; + let (nexp, last_exp) = self.explist(); + + // check optional ';' + if self.ctx.lex.peek() == &Token::SemiColon { + self.ctx.lex.next(); + } + // check block end + if !is_block_end(self.ctx.lex.peek()) { + panic!("'end' expected"); + } + + if let (0, &ExpDesc::Local(i)) = (nexp, &last_exp) { + // only 1 return value, so NOT need discharging all values to + // stack top for continuity + ByteCode::Return(i as u8, 1) - fn assignment(&mut self, var: String) { - self.lex.next(); // `=` + } else if let (0, &ExpDesc::Call(func, narg_plus)) = (nexp, &last_exp) { + // tail call + ByteCode::TailCall(func as u8, narg_plus as u8) - if let Some(i) = self.get_local(&var) { - // local variable - self.load_exp(i); + } else if self.discharge_try_expand(last_exp, 0) { + // return variable values + ByteCode::Return(iret as u8, 0) + + } else { + // return fixed values + ByteCode::Return(iret as u8, nexp as u8 + 1) + } + } + }; + self.fp.byte_codes.push(code); + } + + // process assignment: var = value + fn assign_var(&mut self, var: ExpDesc, value: ExpDesc) { + if let ExpDesc::Local(i) = var { + // self.sp will be set to i+1 in self.discharge(), which is + // NOT expected, but it's ok because self.sp will not be used + // before next statement. + self.discharge(i, value); } else { - // global variable - let dst = self.add_const(Value::String(var)) as u8; - - let code = match self.lex.next() { - // from const values - Token::Nil => ByteCode::SetGlobalConst(dst, self.add_const(Value::Nil) as u8), - Token::True => ByteCode::SetGlobalConst(dst, self.add_const(Value::Boolean(true)) as u8), - Token::False => ByteCode::SetGlobalConst(dst, self.add_const(Value::Boolean(false)) as u8), - Token::Integer(i) => ByteCode::SetGlobalConst(dst, self.add_const(Value::Integer(i)) as u8), - Token::Float(f) => ByteCode::SetGlobalConst(dst, self.add_const(Value::Float(f)) as u8), - Token::String(s) => ByteCode::SetGlobalConst(dst, self.add_const(Value::String(s)) as u8), - - // from variable - Token::Name(var) => - if let Some(i) = self.get_local(&var) { - // local variable - ByteCode::SetGlobal(dst, i as u8) + match self.discharge_const(value) { + ConstStack::Const(i) => self.assign_from_const(var, i), + ConstStack::Stack(i) => self.assign_from_stack(var, i), + } + } + } + + fn assign_from_stack(&mut self, var: ExpDesc, value: usize) { + let code = match var { + ExpDesc::Local(i) => ByteCode::Move(i as u8, value as u8), + ExpDesc::Upvalue(i) => ByteCode::SetUpvalue(i as u8, value as u8), + ExpDesc::Index(t, key) => ByteCode::SetTable(t as u8, key as u8, value as u8), + ExpDesc::IndexField(t, key) => ByteCode::SetField(t as u8, key as u8, value as u8), + ExpDesc::IndexInt(t, key) => ByteCode::SetInt(t as u8, key, value as u8), + ExpDesc::IndexUpField(t, key) => ByteCode::SetUpField(t as u8, key as u8, value as u8), + _ => panic!("assign from stack"), + }; + self.fp.byte_codes.push(code); + } + + fn assign_from_const(&mut self, var: ExpDesc, value: usize) { + let code = match var { + ExpDesc::Upvalue(i) => ByteCode::SetUpvalueConst(i as u8, value as u8), + ExpDesc::Index(t, key) => ByteCode::SetTableConst(t as u8, key as u8, value as u8), + ExpDesc::IndexField(t, key) => ByteCode::SetFieldConst(t as u8, key as u8, value as u8), + ExpDesc::IndexInt(t, key) => ByteCode::SetIntConst(t as u8, key, value as u8), + ExpDesc::IndexUpField(t, key) => ByteCode::SetUpFieldConst(t as u8, key as u8, value as u8), + _ => panic!("assign from const"), + }; + self.fp.byte_codes.push(code); + } + + // add the value to constants + fn add_const(&mut self, c: impl Into) -> usize { + let c = c.into(); + let constants = &mut self.fp.constants; + constants.iter().position(|v| v.same(&c)).unwrap_or_else(|| { + constants.push(c); + constants.len() - 1 + }) + } + + // explist ::= exp {`,` exp} + // + // Read expressions, discharge front ones, and keep last one. + // Return the number of front expressions and the last expression. + fn explist(&mut self) -> (usize, ExpDesc) { + let sp0 = self.sp; + let mut n = 0; + loop { + let desc = self.exp(); + if self.ctx.lex.peek() != &Token::Comma { + self.sp = sp0 + n; + return (n, desc); + } + self.ctx.lex.next(); + + self.discharge(sp0 + n, desc); + n += 1; + } + } + + fn explist_want(&mut self, want: usize) { + let (nexp, last_exp) = self.explist(); + match (nexp + 1).cmp(&want) { + Ordering::Equal => { + self.discharge(self.sp, last_exp); + } + Ordering::Less => { + // expand last expressions + self.discharge_expand_want(last_exp, want - nexp); + } + Ordering::Greater => { + // drop extra expressions + self.sp -= nexp - want; + } + } + } + + // BNF: + // exp ::= nil | false | true | Numeral | LiteralString | `...` | functiondef | + // prefixexp | tableconstructor | exp binop exp | unop exp + // + // Remove left recursion: + // + // exp ::= (nil | false | true | Numeral | LiteralString | `...` | functiondef | + // prefixexp | tableconstructor | unop exp) A' + // where: + // A' ::= binop exp A' | Epsilon + fn exp(&mut self) -> ExpDesc { + self.exp_limit(0) + } + fn exp_limit(&mut self, limit: i32) -> ExpDesc { + let ahead = self.ctx.lex.next(); + self.do_exp(limit, ahead) + } + fn exp_with_ahead(&mut self, ahead: Token) -> ExpDesc { + self.do_exp(0, ahead) + } + fn do_exp(&mut self, limit: i32, ahead: Token) -> ExpDesc { + // beta + let mut desc = match ahead { + Token::Nil => ExpDesc::Nil, + Token::True => ExpDesc::Boolean(true), + Token::False => ExpDesc::Boolean(false), + Token::Integer(i) => ExpDesc::Integer(i), + Token::Float(f) => ExpDesc::Float(f), + Token::String(s) => ExpDesc::String(s), + + Token::Dots => { + if !self.fp.has_varargs { + panic!("no varargs"); + } + ExpDesc::VarArgs + } + Token::Function => self.funcbody(false), + Token::CurlyL => self.table_constructor(), + + Token::Sub => self.unop_neg(), + Token::Not => self.unop_not(), + Token::BitNot => self.unop_bitnot(), + Token::Len => self.unop_len(), + + t => self.prefixexp(t), + }; + + // A' = alpha A' + loop { + // Expand only if next operator has priority higher than 'limit'. + // Non-operator tokens' priority is -1(lowest) so they always break here. + let (left_pri, right_pri) = binop_pri(self.ctx.lex.peek()); + if left_pri <= limit { + return desc; + } + + let binop = self.ctx.lex.next(); + desc = self.preprocess_binop_left(desc, &binop); + let right_desc = self.exp_limit(right_pri); + desc = self.process_binop(binop, desc, right_desc); + } + } + + // used for unary operand + fn exp_unop(&mut self) -> ExpDesc { + self.exp_limit(12) // 12 is all unary operators' priority + } + + // BNF: + // prefixexp ::= var | functioncall | `(` exp `)` + // var ::= Name | prefixexp `[` exp `]` | prefixexp `.` Name + // functioncall ::= prefixexp args | prefixexp `:` Name args + // + // We need to remove left recursion amount these 3 rules. + // + // First unfold 'var' and 'functioncall' in 'prefixexp' to remove indirect recursion: + // + // prefixexp ::= Name | prefixexp `[` exp `]` | prefixexp `.` Name | prefixexp args | prefixexp `:` Name args | `(` exp `)` + // + // Then remove the direct left recursion following: + // A ::= A alpha | beta + // into + // A ::= beta A' + // A' ::= alpha A' | Epsilon + // + // so + // prefixexp ::= prefixexp (`[` exp `]` | `.` Name | args | `:` Name args) | Name | `(` exp `)` + // = prefixexp alpha | beta + // where + // alpha ::= `[` exp `]` | `.` Name | args | `:` Name args + // beta ::= Name | `(` exp `)` + // + // Finally we get: + // prefixexp ::= beta A' + // = (Name | `(` exp `)`) A' + // where: + // A' ::= alpha A' | Epsilon + // = (`[` exp `]` | `.` Name | args | `:` Name args) A' | Epsilon + fn prefixexp(&mut self, ahead: Token) -> ExpDesc { + let sp0 = self.sp; + + // beta + let mut desc = match ahead { + Token::Name(name) => self.simple_name(name), + Token::ParL => { // `(` exp `)` + let desc = self.exp(); + self.ctx.lex.expect(Token::ParR); + desc + } + t => panic!("invalid prefixexp {t:?}"), + }; + + // A' = alpha A' + loop { + match self.ctx.lex.peek() { + Token::SqurL => { // `[` exp `]` + self.ctx.lex.next(); + let key = self.exp(); + self.ctx.lex.expect(Token::SqurR); + + desc = match (desc, key) { + // special case: upvalue-table and string-key + (ExpDesc::Upvalue(itable), ExpDesc::String(key)) => { + ExpDesc::IndexUpField(itable, self.add_const(key)) + } + // normal case + (table, key) => { + let itable = self.discharge_if_need(sp0, table); + match key { + ExpDesc::String(key) => + ExpDesc::IndexField(itable, self.add_const(key)), + ExpDesc::Integer(i) if u8::try_from(i).is_ok() => + ExpDesc::IndexInt(itable, u8::try_from(i).unwrap()), + _ => + ExpDesc::Index(itable, self.discharge_any(key)), + } + } + }; + } + Token::Dot => { // .Name + self.ctx.lex.next(); + let name = self.read_name(); + let ikey = self.add_const(name); + + desc = if let ExpDesc::Upvalue(itable) = desc { + ExpDesc::IndexUpField(itable, ikey) } else { - // global variable - ByteCode::SetGlobalGlobal(dst, self.add_const(Value::String(var)) as u8) + let itable = self.discharge_if_need(sp0, desc); + ExpDesc::IndexField(itable, ikey) + }; + } + Token::Colon => { // :Name args + self.ctx.lex.next(); + let name = self.read_name(); + let ikey = self.add_const(name); + let itable = self.discharge_if_need(sp0, desc); + + // GetFieldSelf: + // stack[sp0] := itable[ikey] # load function + // stack[sp0+1] := itable # load table as first argument + self.fp.byte_codes.push( + ByteCode::GetFieldSelf(sp0 as u8, itable as u8, ikey as u8)); + + // discharge following arguments begin at sp0+2 + self.sp = sp0 + 2; + + desc = self.args(1); + } + Token::ParL | Token::CurlyL | Token::String(_) => { // args + self.discharge(sp0, desc); + desc = self.args(0); + } + _ => return desc, // Epsilon + } + } + } + + fn local_num(&self) -> usize { + self.ctx.levels.last().unwrap().locals.len() + } + + fn local_new(&mut self, name: String) { + self.ctx.levels.last_mut().unwrap().locals.push((name, false)); + } + + fn local_expire(&mut self, from: usize) { + // drop locals + let mut vars = self.ctx.levels.last_mut().unwrap().locals.drain(from..); + + // generate Close if any dropped local variable referred as upvalue + if vars.any(|v| v.1) { + self.fp.byte_codes.push(ByteCode::Close(from as u8)); + } + } + + // generate Close if any local variable in [from..] referred as upvalue + fn local_check_close(&mut self, from: usize) { + let mut vars = self.ctx.levels.last().unwrap().locals[from..].iter(); + if vars.any(|v| v.1) { + self.fp.byte_codes.push(ByteCode::Close(from as u8)); + } + } + + // match the name as local, upvalue, or global + fn simple_name(&mut self, name: String) -> ExpDesc { + let mut level_iter = self.ctx.levels.iter_mut().rev(); + + // search from locals and upvalues in current level + let level = level_iter.next().unwrap(); + if let Some(i) = level.locals.iter().rposition(|v| v.0 == name) { + // search reversely, so new variable covers old one with same name + return ExpDesc::Local(i); + } + if let Some(i) = level.upvalues.iter().position(|v| v.0 == name) { + return ExpDesc::Upvalue(i); + } + + // search in upper levels + for (depth, level) in level_iter.enumerate() { + if let Some(i) = level.locals.iter().rposition(|v| v.0 == name) { + level.locals[i].1 = true; // mark it referred as upvalue + return self.create_upvalue(name, UpIndex::Local(i), depth); + } + if let Some(i) = level.upvalues.iter().position(|v| v.0 == name) { + return self.create_upvalue(name, UpIndex::Upvalue(i), depth); + } + } + + // not matched as local or upvalue, so global variable, by _ENV[name] + let iname = self.add_const(name); + match self.simple_name("_ENV".into()) { + ExpDesc::Local(i) => ExpDesc::IndexField(i, iname), + ExpDesc::Upvalue(i) => ExpDesc::IndexUpField(i, iname), + _ => panic!("no here"), // because "_ENV" must exist! + } + } + + fn create_upvalue(&mut self, name: String, mut upidx: UpIndex, depth: usize) -> ExpDesc { + let levels = &mut self.ctx.levels; + let last = levels.len() - 1; + + // create upvalue in middle levels, if any + for Level { upvalues, .. } in levels[last-depth .. last].iter_mut() { + upvalues.push((name.clone(), upidx)); + upidx = UpIndex::Upvalue(upvalues.len() - 1); + } + + // create upvalue in current level + let upvalues = &mut levels[last].upvalues; + upvalues.push((name, upidx)); + ExpDesc::Upvalue(upvalues.len() - 1) + } + + // unop `-` + fn unop_neg(&mut self) -> ExpDesc { + match self.exp_unop() { + ExpDesc::Integer(i) => ExpDesc::Integer(-i), + ExpDesc::Float(f) => ExpDesc::Float(-f), + ExpDesc::Nil | ExpDesc::Boolean(_) | ExpDesc::String(_) => panic!("invalid - operator"), + desc => ExpDesc::UnaryOp(ByteCode::Neg, self.discharge_any(desc)) + } + } + + // unop `not` + fn unop_not(&mut self) -> ExpDesc { + match self.exp_unop() { + ExpDesc::Nil => ExpDesc::Boolean(true), + ExpDesc::Boolean(b) => ExpDesc::Boolean(!b), + ExpDesc::Integer(_) | ExpDesc::Float(_) | ExpDesc::String(_) => ExpDesc::Boolean(false), + desc => ExpDesc::UnaryOp(ByteCode::Not, self.discharge_any(desc)), + } + } + // unop `~` + fn unop_bitnot(&mut self) -> ExpDesc { + match self.exp_unop() { + ExpDesc::Integer(i) => ExpDesc::Integer(!i), + ExpDesc::Nil | ExpDesc::Boolean(_) | ExpDesc::Float(_) | ExpDesc::String(_) => panic!("invalid ~ operator"), + desc => ExpDesc::UnaryOp(ByteCode::BitNot, self.discharge_any(desc)), + } + } + // unop `#` + fn unop_len(&mut self) -> ExpDesc { + match self.exp_unop() { + ExpDesc::String(s) => ExpDesc::Integer(s.len() as i64), + ExpDesc::Nil | ExpDesc::Boolean(_) | ExpDesc::Integer(_) | ExpDesc::Float(_) => panic!("invalid ~ operator"), + desc => ExpDesc::UnaryOp(ByteCode::Len, self.discharge_any(desc)), + } + } + + fn preprocess_binop_left(&mut self, left: ExpDesc, binop: &Token) -> ExpDesc { + // Generate TestOrJump/TestAndJump before reading right operand, + // because of short-circuit evaluation. + if binop == &Token::And { + ExpDesc::Test(Box::new(ExpDesc::Nil), Vec::new(), self.test_or_jump(left)) + } else if binop == &Token::Or { + ExpDesc::Test(Box::new(ExpDesc::Nil), self.test_and_jump(left), Vec::new()) + + // Discharge left operand before reading right operand, which may + // affect the evaluation of left operand. e.g. `t.k + f(t) * 1`, + // the `f(t)` may change `t.k`, so we must evaluate `t.k` before + // calling `f(t)`. */ + // But we do not discharge constants, because they will not be + // affected by right operand. Besides we try to fold constants + // in process_binop() later. + } else if matches!(left, ExpDesc::Integer(_) | ExpDesc::Float(_) | ExpDesc::String(_)) { + left + } else { + ExpDesc::Local(self.discharge_any(left)) + } + } + + fn process_binop(&mut self, binop: Token, left: ExpDesc, right: ExpDesc) -> ExpDesc { + if let Some(r) = fold_const(&binop, &left, &right) { + return r; + } + + match binop { + Token::Add => self.do_binop(left, right, ByteCode::Add, ByteCode::AddInt, ByteCode::AddConst), + Token::Sub => self.do_binop(left, right, ByteCode::Sub, ByteCode::SubInt, ByteCode::SubConst), + Token::Mul => self.do_binop(left, right, ByteCode::Mul, ByteCode::MulInt, ByteCode::MulConst), + Token::Mod => self.do_binop(left, right, ByteCode::Mod, ByteCode::ModInt, ByteCode::ModConst), + Token::Idiv => self.do_binop(left, right, ByteCode::Idiv, ByteCode::IdivInt, ByteCode::IdivConst), + Token::Div => self.do_binop(left, right, ByteCode::Div, ByteCode::DivInt, ByteCode::DivConst), + Token::Pow => self.do_binop(left, right, ByteCode::Pow, ByteCode::PowInt, ByteCode::PowConst), + Token::BitAnd => self.do_binop(left, right, ByteCode::BitAnd, ByteCode::BitAndInt, ByteCode::BitAndConst), + Token::BitNot => self.do_binop(left, right, ByteCode::BitXor, ByteCode::BitXorInt, ByteCode::BitXorConst), + Token::BitOr => self.do_binop(left, right, ByteCode::BitOr, ByteCode::BitOrInt, ByteCode::BitOrConst), + Token::ShiftL => self.do_binop(left, right, ByteCode::ShiftL, ByteCode::ShiftLInt, ByteCode::ShiftLConst), + Token::ShiftR => self.do_binop(left, right, ByteCode::ShiftR, ByteCode::ShiftRInt, ByteCode::ShiftRConst), + + Token::Equal => self.do_compare(left, right, ByteCode::Equal, ByteCode::EqualInt, ByteCode::EqualConst), + Token::NotEq => self.do_compare(left, right, ByteCode::NotEq, ByteCode::NotEqInt, ByteCode::NotEqConst), + Token::LesEq => self.do_compare(left, right, ByteCode::LesEq, ByteCode::LesEqInt, ByteCode::LesEqConst), + Token::GreEq => self.do_compare(left, right, ByteCode::GreEq, ByteCode::GreEqInt, ByteCode::GreEqConst), + Token::Less => self.do_compare(left, right, ByteCode::Less, ByteCode::LessInt, ByteCode::LessConst), + Token::Greater => self.do_compare(left, right, ByteCode::Greater, ByteCode::GreaterInt, ByteCode::GreaterConst), + + Token::Concat => { + // TODO support multiple operants + let left = self.discharge_any(left); + let right = self.discharge_any(right); + ExpDesc::BinaryOp(ByteCode::Concat, left, right) + } + + Token::And | Token::Or => { + // left operand has been made into ExpDesc::Test in preprocess_binop_left() + let ExpDesc::Test(_, mut left_true_list, mut left_false_list) = left else { + panic!("impossible"); + }; + match right { + ExpDesc::Compare(op, l, r, mut right_true_list, mut right_false_list) => { + left_true_list.append(&mut right_true_list); + left_false_list.append(&mut right_false_list); + ExpDesc::Compare(op, l, r, left_true_list, left_false_list) } + ExpDesc::Test(condition, mut right_true_list, mut right_false_list) => { + left_true_list.append(&mut right_true_list); + left_false_list.append(&mut right_false_list); + ExpDesc::Test(condition, left_true_list, left_false_list) + } + _ => ExpDesc::Test(Box::new(right), left_true_list, left_false_list), + } + } + _ => panic!("impossible"), + } + } + + fn do_binop(&mut self, mut left: ExpDesc, mut right: ExpDesc, + opr: FnBc3u8, opi: FnBc3u8, opk: FnBc3u8) -> ExpDesc { + + if opr == ByteCode::Add || opr == ByteCode::Mul { // commutative + if matches!(left, ExpDesc::Integer(_) | ExpDesc::Float(_)) { + // swap the left-const-operand to right, in order to use opi/opk + (left, right) = (right, left); + } + } + + let left = self.discharge_any(left); + + let (op, right) = match right { + ExpDesc::Integer(i) => + if let Ok(i) = u8::try_from(i) { + (opi, i as usize) + } else { + (opk, self.add_const(i)) + } + ExpDesc::Float(f) => (opk, self.add_const(f)), + _ => (opr, self.discharge_any(right)), + }; + + ExpDesc::BinaryOp(op, left, right) + } + + fn do_compare(&mut self, mut left: ExpDesc, mut right: ExpDesc, + opr: FnBcBool, opi: FnBcBool, opk: FnBcBool) -> ExpDesc { + + if opr == ByteCode::Equal || opr == ByteCode::NotEq { // commutative + if matches!(left, ExpDesc::Integer(_) | ExpDesc::Float(_)) { + // swap the left-const-operand to right, in order to use opi/opk + (left, right) = (right, left); + } + } + + let left = self.discharge_any(left); + + let (op, right) = match right { + ExpDesc::Integer(i) => + if let Ok(i) = u8::try_from(i) { + (opi, i as usize) + } else { + (opk, self.add_const(i)) + } + ExpDesc::Float(f) => (opk, self.add_const(f)), + ExpDesc::String(s) => (opk, self.add_const(s)), + _ => (opr, self.discharge_any(right)), + }; + + ExpDesc::Compare(op, left, right, Vec::new(), Vec::new()) + } + + // Generate a TestOrJump: test @condition or jump to somewhere unknown. + // Link the new code to previous false-list if any. + // Close true-list if any. + // Return false-list to be fixed later in fix_test_list() + fn test_or_jump(&mut self, condition: ExpDesc) -> Vec { + let (code, true_list, mut false_list) = match condition { + ExpDesc::Boolean(true) | ExpDesc::Integer(_) | ExpDesc::Float(_) | ExpDesc::String(_) => { + // always true, no need to test or jump, e.g. `while true do ... end` + return Vec::new(); + } + ExpDesc::Compare(op, left, right, true_list, false_list) => { + self.fp.byte_codes.push(op(left as u8, right as u8, true)); + (ByteCode::Jump(0), Some(true_list), false_list) + } + ExpDesc::Test(condition, true_list, false_list) => { + let icondition = self.discharge_any(*condition); + (ByteCode::TestOrJump(icondition as u8, 0), Some(true_list), false_list) + } + _ => { + let icondition = self.discharge_any(condition); + (ByteCode::TestOrJump(icondition as u8, 0), None, Vec::new()) + } + }; + + self.fp.byte_codes.push(code); + + false_list.push(self.fp.byte_codes.len() - 1); + + if let Some(true_list) = true_list { + // close true_list to jump here, after TestOrJump + self.fix_test_list(true_list); + } + + false_list + } + + // see test_or_jump() + fn test_and_jump(&mut self, condition: ExpDesc) -> Vec { + let (code, mut true_list, false_list) = match condition { + ExpDesc::Boolean(false) | ExpDesc::Nil => { + // always false, no need to test or jump, but I don't know any useful case + return Vec::new(); + } + ExpDesc::Compare(op, left, right, true_list, false_list) => { + self.fp.byte_codes.push(op(left as u8, right as u8, false)); + (ByteCode::Jump(0), true_list, Some(false_list)) + } + ExpDesc::Test(condition, true_list, false_list) => { + let icondition = self.discharge_any(*condition); + (ByteCode::TestAndJump(icondition as u8, 0), true_list, Some(false_list)) + } + _ => { + let icondition = self.discharge_any(condition); + (ByteCode::TestAndJump(icondition as u8, 0), Vec::new(), None) + } + }; + + self.fp.byte_codes.push(code); + + true_list.push(self.fp.byte_codes.len() - 1); + + if let Some(false_list) = false_list { + // close false_list to jump here, after TestAndJump + self.fix_test_list(false_list); + } + + true_list + } - _ => panic!("invalid argument"), + // fix TestAndJump/TestOrJump list to jump to current place + fn fix_test_list(&mut self, list: Vec) { + let here = self.fp.byte_codes.len(); + self.fix_test_list_to(list, here); + } + + // fix TestAndJump/TestOrJump list to jump to $to + fn fix_test_list_to(&mut self, list: Vec, to: usize) { + for i in list.into_iter() { + let jmp = (to as isize - i as isize - 1) as i16; + let code = match self.fp.byte_codes[i] { + ByteCode::Jump(0) => ByteCode::Jump(jmp), + ByteCode::TestOrJump(icondition, 0) => ByteCode::TestOrJump(icondition, jmp), + ByteCode::TestAndJump(icondition, 0) => ByteCode::TestAndJump(icondition, jmp), + _ => panic!("invalid Test"), }; - self.byte_codes.push(code); + self.fp.byte_codes[i] = code; } } - fn add_const(&mut self, c: Value) -> usize { - let constants = &mut self.constants; - constants.iter().position(|v| v == &c) - .unwrap_or_else(|| { - constants.push(c); - constants.len() - 1 - }) + // fix TestAndJump/TestOrJump list to TestAndSetJump/TestOrSetJump + fn fix_test_set_list(&mut self, list: Vec, dst: usize) { + let here = self.fp.byte_codes.len(); + let dst = dst as u8; + for i in list.into_iter() { + let jmp = here - i - 1; // should not be negative + let code = match self.fp.byte_codes[i] { + ByteCode::Jump(0) => ByteCode::Jump(jmp as i16), + ByteCode::TestOrJump(icondition, 0) => + if icondition == dst { + ByteCode::TestOrJump(icondition, jmp as i16) + } else { + ByteCode::TestOrSetJump(dst as u8, icondition, jmp as u8) + } + ByteCode::TestAndJump(icondition, 0) => + if icondition == dst { + ByteCode::TestAndJump(icondition, jmp as i16) + } else { + ByteCode::TestAndSetJump(dst as u8, icondition, jmp as u8) + } + _ => panic!("invalid Test"), + }; + self.fp.byte_codes[i] = code; + } } - fn load_const(&mut self, dst: usize, c: Value) -> ByteCode { - ByteCode::LoadConst(dst as u8, self.add_const(c) as u16) + // args ::= `(` [explist] `)` | tableconstructor | LiteralString + fn args(&mut self, implicit_argn: usize) -> ExpDesc { + let ifunc = self.sp - 1 - implicit_argn; + let narg = match self.ctx.lex.next() { + Token::ParL => { + if self.ctx.lex.peek() != &Token::ParR { + let (nexp, last_exp) = self.explist(); + self.ctx.lex.expect(Token::ParR); + if self.discharge_try_expand(last_exp, 0) { + None // variable arguments + } else { + Some(nexp + 1) + } + } else { + self.ctx.lex.next(); + Some(0) + } + } + Token::CurlyL => { + self.table_constructor(); + Some(1) + } + Token::String(s) => { + self.discharge(ifunc+1, ExpDesc::String(s)); + Some(1) + } + t => panic!("invalid args {t:?}"), + }; + + // n+1: for fixed #n arguments + // 0: for variable arguments + let narg_plus = if let Some(n) = narg { n + implicit_argn + 1 } else { 0 }; + + ExpDesc::Call(ifunc, narg_plus) } - fn load_var(&mut self, dst: usize, name: String) -> ByteCode { - if let Some(i) = self.get_local(&name) { - // local variable - ByteCode::Move(dst as u8, i as u8) + // discharge @desc into the top of stack, if need + fn discharge_any(&mut self, desc: ExpDesc) -> usize { + let dst = if let &ExpDesc::Call(ifunc, _) = &desc { + ifunc } else { - // global variable - let ic = self.add_const(Value::String(name)); - ByteCode::GetGlobal(dst as u8, ic as u8) - } + self.sp + }; + self.discharge_if_need(dst, desc) } - fn get_local(&self, name: &str) -> Option { - self.locals.iter().rposition(|v| v == name) + // discharge @desc into @dst, if need + fn discharge_if_need(&mut self, dst: usize, desc: ExpDesc) -> usize { + if let ExpDesc::Local(i) = desc { + i // no need + } else { + self.discharge(dst, desc); + dst + } } - fn load_exp(&mut self, dst: usize) { - let code = match self.lex.next() { - Token::Nil => ByteCode::LoadNil(dst as u8), - Token::True => ByteCode::LoadBool(dst as u8, true), - Token::False => ByteCode::LoadBool(dst as u8, false), - Token::Integer(i) => - if let Ok(ii) = i16::try_from(i) { - ByteCode::LoadInt(dst as u8, ii) + // discharge @desc into @dst, and update self.sp=dst+1 + fn discharge(&mut self, dst: usize, desc: ExpDesc) { + let code = match desc { + ExpDesc::Nil => ByteCode::LoadNil(dst as u8, 1), + ExpDesc::Boolean(b) => ByteCode::LoadBool(dst as u8, b), + ExpDesc::Integer(i) => + if let Ok(i) = i16::try_from(i) { + ByteCode::LoadInt(dst as u8, i) + } else { + ByteCode::LoadConst(dst as u8, self.add_const(i) as u16) + } + ExpDesc::Float(f) => ByteCode::LoadConst(dst as u8, self.add_const(f) as u16), + ExpDesc::String(s) => ByteCode::LoadConst(dst as u8, self.add_const(s) as u16), + ExpDesc::Local(src) => + if dst != src { + ByteCode::Move(dst as u8, src as u8) } else { - self.load_const(dst, Value::Integer(i)) + return; } - Token::Float(f) => self.load_const(dst, Value::Float(f)), - Token::String(s) => self.load_const(dst, Value::String(s)), - Token::Name(var) => self.load_var(dst, var), - _ => panic!("invalid argument"), + ExpDesc::Upvalue(src) => ByteCode::GetUpvalue(dst as u8, src as u8), + ExpDesc::Index(itable, ikey) => ByteCode::GetTable(dst as u8, itable as u8, ikey as u8), + ExpDesc::IndexField(itable, ikey) => ByteCode::GetField(dst as u8, itable as u8, ikey as u8), + ExpDesc::IndexInt(itable, ikey) => ByteCode::GetInt(dst as u8, itable as u8, ikey), + ExpDesc::IndexUpField(itable, ikey) => ByteCode::GetUpField(dst as u8, itable as u8, ikey as u8), + ExpDesc::VarArgs => ByteCode::VarArgs(dst as u8, 1), + ExpDesc::Function(f) => ByteCode::LoadConst(dst as u8, f as u16), + ExpDesc::Closure(f) => ByteCode::Closure(dst as u8, f as u16), + ExpDesc::Call(ifunc, narg_plus) => ByteCode::CallSet(dst as u8, ifunc as u8, narg_plus as u8), + ExpDesc::UnaryOp(op, i) => op(dst as u8, i as u8), + ExpDesc::BinaryOp(op, left, right) => op(dst as u8, left as u8, right as u8), + ExpDesc::Test(condition, true_list, false_list) => { + // fix TestSet list after discharging + self.discharge(dst, *condition); + self.fix_test_set_list(true_list, dst); + self.fix_test_set_list(false_list, dst); + return; + } + ExpDesc::Compare(op, left, right, true_list, false_list) => { + self.fp.byte_codes.push(op(left as u8, right as u8, false)); + self.fp.byte_codes.push(ByteCode::Jump(1)); + + // terminate false-list to SetFalseSkip + self.fix_test_list(false_list); + self.fp.byte_codes.push(ByteCode::SetFalseSkip(dst as u8)); + // terminate true-list to LoadBool(true) + self.fix_test_list(true_list); + ByteCode::LoadBool(dst as u8, true) + } }; - self.byte_codes.push(code); + self.fp.byte_codes.push(code); + self.sp = dst + 1; + } + + // for constant types, add @desc to constants; + // otherwise, discharge @desc into stack + fn discharge_const(&mut self, desc: ExpDesc) -> ConstStack { + match desc { + // add const + ExpDesc::Nil => ConstStack::Const(self.add_const(())), + ExpDesc::Boolean(b) => ConstStack::Const(self.add_const(b)), + ExpDesc::Integer(i) => ConstStack::Const(self.add_const(i)), + ExpDesc::Float(f) => ConstStack::Const(self.add_const(f)), + ExpDesc::String(s) => ConstStack::Const(self.add_const(s)), + ExpDesc::Function(f) => ConstStack::Const(f), + + // discharge to stack + _ => ConstStack::Stack(self.discharge_any(desc)), + } + } + + fn discharge_expand_want(&mut self, desc: ExpDesc, want: usize) { + debug_assert!(want > 1); + if !self.discharge_try_expand(desc, want) { + let code = ByteCode::LoadNil(self.sp as u8, want as u8 - 1); + self.fp.byte_codes.push(code); + } + } + + // try to expand the @desc to #want values. + // want==0 means expand as many as possible. + fn discharge_try_expand(&mut self, desc: ExpDesc, want: usize) -> bool { + match desc { + ExpDesc::Call(ifunc, narg_plus) => { + let code = ByteCode::Call(ifunc as u8, narg_plus as u8, want as u8); + self.fp.byte_codes.push(code); + true + } + ExpDesc::VarArgs => { + let code = ByteCode::VarArgs(self.sp as u8, want as u8); + self.fp.byte_codes.push(code); + true + } + _ => { + self.discharge(self.sp, desc); + false + } + } + } + + fn table_constructor(&mut self) -> ExpDesc { + let table = self.sp; + self.sp += 1; + + let inew = self.fp.byte_codes.len(); + self.fp.byte_codes.push(ByteCode::NewTable(table as u8, 0, 0)); + + enum TableEntry { + Map((FnBc3u8, FnBc3u8, usize)), + Array(ExpDesc), + } + + // record the last array entry and do not discharge it immediately, + // because it may be expanded as varargs or function call. + let mut last_array_entry = None; + + let mut narray: usize = 0; + let mut nmap: usize = 0; + loop { + let sp0 = self.sp; + + // parse entry of map or array? + let entry = match self.ctx.lex.peek() { + Token::CurlyR => { // `}` + self.ctx.lex.next(); + break; + } + Token::SqurL => { // `[` exp `]` `=` exp + self.ctx.lex.next(); + + let key = self.exp(); // key + self.ctx.lex.expect(Token::SqurR); // `]` + self.ctx.lex.expect(Token::Assign); // `=` + + TableEntry::Map(match key { + ExpDesc::Local(i) => + (ByteCode::SetTable, ByteCode::SetTableConst, i), + ExpDesc::String(s) => + (ByteCode::SetField, ByteCode::SetFieldConst, self.add_const(s)), + ExpDesc::Integer(i) if u8::try_from(i).is_ok() => + (ByteCode::SetInt, ByteCode::SetIntConst, i as usize), + ExpDesc::Nil => + panic!("nil can not be table key"), + ExpDesc::Float(f) if f.is_nan() => + panic!("NaN can not be table key"), + _ => (ByteCode::SetTable, ByteCode::SetTableConst, self.discharge_any(key)), + }) + } + Token::Name(_) => { + let name = self.read_name(); + if self.ctx.lex.peek() == &Token::Assign { // Name `=` exp + self.ctx.lex.next(); + TableEntry::Map((ByteCode::SetField, ByteCode::SetFieldConst, self.add_const(name))) + } else { // Name + TableEntry::Array(self.exp_with_ahead(Token::Name(name))) + } + } + _ => { // exp + TableEntry::Array(self.exp()) + } + }; + + // insert the entry into table + match entry { + TableEntry::Map((op, opk, key)) => { + let value = self.exp(); + let code = match self.discharge_const(value) { + ConstStack::Const(i) => opk(table as u8, key as u8, i as u8), + ConstStack::Stack(i) => op(table as u8, key as u8, i as u8), + }; + self.fp.byte_codes.push(code); + + nmap += 1; + self.sp = sp0; + } + TableEntry::Array(desc) => { + if let Some(last) = last_array_entry.replace(desc) { + self.discharge(sp0, last); + + narray += 1; + if narray % 50 == 0 { // reset the array members every 50 + self.fp.byte_codes.push(ByteCode::SetList(table as u8, 50)); + self.sp = table + 1; + } + } + } + } + + // any more entry? + match self.ctx.lex.next() { + Token::SemiColon | Token::Comma => (), // yes + Token::CurlyR => break, // no + t => panic!("invalid table {t:?}"), + } + } + + if let Some(last) = last_array_entry { + let num = if self.discharge_try_expand(last, 0) { + // do not update @narray + 0 // 0 is special, means all following values in stack + } else { + narray += 1; + (self.sp - (table + 1)) as u8 + }; + self.fp.byte_codes.push(ByteCode::SetList(table as u8, num)); + } + + // reset narray and nmap + self.fp.byte_codes[inew] = ByteCode::NewTable(table as u8, + u8::try_from(narray).unwrap_or(255), + u8::try_from(nmap).unwrap_or(255)); + + self.sp = table + 1; + ExpDesc::Local(table) } + + fn read_name(&mut self) -> String { + if let Token::Name(name) = self.ctx.lex.next() { + name + } else { + panic!("expect name"); + } + } +} + +pub fn load(input: impl Read) -> FuncProto { + let mut ctx = ParseContext { + lex: Lex::new(input), + levels: Default::default(), + }; + chunk(&mut ctx, false, vec!["_ENV".into()], Token::Eos) // XXX has_varargs->true +} + +fn chunk(ctx: &mut ParseContext, has_varargs: bool, params: Vec, end_token: Token) -> FuncProto { + // prepare + let fp = FuncProto { + has_varargs: has_varargs, + nparam: params.len(), + ..Default::default() + }; + + ctx.levels.push(Level { + locals: params.into_iter().map(|p|(p, false)).collect(), + upvalues: Vec::new(), + }); + + let mut proto = ParseProto { + sp: 0, + break_blocks: Vec::new(), + continue_blocks: Vec::new(), + gotos: Vec::new(), + labels: Vec::new(), + + fp, + ctx, + }; + + // parse! + // use `block_scope()` because local variables will be dropped + // after function, and upvalues will be closed in `Return` + // byte code. + assert_eq!(proto.block_scope(), end_token); + + if let Some(goto) = proto.gotos.first() { + panic!("goto {} no destination", &goto.name); + } + + // clear + let ParseProto { mut fp, ctx, ..} = proto; + + let level = ctx.levels.pop().unwrap(); + fp.upindexes = level.upvalues.into_iter().map(|u| u.1).collect(); + + fp.byte_codes.push(ByteCode::Return0); + + println!("constants: {:?}", &fp.constants); + println!("upindexes: {:?}", &fp.upindexes); + println!("byte_codes:"); + for (i,c) in fp.byte_codes.iter().enumerate() { + println!(" {i}\t{c:?}"); + } + + fp +} + +// priorities of binops +fn binop_pri(binop: &Token) -> (i32, i32) { + match binop { + Token::Pow => (14, 13), // right associative + Token::Mul | Token::Mod | Token::Div | Token::Idiv => (11, 11), + Token::Add | Token::Sub => (10, 10), + Token::Concat => (9, 8), // right associative + Token::ShiftL | Token::ShiftR => (7, 7), + Token::BitAnd => (6, 6), + Token::BitNot => (5, 5), + Token::BitOr => (4, 4), + Token::Equal | Token::NotEq | Token::Less | + Token::Greater | Token::LesEq | Token::GreEq => (3, 3), + Token::And => (2, 2), + Token::Or => (1, 1), + _ => (-1, -1) + } +} + +fn is_block_end(t: &Token) -> bool { + matches!(t, Token::End | Token::Elseif | Token::Else | Token::Until | Token::Eos) +} + +fn fold_const(binop: &Token, left: &ExpDesc, right: &ExpDesc) -> Option { + match binop { + Token::Add => do_fold_const(left, right, |a,b|a+b, |a,b|a+b), + Token::Sub => do_fold_const(left, right, |a,b|a-b, |a,b|a-b), + Token::Mul => do_fold_const(left, right, |a,b|a*b, |a,b|a*b), + Token::Mod => do_fold_const(left, right, |a,b|a%b, |a,b|a%b), + Token::Idiv => do_fold_const(left, right, |a,b|a/b, |a,b|a/b), + + Token::Div => do_fold_const_float(left, right, |a,b|a/b), + Token::Pow => do_fold_const_float(left, right, |a,b|a.powf(b)), + + Token::BitAnd => do_fold_const_int(left, right, |a,b|a&b), + Token::BitNot => do_fold_const_int(left, right, |a,b|a^b), + Token::BitOr => do_fold_const_int(left, right, |a,b|a|b), + Token::ShiftL => do_fold_const_int(left, right, |a,b|a< do_fold_const_int(left, right, |a,b|a>>b), + + Token::Concat => { + if let (ExpDesc::String(s1), ExpDesc::String(s2)) = (left, right) { + Some(ExpDesc::String([s1.as_slice(), s2.as_slice()].concat())) + } else { + None + } + } + + _ => None, + } +} + +fn do_fold_const(left: &ExpDesc, right: &ExpDesc, arith_i: fn(i64,i64)->i64, arith_f: fn(f64,f64)->f64) -> Option { + match (left, right) { + (&ExpDesc::Integer(i1), &ExpDesc::Integer(i2)) => Some(ExpDesc::Integer(arith_i(i1, i2))), + (&ExpDesc::Float(f1), &ExpDesc::Float(f2)) => Some(ExpDesc::Float(arith_f(f1, f2))), + (&ExpDesc::Float(f1), &ExpDesc::Integer(i2)) => Some(ExpDesc::Float(arith_f(f1, i2 as f64))), + (&ExpDesc::Integer(i1), &ExpDesc::Float(f2)) => Some(ExpDesc::Float(arith_f(i1 as f64, f2))), + (_, _) => None, + } +} + +fn do_fold_const_int(left: &ExpDesc, right: &ExpDesc, arith_i: fn(i64,i64)->i64) -> Option { + let (i1, i2) = match (left, right) { + (&ExpDesc::Integer(i1), &ExpDesc::Integer(i2)) => (i1, i2), + (&ExpDesc::Float(f1), &ExpDesc::Float(f2)) => (ftoi(f1).unwrap(), ftoi(f2).unwrap()), + (&ExpDesc::Float(f1), &ExpDesc::Integer(i2)) => (ftoi(f1).unwrap(), i2), + (&ExpDesc::Integer(i1), &ExpDesc::Float(f2)) => (i1, ftoi(f2).unwrap()), + (_, _) => return None, + }; + Some(ExpDesc::Integer(arith_i(i1, i2))) +} + +fn do_fold_const_float(left: &ExpDesc, right: &ExpDesc, arith_f: fn(f64,f64)->f64) -> Option { + let (f1, f2) = match (left, right) { + (&ExpDesc::Integer(i1), &ExpDesc::Integer(i2)) => (i1 as f64, i2 as f64), + (&ExpDesc::Float(f1), &ExpDesc::Float(f2)) => (f1, f2), + (&ExpDesc::Float(f1), &ExpDesc::Integer(i2)) => (f1, i2 as f64), + (&ExpDesc::Integer(i1), &ExpDesc::Float(f2)) => (i1 as f64, f2), + (_, _) => return None, + }; + Some(ExpDesc::Float(arith_f(f1, f2))) } \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..7300086 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,22 @@ +use std::cmp::Ordering; +use crate::value::Value; + +pub fn ftoi(f: f64) -> Option { + let i = f as i64; + if i as f64 != f { + None + } else { + Some(i) + } +} + +pub fn set_vec(vec: &mut Vec, i: usize, value: Value) { + match i.cmp(&vec.len()) { + Ordering::Less => vec[i] = value, + Ordering::Equal => vec.push(value), + Ordering::Greater => { + vec.resize(i, Value::Nil); + vec.push(value); + } + } +} \ No newline at end of file diff --git a/src/value.rs b/src/value.rs index e612690..c03076d 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,15 +1,94 @@ use std::fmt; -use crate::vm::ExeState; +use std::mem; +use std::rc::Rc; +use std::cell::RefCell; +use std::hash::{Hash, Hasher}; +use std::collections::HashMap; +use crate::parse::FuncProto; +use crate::vm::{ExeState, LuaClosure}; +use crate::utils::{ftoi, set_vec}; + +const SHORT_STR_MAX: usize = 14; // sizeof(Value) - 1(tag) - 1(len) +const MID_STR_MAX: usize = 48 - 1; -// ANCHOR: value #[derive(Clone)] pub enum Value { Nil, Boolean(bool), Integer(i64), Float(f64), - String(String), - Function(fn (&mut ExeState) -> i32), + ShortStr(u8, [u8; SHORT_STR_MAX]), + MidStr(Rc<(u8, [u8; MID_STR_MAX])>), + LongStr(Rc>), + Table(Rc>), + RustFunction(fn (&mut ExeState) -> i32), + RustClosure(Rc i32>>>), + LuaFunction(Rc), + LuaClosure(Rc), +} + +pub struct Table { + pub array: Vec, + pub map: HashMap, +} + +impl Table { + pub fn new(narray: usize, nmap: usize) -> Self { + Table { + array: Vec::with_capacity(narray), + map: HashMap::with_capacity(nmap), + } + } + + pub fn index(&self, key: &Value) -> &Value { + match key { + // TODO float + &Value::Integer(i) => self.index_array(i), + _ => self.map.get(key).unwrap_or(&Value::Nil), + } + } + pub fn index_array(&self, i: i64) -> &Value { + self.array.get(i as usize - 1) + .unwrap_or_else(|| self.map.get(&Value::Integer(i as i64)) + .unwrap_or(&Value::Nil)) + } + + pub fn new_index(&mut self, key: Value, value: Value) { + match key { + // TODO float + Value::Integer(i) => self.new_index_array(i, value), + _ => { + self.map.insert(key, value); + } + } + } + pub fn new_index_array(&mut self, i: i64, value: Value) { + // this is not same with Lua's official implement + if i > 0 && (i < 4 || i < self.array.capacity() as i64 * 2) { + set_vec(&mut self.array, i as usize - 1, value); + } else { + self.map.insert(Value::Integer(i), value); + } + } +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + Value::Nil => write!(f, "nil"), + Value::Boolean(b) => write!(f, "{b}"), + Value::Integer(i) => write!(f, "{i}"), + Value::Float(n) => write!(f, "{n:?}"), + Value::ShortStr(len, buf) => write!(f, "{}", String::from_utf8_lossy(&buf[..*len as usize])), + Value::MidStr(s) => write!(f, "{}", String::from_utf8_lossy(&s.1[..s.0 as usize])), + Value::LongStr(s) => write!(f, "{}", String::from_utf8_lossy(s)), + Value::Table(t) => write!(f, "table: {:?}", Rc::as_ptr(t)), + Value::RustFunction(_) => write!(f, "function"), + Value::RustClosure(_) => write!(f, "function"), + Value::LuaFunction(l) => write!(f, "function: {:?}", Rc::as_ptr(l)), + Value::LuaClosure(l) => write!(f, "function: {:?}", Rc::as_ptr(l)), + } + } } impl fmt::Debug for Value { @@ -19,26 +98,263 @@ impl fmt::Debug for Value { Value::Boolean(b) => write!(f, "{b}"), Value::Integer(i) => write!(f, "{i}"), Value::Float(n) => write!(f, "{n:?}"), - Value::String(s) => write!(f, "{s}"), - Value::Function(_) => write!(f, "function"), + Value::ShortStr(len, buf) => write!(f, "'{}'", String::from_utf8_lossy(&buf[..*len as usize])), + Value::MidStr(s) => write!(f, "\"{}\"", String::from_utf8_lossy(&s.1[..s.0 as usize])), + Value::LongStr(s) => write!(f, "'''{}'''", String::from_utf8_lossy(s)), + Value::Table(t) => { + let t = t.borrow(); + write!(f, "table:{}:{}", t.array.len(), t.map.len()) + } + Value::RustFunction(_) => write!(f, "rust function"), + Value::RustClosure(_) => write!(f, "rust closure"), + Value::LuaFunction(_) => write!(f, "Lua function"), + Value::LuaClosure(_) => write!(f, "Lua closure"), } } } -// ANCHOR_END: value -// ANCHOR: peq impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { - // TODO compare Integer vs Float match (self, other) { (Value::Nil, Value::Nil) => true, - (Value::Boolean(b1), Value::Boolean(b2)) => *b1 == *b2, - (Value::Integer(i1), Value::Integer(i2)) => *i1 == *i2, - (Value::Float(f1), Value::Float(f2)) => *f1 == *f2, - (Value::String(s1), Value::String(s2)) => *s1 == *s2, - (Value::Function(f1), Value::Function(f2)) => std::ptr::eq(f1, f2), + (&Value::Boolean(b1), &Value::Boolean(b2)) => b1 == b2, + (&Value::Integer(i1), &Value::Integer(i2)) => i1 == i2, + (&Value::Integer(i), &Value::Float(f)) | + (&Value::Float(f), &Value::Integer(i)) => i as f64 == f && i == f as i64, + (&Value::Float(f1), &Value::Float(f2)) => f1 == f2, + (Value::ShortStr(len1, s1), Value::ShortStr(len2, s2)) => s1[..*len1 as usize] == s2[..*len2 as usize], + (Value::MidStr(s1), Value::MidStr(s2)) => s1.1[..s1.0 as usize] == s2.1[..s2.0 as usize], + (Value::LongStr(s1), Value::LongStr(s2)) => s1 == s2, + (Value::Table(t1), Value::Table(t2)) => Rc::as_ptr(t1) == Rc::as_ptr(t2), + (Value::RustFunction(f1), Value::RustFunction(f2)) => std::ptr::eq(f1, f2), + (Value::RustClosure(f1), Value::RustClosure(f2)) => Rc::as_ptr(f1) == Rc::as_ptr(f2), + (Value::LuaFunction(f1), Value::LuaFunction(f2)) => Rc::as_ptr(f1) == Rc::as_ptr(f2), + (Value::LuaClosure(f1), Value::LuaClosure(f2)) => Rc::as_ptr(f1) == Rc::as_ptr(f2), (_, _) => false, } } } -// ANCHOR_END: peq \ No newline at end of file + +impl Eq for Value {} + +impl PartialOrd for Value { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + // numbers + (Value::Integer(i1), Value::Integer(i2)) => Some(i1.cmp(i2)), + (Value::Integer(i), Value::Float(f)) => (*i as f64).partial_cmp(f), + (Value::Float(f), Value::Integer(i)) => f.partial_cmp(&(*i as f64)), + (Value::Float(f1), Value::Float(f2)) => f1.partial_cmp(f2), + + // strings + (Value::ShortStr(len1, s1), Value::ShortStr(len2, s2)) => Some(s1[..*len1 as usize].cmp(&s2[..*len2 as usize])), + (Value::MidStr(s1), Value::MidStr(s2)) => Some(s1.1[..s1.0 as usize].cmp(&s2.1[..s2.0 as usize])), + (Value::LongStr(s1), Value::LongStr(s2)) => Some(s1.cmp(s2)), + + // strings of different types + (Value::ShortStr(len1, s1), Value::MidStr(s2)) => Some(s1[..*len1 as usize].cmp(&s2.1[..s2.0 as usize])), + (Value::ShortStr(len1, s1), Value::LongStr(s2)) => Some(s1[..*len1 as usize].cmp(s2)), + (Value::MidStr(s1), Value::ShortStr(len2, s2)) => Some(s1.1[..s1.0 as usize].cmp(&s2[..*len2 as usize])), + (Value::MidStr(s1), Value::LongStr(s2)) => Some(s1.1[..s1.0 as usize].cmp(s2)), + (Value::LongStr(s1), Value::ShortStr(len2, s2)) => Some(s1.as_ref().as_slice().cmp(&s2[..*len2 as usize])), + (Value::LongStr(s1), Value::MidStr(s2)) => Some(s1.as_ref().as_slice().cmp(&s2.1[..s2.0 as usize])), + + (_, _) => None, + } + } +} + +impl Value { + pub fn same(&self, other: &Self) -> bool { + // eliminate Integer and Float with same number value + mem::discriminant(self) == mem::discriminant(other) && self == other + } + pub fn ty(&self) -> &'static str { + match self { + &Value::Nil => "nil", + &Value::Boolean(_) => "boolean", + &Value::Integer(_) => "number", + &Value::Float(_) => "number", + &Value::ShortStr(_, _) => "string", + &Value::MidStr(_) => "string", + &Value::LongStr(_) => "string", + &Value::Table(_) => "table", + &Value::RustFunction(_) => "function", + &Value::RustClosure(_) => "function", + &Value::LuaFunction(_) => "function", + &Value::LuaClosure(_) => "function", + } + } + + pub fn index(&self, key: &Value) -> Value { + match self { + Value::Table(t) => t.borrow().index(key).clone(), + _ => todo!("meta __index"), + } + } + pub fn index_array(&self, i: i64) -> Value { + match self { + Value::Table(t) => t.borrow().index_array(i).clone(), + _ => todo!("meta __index"), + } + } + + pub fn new_index(&self, key: Value, value: Value) { + match self { + Value::Table(t) => t.borrow_mut().new_index(key, value), + _ => todo!("meta __index"), + } + } + pub fn new_index_array(&self, i: i64, value: Value) { + match self { + Value::Table(t) => t.borrow_mut().new_index_array(i, value), + _ => todo!("meta __newindex"), + } + } + + pub fn concat(&self, v2: &Self) -> Self { + let s1: &[u8] = self.as_ref(); + + match v2 { + Value::Integer(_) => todo!("int"), + Value::Float(_) => todo!("float"), + _ => { + let s2: &[u8] = v2.as_ref(); + let l1 = s1.len(); + let l2 = s2.len(); + if l1 + l2 < MID_STR_MAX { + let mut buf = [0; MID_STR_MAX]; + buf[..l1].copy_from_slice(s1); + buf[l1..l1+l2].copy_from_slice(s2); + buf[..l1+l2].into() + } else { + [s1, s2].concat().into() + } + } + } + } +} + +impl Hash for Value { + fn hash(&self, state: &mut H) { + match self { + Value::Nil => (), + Value::Boolean(b) => b.hash(state), + Value::Integer(i) => i.hash(state), + &Value::Float(f) => + if let Some(i) = ftoi(f) { + i.hash(state) + } else { + (f.to_bits() as i64).hash(state) + } + Value::ShortStr(len, buf) => buf[..*len as usize].hash(state), + Value::MidStr(s) => s.1[..s.0 as usize].hash(state), + Value::LongStr(s) => s.hash(state), + Value::Table(t) => Rc::as_ptr(t).hash(state), + Value::RustFunction(f) => (*f as *const usize).hash(state), + Value::RustClosure(f) => Rc::as_ptr(f).hash(state), + Value::LuaFunction(f) => Rc::as_ptr(f).hash(state), + Value::LuaClosure(f) => Rc::as_ptr(f).hash(state), + } + } +} + +impl From<()> for Value { + fn from(_: ()) -> Self { + Value::Nil + } +} + +impl From for Value { + fn from(b: bool) -> Self { + Value::Boolean(b) + } +} + +impl From for Value { + fn from(n: f64) -> Self { + Value::Float(n) + } +} + +impl From for Value { + fn from(n: i64) -> Self { + Value::Integer(n) + } +} + +// convert &[u8], Vec, &str and String into Value +impl From<&[u8]> for Value { + fn from(v: &[u8]) -> Self { + vec_to_short_mid_str(v).unwrap_or_else(||Value::LongStr(Rc::new(v.to_vec()))) + } +} +impl From<&str> for Value { + fn from(s: &str) -> Self { + s.as_bytes().into() // &[u8] + } +} + +impl From> for Value { + fn from(v: Vec) -> Self { + vec_to_short_mid_str(&v).unwrap_or_else(||Value::LongStr(Rc::new(v))) + } +} +impl From for Value { + fn from(s: String) -> Self { + s.into_bytes().into() // Vec + } +} + +fn vec_to_short_mid_str(v: &[u8]) -> Option { + let len = v.len(); + if len <= SHORT_STR_MAX { + let mut buf = [0; SHORT_STR_MAX]; + buf[..len].copy_from_slice(v); + Some(Value::ShortStr(len as u8, buf)) + + } else if len <= MID_STR_MAX { + let mut buf = [0; MID_STR_MAX]; + buf[..len].copy_from_slice(v); + Some(Value::MidStr(Rc::new((len as u8, buf)))) + + } else { + None + } +} + +impl AsRef<[u8]> for Value { + fn as_ref(&self) -> &[u8] { + match self { + Value::ShortStr(len, buf) => &buf[..*len as usize], + Value::MidStr(s) => &s.1[..s.0 as usize], + Value::LongStr(s) => s, + _ => panic!("invalid string Value"), + } + } +} + +impl AsRef for Value { + fn as_ref(&self) -> &str { + std::str::from_utf8(self.as_ref()).unwrap() + } +} + +impl From<&Value> for bool { + fn from(v: &Value) -> Self { + !matches!(v, Value::Nil | Value::Boolean(false)) + } +} + +impl From<&Value> for i64 { + fn from(v: &Value) -> Self { + match v { + Value::Integer(i) => *i, + Value::Float(f) => *f as i64, + Value::ShortStr(_, _) => todo!("tonumber"), + Value::MidStr(_) => todo!("tonumber"), + Value::LongStr(_) => todo!("tonumber"), + _ => panic!("invalid string Value"), + } + } +} \ No newline at end of file diff --git a/src/vm.rs b/src/vm.rs index 31dd69f..786959f 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,91 +1,162 @@ +use std::rc::Rc; +use std::cell::RefCell; use std::cmp::Ordering; -use std::collections::HashMap; use crate::bytecode::ByteCode; -use crate::value::Value; -use crate::parse::ParseProto; +use crate::value::{Value, Table}; +use crate::parse::{FuncProto, UpIndex}; +use crate::utils::{ftoi, set_vec}; -// ANCHOR: print -// "print" function in Lua's std-lib. -// It supports only 1 argument and assumes the argument is at index:1 on stack. +// TODO move these library functions out fn lib_print(state: &mut ExeState) -> i32 { - println!("{:?}", state.stack[state.func_index + 1]); + for i in 1 ..= state.get_top() { + if i != 1 { + print!("\t"); + } + print!("{}", state.get::<&Value>(i).to_string()); + } + println!(""); 0 } -// ANCHOR_END: print +fn lib_type(state: &mut ExeState) -> i32 { + let ty = state.get::<&Value>(1).ty(); + state.push(ty); + 1 +} +fn test_new_counter(state: &mut ExeState) -> i32 { + let mut i = 0_i32; + let c = move |_: &mut ExeState| { + i += 1; + println!("counter: {i}"); + 0 + }; + state.push(Value::RustClosure(Rc::new(RefCell::new(Box::new(c))))); + 1 +} +fn ipairs_aux(state: &mut ExeState) -> i32 { + let table = match state.get::<&Value>(1) { + Value::Table(t) => t.borrow(), + _ => panic!("ipairs non-table"), + }; + + let i: i64 = state.get(2); + if i < 0 || i as usize >= table.array.len() { + return 0; + } + + let v = table.array[i as usize].clone(); + drop(table); + + state.push(i + 1); + state.push(v); + 2 +} + +fn ipairs(state: &mut ExeState) -> i32 { + state.push(Value::RustFunction(ipairs_aux)); + state.push(state.get::<&Value>(1).clone()); + state.push(0); + 3 +} + +#[derive(Debug, PartialEq)] +pub enum Upvalue { + Open(usize), + Closed(Value), +} + +impl Upvalue { + fn get<'a>(&'a self, stack: &'a Vec) -> &'a Value { + match self { + Upvalue::Open(i) => &stack[*i], + Upvalue::Closed(v) => &v, + } + } + fn set(&mut self, stack: &mut Vec, value: Value) { + match self { + Upvalue::Open(i) => stack[*i] = value, + Upvalue::Closed(v) => *v = value, + } + } +} + +// broker between local variables and open upvalues. +struct OpenBroker { + // @broker contains @ilocal, however, the duplicated @ilocal + // exists for quick comparation + ilocal: usize, + broker: Rc>, +} + +impl From for OpenBroker { + fn from(ilocal: usize) -> Self { + OpenBroker { + ilocal, + broker: Rc::new(RefCell::new(Upvalue::Open(ilocal))), + } + } +} + +pub struct LuaClosure { + proto: Rc, + upvalues: Vec>>, +} -// ANCHOR: state +// global execute state pub struct ExeState { - globals: HashMap, stack: Vec::, - func_index: usize, + base: usize, // stack base of current function } -// ANCHOR_END: state -// ANCHOR: new impl ExeState { pub fn new() -> Self { - let mut globals = HashMap::new(); - globals.insert(String::from("print"), Value::Function(lib_print)); + // TODO initilize the standard library outside + let mut env = Table::new(0, 0); + env.map.insert("print".into(), Value::RustFunction(lib_print)); + env.map.insert("type".into(), Value::RustFunction(lib_type)); + env.map.insert("ipairs".into(), Value::RustFunction(ipairs)); + env.map.insert("new_counter".into(), Value::RustFunction(test_new_counter)); ExeState { - globals, - stack: Vec::new(), - func_index: 0, + // 0: un-used entry function, 1: `_ENV` argument + stack: vec![Value::Nil, Value::Table(Rc::new(RefCell::new(env)))], + + // always an entry function, even not used + base: 1, } } -// ANCHOR_END: new - -// ANCHOR: execute - pub fn execute(&mut self, proto: &ParseProto) { - for code in proto.byte_codes.iter() { - match *code { - ByteCode::GetGlobal(dst, name) => { - let name = &proto.constants[name as usize]; - if let Value::String(key) = name { - let v = self.globals.get(key).unwrap_or(&Value::Nil).clone(); - self.set_stack(dst, v); - } else { - panic!("invalid global key: {name:?}"); - } - } - ByteCode::SetGlobal(name, src) => { - let name = proto.constants[name as usize].clone(); - if let Value::String(key) = name { - let value = self.stack[src as usize].clone(); - self.globals.insert(key, value); - } else { - panic!("invalid global key: {name:?}"); - } - } - ByteCode::SetGlobalConst(name, src) => { - let name = proto.constants[name as usize].clone(); - if let Value::String(key) = name { - let value = proto.constants[src as usize].clone(); - self.globals.insert(key, value); - } else { - panic!("invalid global key: {name:?}"); - } - } - ByteCode::SetGlobalGlobal(name, src) => { - let name = proto.constants[name as usize].clone(); - if let Value::String(key) = name { - let src = &proto.constants[src as usize]; - if let Value::String(src) = src { - let value = self.globals.get(src).unwrap_or(&Value::Nil).clone(); - self.globals.insert(key, value); - } else { - panic!("invalid global key: {src:?}"); - } - } else { - panic!("invalid global key: {name:?}"); - } - } + + pub fn execute(&mut self, proto: &FuncProto, upvalues: &Vec>>) -> usize { + + // open brokers between local variables and upvalues + let mut open_brokers: Vec = Vec::new(); + + // fill nil if #argument < #parameter + if self.stack.len() - self.base < proto.nparam { + self.fill_stack_nil(0, proto.nparam); + } + + // move varargs out from stack + let varargs = if proto.has_varargs { + self.stack.drain(self.base + proto.nparam ..).collect() + } else { + Vec::new() + }; + + let mut pc = 0; + loop { + println!(" [{pc}]\t{:?}", proto.byte_codes[pc]); + match proto.byte_codes[pc] { + // local variable ByteCode::LoadConst(dst, c) => { let v = proto.constants[c as usize].clone(); self.set_stack(dst, v); } - ByteCode::LoadNil(dst) => { - self.set_stack(dst, Value::Nil); + ByteCode::LoadNil(dst, n) => { + let begin = self.base + dst as usize; + if begin < self.stack.len() { + self.stack[begin..].fill(Value::Nil); + } + self.fill_stack_nil(dst, n as usize); } ByteCode::LoadBool(dst, b) => { self.set_stack(dst, Value::Boolean(b)); @@ -94,31 +165,843 @@ impl ExeState { self.set_stack(dst, Value::Integer(i as i64)); } ByteCode::Move(dst, src) => { - let v = self.stack[src as usize].clone(); + let v = self.get_stack(src).clone(); self.set_stack(dst, v); } - ByteCode::Call(func, _) => { - self.func_index = func as usize; - let func = &self.stack[self.func_index]; - if let Value::Function(f) = func { - f(self); + + // upvalues + ByteCode::GetUpvalue(dst, src) => { + let v = upvalues[src as usize].borrow().get(&self.stack).clone(); + self.set_stack(dst, v); + } + ByteCode::SetUpvalue(dst, src) => { + let v = self.get_stack(src).clone(); + upvalues[dst as usize].borrow_mut().set(&mut self.stack, v); + } + ByteCode::SetUpvalueConst(dst, src) => { + let v = proto.constants[src as usize].clone(); + upvalues[dst as usize].borrow_mut().set(&mut self.stack, v); + } + ByteCode::Close(ilocal) => { + let ilocal = self.base + ilocal as usize; + let from = open_brokers.binary_search_by_key(&ilocal, |b| b.ilocal) + .unwrap_or_else(|i| i); + self.close_brokers(open_brokers.drain(from..)); + } + + // table + ByteCode::NewTable(dst, narray, nmap) => { + let table = Table::new(narray as usize, nmap as usize); + self.set_stack(dst, Value::Table(Rc::new(RefCell::new(table)))); + } + ByteCode::SetTable(t, k, v) => { + let key = self.get_stack(k).clone(); + let value = self.get_stack(v).clone(); + self.get_stack(t).new_index(key, value); + } + ByteCode::SetField(t, k, v) => { + let key = proto.constants[k as usize].clone(); + let value = self.get_stack(v).clone(); + self.get_stack(t).new_index(key, value); + } + ByteCode::SetInt(t, i, v) => { + let value = self.get_stack(v).clone(); + self.get_stack(t).new_index_array(i as i64, value); + } + ByteCode::SetTableConst(t, k, v) => { + let key = self.get_stack(k).clone(); + let value = proto.constants[v as usize].clone(); + self.get_stack(t).new_index(key, value); + } + ByteCode::SetFieldConst(t, k, v) => { + let key = proto.constants[k as usize].clone(); + let value = proto.constants[v as usize].clone(); + self.get_stack(t).new_index(key, value); + } + ByteCode::SetIntConst(t, i, v) => { + let value = proto.constants[v as usize].clone(); + self.get_stack(t).new_index_array(i as i64, value); + } + ByteCode::SetList(table, n) => { + let ivalue = self.base + table as usize + 1; + let Value::Table(table) = self.get_stack(table).clone() else { + panic!("not table"); + }; + let end = if n == 0 { + // 0 is special, means all following values in stack + self.stack.len() + } else { + ivalue + n as usize + }; + let values = self.stack.drain(ivalue .. end); + table.borrow_mut().array.extend(values); + } + ByteCode::GetTable(dst, t, k) => { + let key = self.get_stack(k); + let value = self.get_stack(t).index(key); + self.set_stack(dst, value); + } + ByteCode::GetField(dst, t, k) => { + let key = &proto.constants[k as usize]; + let value = self.get_stack(t).index(key); + self.set_stack(dst, value); + } + ByteCode::GetInt(dst, t, k) => { + let value = self.get_stack(t).index_array(k as i64); + self.set_stack(dst, value); + } + ByteCode::GetFieldSelf(dst, t, k) => { + let table = self.get_stack(t).clone(); + let key = &proto.constants[k as usize]; + let value = table.index(key).clone(); + self.set_stack(dst, value); + self.set_stack(dst+1, table); + } + + // upvalue table + // + // The upvalue-table is get by: + // `upvalues[t as usize].borrow().get(&self.stack)` + // I do not know how to move this piece of code into a + // function because of the `borrow()`. + ByteCode::SetUpField(t, k, v) => { + let key = proto.constants[k as usize].clone(); + let value = self.get_stack(v).clone(); + upvalues[t as usize].borrow().get(&self.stack) + .new_index(key, value); + } + ByteCode::SetUpFieldConst(t, k, v) => { + let key = proto.constants[k as usize].clone(); + let value = proto.constants[v as usize].clone(); + upvalues[t as usize].borrow().get(&self.stack) + .new_index(key, value); + } + ByteCode::GetUpField(dst, t, k) => { + let key = &proto.constants[k as usize]; + let value = upvalues[t as usize].borrow().get(&self.stack) + .index(key).clone(); + self.set_stack(dst, value); + } + + // condition structures + ByteCode::Jump(jmp) => { + pc = (pc as isize + jmp as isize) as usize; + } + ByteCode::TestAndJump(icondition, jmp) => { + if self.get_stack(icondition).into() { // jump if true + pc = (pc as isize + jmp as isize) as usize; + } + } + ByteCode::TestOrJump(icondition, jmp) => { + if self.get_stack(icondition).into() {} else { // jump if false + pc = (pc as isize + jmp as isize) as usize; + } + } + ByteCode::TestAndSetJump(dst, icondition, jmp) => { + let condition = self.get_stack(icondition); + if condition.into() { // set and jump if true + self.set_stack(dst, condition.clone()); + pc += jmp as usize; + } + } + ByteCode::TestOrSetJump(dst, icondition, jmp) => { + let condition = self.get_stack(icondition); + if condition.into() {} else { // set and jump if false + self.set_stack(dst, condition.clone()); + pc += jmp as usize; + } + } + + // for-loop + ByteCode::ForPrepare(dst, jmp) => { + // clear into 2 cases: integer and float + // stack: i, limit, step + if let (&Value::Integer(mut i), &Value::Integer(step)) = + (self.get_stack(dst), self.get_stack(dst + 2)) { + // integer case + if step == 0 { + panic!("0 step in numerical for"); + } + let limit = match self.get_stack(dst + 1) { + &Value::Integer(limit) => limit, + &Value::Float(limit) => { + let limit = for_int_limit(limit, step>0, &mut i); + self.set_stack(dst+1, Value::Integer(limit)); + limit + } + // TODO convert string + _ => panic!("invalid limit type"), + }; + if !for_check(i, limit, step>0) { + pc += jmp as usize; + } } else { - panic!("invalid function: {func:?}"); + // float case + let i = self.make_float(dst); + let limit = self.make_float(dst+1); + let step = self.make_float(dst+2); + if step == 0.0 { + panic!("0 step in numerical for"); + } + if !for_check(i, limit, step>0.0) { + pc += jmp as usize; + } + } + } + ByteCode::ForLoop(dst, jmp) => { + // stack: i, limit, step + match (self.get_stack(dst + 1), self.get_stack(dst + 2)) { + (&Value::Integer(limit), &Value::Integer(step)) => { + let Value::Integer(i) = self.get_stack_mut(dst) else { + panic!("xxx"); + }; + *i += step; + if for_check(*i, limit, step>0) { + pc -= jmp as usize; + } + } + (&Value::Float(limit), &Value::Float(step)) => { + let Value::Float(i) = self.get_stack_mut(dst) else { + panic!("xxx"); + }; + *i += step; + if for_check(*i, limit, step>0.0) { + pc -= jmp as usize; + } + } + _ => panic!("xx"), } } + + ByteCode::ForCallLoop(iter, nvar, jmp) => { + // stack: + // - before call: + // iter-func, state, ctrl-var + // - after call: + // iter-func, state, ctrl-var, ..., return-values + // - update ctrl-var, and clear middle values + // iter-func, state, ctrl-var*, return-values + let nret = self.call_function(iter, 2+1); + let iret = self.stack.len() - nret; + + if nret > 0 && self.stack[iret] != Value::Nil { + // continue the loop + // duplicate the first return value as ctrl-var, + // so it could be changed during loop. + let first_ret = self.stack[iret].clone(); + self.set_stack(iter + 2, first_ret); + + // move return values to @iter+3 + self.stack.drain(self.base + iter as usize + 3 .. iret); + self.fill_stack_nil(iter + 3, nvar as usize); + + // jump back to loop + pc -= jmp as usize; + + } else if jmp == 0 { + // skip the following Jump + pc += 1; + } + } + + // define closure + ByteCode::Closure(dst, inner) => { + let Value::LuaFunction(inner_proto) = proto.constants[inner as usize].clone() else { + panic!("must be funcproto"); + }; + + // generate upvalues + let inner_upvalues = inner_proto.upindexes.iter().map(|up| match up { + &UpIndex::Upvalue(iup) => upvalues[iup].clone(), + &UpIndex::Local(ilocal) => { + let ilocal = self.base + ilocal; + let iob = open_brokers.binary_search_by_key(&ilocal, |b|b.ilocal) + .unwrap_or_else(|i| { + open_brokers.insert(i, OpenBroker::from(ilocal)); + i + }); + open_brokers[iob].broker.clone() + } + }).collect(); + + let c = LuaClosure { + upvalues: inner_upvalues, + proto: inner_proto, + }; + self.set_stack(dst, Value::LuaClosure(Rc::new(c))) + } + + // function call + ByteCode::Call(func, narg_plus, want_nret) => { + let nret = self.call_function(func, narg_plus); + + // move return values to @func + let iret = self.stack.len() - nret; + self.stack.drain(self.base+func as usize .. iret); + + // want_nret==0 means 1.want all return values or 2.want no + // return values, while we do not need handle in both cases; + // otherwise, means @want_nret return values are need, and + // we need to fill nil if necessary. + let want_nret = want_nret as usize; + if nret < want_nret { + self.fill_stack_nil(func, want_nret); + } + } + ByteCode::CallSet(dst, func, narg_plus) => { + let nret = self.call_function(func, narg_plus); + + // set first return value to @dst directly + if nret == 0 { + self.set_stack(dst, Value::Nil); + } else { + // use swap() to avoid clone() + let iret = self.stack.len() - nret; + self.stack.swap(self.base+dst as usize, iret); + } + self.stack.truncate(self.base + func as usize + 1); + } + + ByteCode::TailCall(func, narg_plus) => { + self.close_brokers(open_brokers); + + // clear current call-frame, and move new function entry and + // arguments (self.stack[@func ..]) into current call-frame + self.stack.drain(self.base-1 .. self.base+func as usize); + + return self.do_call_function(narg_plus); + } + + ByteCode::Return(iret, nret) => { + self.close_brokers(open_brokers); + + // if nret==0, return stack[iret .. ]; + // otherwise, return stack[iret .. iret+nret] and truncate + // the stack to make sure there is no more extra temprary + // values, so: + // - we can return @nret only (but no need @iret) to + // indicate the return values, so we get the same + // return type with RustFunction; + // - the following byte code, including Return(_,0), + // Call(_,_,0) or SetList(_,0), can get the + // #return-values by stack top. + let iret = self.base + iret as usize; + if nret == 0 { + return self.stack.len() - iret; + } else { + self.stack.truncate(iret + nret as usize); + return nret as usize; + } + } + ByteCode::Return0 => { + self.close_brokers(open_brokers); + return 0; + } + + ByteCode::VarArgs(dst, want) => { + // truncate the stack to make sure there is no more + // extra temprary values, so the following byte code, + // including Return(_,0), Call(_,_,0) or SetList(_,0), + // can get the #varargs by stack top. + self.stack.truncate(self.base + dst as usize); + + let len = varargs.len(); + let want = want as usize; + if want == 0 { // 0 means all + self.stack.extend_from_slice(&varargs); + } else if want > len { + self.stack.extend_from_slice(&varargs); + self.fill_stack_nil(dst, want); + } else { + self.stack.extend_from_slice(&varargs[..want]); + } + } + + // unops + ByteCode::Neg(dst, src) => { + let value = match &self.get_stack(src) { + Value::Integer(i) => Value::Integer(-i), + Value::Float(f) => Value::Float(-f), + _ => panic!("invalid -"), + }; + self.set_stack(dst, value); + } + ByteCode::Not(dst, src) => { + let value = match &self.get_stack(src) { + Value::Nil => Value::Boolean(true), + Value::Boolean(b) => Value::Boolean(!b), + _ => Value::Boolean(false), + }; + self.set_stack(dst, value); + } + ByteCode::BitNot(dst, src) => { + let value = match &self.get_stack(src) { + Value::Integer(i) => Value::Integer(!i), + _ => panic!("invalid ~"), + }; + self.set_stack(dst, value); + } + ByteCode::Len(dst, src) => { + let value = match &self.get_stack(src) { + Value::ShortStr(len, _) => Value::Integer(*len as i64), + Value::MidStr(s) => Value::Integer(s.0 as i64), + Value::LongStr(s) => Value::Integer(s.len() as i64), + Value::Table(t) => Value::Integer(t.borrow().array.len() as i64), + _ => panic!("invalid -"), + }; + self.set_stack(dst, value); + } + + // binops + ByteCode::Add(dst, a, b) => { + let r = exe_binop(&self.get_stack(a), &self.get_stack(b), |a,b|a+b, |a,b|a+b); + self.set_stack(dst, r); + } + ByteCode::AddConst(dst, a, b) => { + let r = exe_binop(&self.get_stack(a), &proto.constants[b as usize], |a,b|a+b, |a,b|a+b); + self.set_stack(dst, r); + } + ByteCode::AddInt(dst, a, i) => { + let r = exe_binop_int(&self.get_stack(a), i, |a,b|a+b, |a,b|a+b); + self.set_stack(dst, r); + } + ByteCode::Sub(dst, a, b) => { + let r = exe_binop(&self.get_stack(a), &self.get_stack(b), |a,b|a-b, |a,b|a-b); + self.set_stack(dst, r); + } + ByteCode::SubConst(dst, a, b) => { + let r = exe_binop(&self.get_stack(a), &proto.constants[b as usize], |a,b|a-b, |a,b|a-b); + self.set_stack(dst, r); + } + ByteCode::SubInt(dst, a, i) => { + let r = exe_binop_int(&self.get_stack(a), i, |a,b|a-b, |a,b|a-b); + self.set_stack(dst, r); + } + ByteCode::Mul(dst, a, b) => { + let r = exe_binop(&self.get_stack(a), &self.get_stack(b), |a,b|a*b, |a,b|a*b); + self.set_stack(dst, r); + } + ByteCode::MulConst(dst, a, b) => { + let r = exe_binop(&self.get_stack(a), &proto.constants[b as usize], |a,b|a*b, |a,b|a*b); + self.set_stack(dst, r); + } + ByteCode::MulInt(dst, a, i) => { + let r = exe_binop_int(&self.get_stack(a), i, |a,b|a*b, |a,b|a*b); + self.set_stack(dst, r); + } + ByteCode::Mod(dst, a, b) => { + let r = exe_binop(&self.get_stack(a), &self.get_stack(b), |a,b|a%b, |a,b|a%b); + self.set_stack(dst, r); + } + ByteCode::ModConst(dst, a, b) => { + let r = exe_binop(&self.get_stack(a), &proto.constants[b as usize], |a,b|a%b, |a,b|a%b); + self.set_stack(dst, r); + } + ByteCode::ModInt(dst, a, i) => { + let r = exe_binop_int(&self.get_stack(a), i, |a,b|a%b, |a,b|a%b); + self.set_stack(dst, r); + } + ByteCode::Idiv(dst, a, b) => { + let r = exe_binop(&self.get_stack(a), &self.get_stack(b), |a,b|a/b, |a,b|a/b); + self.set_stack(dst, r); + } + ByteCode::IdivConst(dst, a, b) => { + let r = exe_binop(&self.get_stack(a), &proto.constants[b as usize], |a,b|a/b, |a,b|a/b); + self.set_stack(dst, r); + } + ByteCode::IdivInt(dst, a, i) => { + let r = exe_binop_int(&self.get_stack(a), i, |a,b|a/b, |a,b|a/b); + self.set_stack(dst, r); + } + ByteCode::Div(dst, a, b) => { + let r = exe_binop_f(&self.get_stack(a), &self.get_stack(b), |a,b|a/b); + self.set_stack(dst, r); + } + ByteCode::DivConst(dst, a, b) => { + let r = exe_binop_f(&self.get_stack(a), &proto.constants[b as usize], |a,b|a/b); + self.set_stack(dst, r); + } + ByteCode::DivInt(dst, a, i) => { + let r = exe_binop_int_f(&self.get_stack(a), i, |a,b|a/b); + self.set_stack(dst, r); + } + ByteCode::Pow(dst, a, b) => { + let r = exe_binop_f(&self.get_stack(a), &self.get_stack(b), |a,b|a.powf(b)); + self.set_stack(dst, r); + } + ByteCode::PowConst(dst, a, b) => { + let r = exe_binop_f(&self.get_stack(a), &proto.constants[b as usize], |a,b|a.powf(b)); + self.set_stack(dst, r); + } + ByteCode::PowInt(dst, a, i) => { + let r = exe_binop_int_f(&self.get_stack(a), i, |a,b|a.powf(b)); + self.set_stack(dst, r); + } + ByteCode::BitAnd(dst, a, b) => { + let r = exe_binop_i(&self.get_stack(a), &self.get_stack(b), |a,b|a&b); + self.set_stack(dst, r); + } + ByteCode::BitAndConst(dst, a, b) => { + let r = exe_binop_i(&self.get_stack(a), &proto.constants[b as usize], |a,b|a&b); + self.set_stack(dst, r); + } + ByteCode::BitAndInt(dst, a, i) => { + let r = exe_binop_int_i(&self.get_stack(a), i, |a,b|a&b); + self.set_stack(dst, r); + } + ByteCode::BitOr(dst, a, b) => { + let r = exe_binop_i(&self.get_stack(a), &self.get_stack(b), |a,b|a|b); + self.set_stack(dst, r); + } + ByteCode::BitOrConst(dst, a, b) => { + let r = exe_binop_i(&self.get_stack(a), &proto.constants[b as usize], |a,b|a|b); + self.set_stack(dst, r); + } + ByteCode::BitOrInt(dst, a, i) => { + let r = exe_binop_int_i(&self.get_stack(a), i, |a,b|a|b); + self.set_stack(dst, r); + } + ByteCode::BitXor(dst, a, b) => { + let r = exe_binop_i(&self.get_stack(a), &self.get_stack(b), |a,b|a^b); + self.set_stack(dst, r); + } + ByteCode::BitXorConst(dst, a, b) => { + let r = exe_binop_i(&self.get_stack(a), &proto.constants[b as usize], |a,b|a^b); + self.set_stack(dst, r); + } + ByteCode::BitXorInt(dst, a, i) => { + let r = exe_binop_int_i(&self.get_stack(a), i, |a,b|a^b); + self.set_stack(dst, r); + } + ByteCode::ShiftL(dst, a, b) => { + let r = exe_binop_i(&self.get_stack(a), &self.get_stack(b), |a,b|a< { + let r = exe_binop_i(&self.get_stack(a), &proto.constants[b as usize], |a,b|a< { + let r = exe_binop_int_i(&self.get_stack(a), i, |a,b|a< { + let r = exe_binop_i(&self.get_stack(a), &self.get_stack(b), |a,b|a>>b); + self.set_stack(dst, r); + } + ByteCode::ShiftRConst(dst, a, b) => { + let r = exe_binop_i(&self.get_stack(a), &proto.constants[b as usize], |a,b|a>>b); + self.set_stack(dst, r); + } + ByteCode::ShiftRInt(dst, a, i) => { + let r = exe_binop_int_i(&self.get_stack(a), i, |a,b|a>>b); + self.set_stack(dst, r); + } + + ByteCode::Equal(a, b, r) => { + if (self.get_stack(a) == self.get_stack(b)) == r { + pc += 1; + } + } + ByteCode::EqualConst(a, b, r) => { + if (self.get_stack(a) == &proto.constants[b as usize]) == r { + pc += 1; + } + } + ByteCode::EqualInt(a, i, r) => { + if let &Value::Integer(ii) = self.get_stack(a) { + if (ii == i as i64) == r { + pc += 1; + } + } + } + ByteCode::NotEq(a, b, r) => { + if (self.get_stack(a) != self.get_stack(b)) == r { + pc += 1; + } + } + ByteCode::NotEqConst(a, b, r) => { + if (self.get_stack(a) != &proto.constants[b as usize]) == r { + pc += 1; + } + } + ByteCode::NotEqInt(a, i, r) => { + if let &Value::Integer(ii) = self.get_stack(a) { + if (ii != i as i64) == r { + pc += 1; + } + } + } + ByteCode::LesEq(a, b, r) => { + let cmp = self.get_stack(a).partial_cmp(self.get_stack(b)).unwrap(); + if !matches!(cmp, Ordering::Greater) == r { + pc += 1; + } + } + ByteCode::LesEqConst(a, b, r) => { + let cmp = self.get_stack(a).partial_cmp(&proto.constants[b as usize]).unwrap(); + if !matches!(cmp, Ordering::Greater) == r { + pc += 1; + } + } + ByteCode::LesEqInt(a, i, r) => { + let a = match self.get_stack(a) { + &Value::Integer(i) => i, + &Value::Float(f) => f as i64, + _ => panic!("invalid compare"), + }; + if (a <= i as i64) == r { + pc += 1; + } + } + ByteCode::GreEq(a, b, r) => { + let cmp = self.get_stack(a).partial_cmp(self.get_stack(b)).unwrap(); + if !matches!(cmp, Ordering::Less) == r { + pc += 1; + } + } + ByteCode::GreEqConst(a, b, r) => { + let cmp = self.get_stack(a).partial_cmp(&proto.constants[b as usize]).unwrap(); + if !matches!(cmp, Ordering::Less) == r { + pc += 1; + } + } + ByteCode::GreEqInt(a, i, r) => { + let a = match self.get_stack(a) { + &Value::Integer(i) => i, + &Value::Float(f) => f as i64, + _ => panic!("invalid compare"), + }; + if (a >= i as i64) == r { + pc += 1; + } + } + ByteCode::Less(a, b, r) => { + let cmp = self.get_stack(a).partial_cmp(self.get_stack(b)).unwrap(); + if matches!(cmp, Ordering::Less) == r { + pc += 1; + } + } + ByteCode::LessConst(a, b, r) => { + let cmp = self.get_stack(a).partial_cmp(&proto.constants[b as usize]).unwrap(); + if matches!(cmp, Ordering::Less) == r { + pc += 1; + } + } + ByteCode::LessInt(a, i, r) => { + let a = match self.get_stack(a) { + &Value::Integer(i) => i, + &Value::Float(f) => f as i64, + _ => panic!("invalid compare"), + }; + if (a < i as i64) == r { + pc += 1; + } + } + ByteCode::Greater(a, b, r) => { + let cmp = self.get_stack(a).partial_cmp(self.get_stack(b)).unwrap(); + if matches!(cmp, Ordering::Greater) == r { + pc += 1; + } + } + ByteCode::GreaterConst(a, b, r) => { + let cmp = self.get_stack(a).partial_cmp(&proto.constants[b as usize]).unwrap(); + if matches!(cmp, Ordering::Greater) == r { + pc += 1; + } + } + ByteCode::GreaterInt(a, i, r) => { + let a = match self.get_stack(a) { + &Value::Integer(i) => i, + &Value::Float(f) => f as i64, + _ => panic!("invalid compare"), + }; + if (a > i as i64) == r { + pc += 1; + } + } + + ByteCode::SetFalseSkip(dst) => { + self.set_stack(dst, Value::Boolean(false)); + pc += 1; + } + + ByteCode::Concat(dst, a, b) => { + let r = self.get_stack(a).concat(self.get_stack(b)); + self.set_stack(dst, r); + } } + + pc += 1; } } -// ANCHOR_END: execute -// ANCHOR: set_stack + fn get_stack(&self, dst: u8) -> &Value { + &self.stack[self.base + dst as usize] + } + fn get_stack_mut(&mut self, dst: u8) -> &mut Value { + &mut self.stack[self.base + dst as usize] + } fn set_stack(&mut self, dst: u8, v: Value) { - let dst = dst as usize; - match dst.cmp(&self.stack.len()) { - Ordering::Equal => self.stack.push(v), - Ordering::Less => self.stack[dst] = v, - Ordering::Greater => panic!("fail in set_stack"), + set_vec(&mut self.stack, self.base + dst as usize, v); + } + fn fill_stack_nil(&mut self, base: u8, to: usize) { + self.stack.resize(self.base + base as usize + to, Value::Nil); + } + + // call function + // return the number of return values which are at the stack end + fn call_function(&mut self, func: u8, narg_plus: u8) -> usize { + self.base += func as usize + 1; // get into new world + let nret = self.do_call_function(narg_plus); + self.base -= func as usize + 1; // come back + nret + } + + // Before calling, the function entry is at @self.base-1, and the + // arguments follows with: + // - narg_plus==0 means variable arguments, and all stack values + // following the function entry are arguments; + // - otherwise means (narg_plus-1) fixed arguments, and there may + // be temprary values following which need be truncated sometime. + // + // After calling, the return values lay at the top of stack. + // + // Return the number of return values. + fn do_call_function(&mut self, narg_plus: u8) -> usize { + // drop potential temprary stack usage, for get_top() + if narg_plus != 0 { + self.stack.truncate(self.base + narg_plus as usize - 1); + } + + match self.stack[self.base - 1].clone() { + Value::RustFunction(f) => f(self) as usize, + Value::RustClosure(c) => c.borrow_mut()(self) as usize, + Value::LuaFunction(f) => self.execute(&f, &Vec::new()), + Value::LuaClosure(c) => self.execute(&c.proto, &c.upvalues), + v => panic!("invalid function: {v:?}"), + } + } + + fn close_brokers(&self, open_brokers: impl IntoIterator) { + for OpenBroker { ilocal, broker } in open_brokers { + let openi = broker.replace(Upvalue::Closed(self.stack[ilocal].clone())); + debug_assert_eq!(openi, Upvalue::Open(ilocal)); + } + } + + fn make_float(&mut self, dst: u8) -> f64 { + match self.get_stack(dst) { + &Value::Float(f) => f, + &Value::Integer(i) => { + let f = i as f64; + self.set_stack(dst, Value::Float(f)); + f + } + // TODO convert string + ref v => panic!("not number {v:?}"), + } + } +} + +// API +impl<'a> ExeState { + pub fn get_top(&self) -> usize { + self.stack.len() - self.base + } + pub fn get(&'a self, i: usize) -> T where T: From<&'a Value> { + (&self.stack[self.base + i - 1]).into() + } + pub fn push(&mut self, v: impl Into) { + self.stack.push(v.into()); + } +} + +fn exe_binop(v1: &Value, v2: &Value, arith_i: fn(i64,i64)->i64, arith_f: fn(f64,f64)->f64) -> Value { + match (v1, v2) { + (&Value::Integer(i1), &Value::Integer(i2)) => Value::Integer(arith_i(i1, i2)), + (&Value::Integer(i1), &Value::Float(f2)) => Value::Float(arith_f(i1 as f64, f2)), + (&Value::Float(f1), &Value::Float(f2)) => Value::Float(arith_f(f1, f2)), + (&Value::Float(f1), &Value::Integer(i2)) => Value::Float(arith_f(f1, i2 as f64)), + (_, _) => todo!("meta"), + } +} +fn exe_binop_int(v1: &Value, i2: u8, arith_i: fn(i64,i64)->i64, arith_f: fn(f64,f64)->f64) -> Value { + match v1 { + &Value::Integer(i1) => Value::Integer(arith_i(i1, i2 as i64)), + &Value::Float(f1) => Value::Float(arith_f(f1, i2 as f64)), + _ => todo!("meta"), + } +} + +fn exe_binop_f(v1: &Value, v2: &Value, arith_f: fn(f64,f64)->f64) -> Value { + let (f1, f2) = match (v1, v2) { + (&Value::Integer(i1), &Value::Integer(i2)) => (i1 as f64, i2 as f64), + (&Value::Integer(i1), &Value::Float(f2)) => (i1 as f64, f2), + (&Value::Float(f1), &Value::Float(f2)) => (f1, f2), + (&Value::Float(f1), &Value::Integer(i2)) => (f1, i2 as f64), + (_, _) => todo!("meta"), + }; + Value::Float(arith_f(f1, f2)) +} +fn exe_binop_int_f(v1: &Value, i2: u8, arith_f: fn(f64,f64)->f64) -> Value { + let f1 = match v1 { + &Value::Integer(i1) => i1 as f64, + &Value::Float(f1) => f1, + _ => todo!("meta"), + }; + Value::Float(arith_f(f1, i2 as f64)) +} + +fn exe_binop_i(v1: &Value, v2: &Value, arith_i: fn(i64,i64)->i64) -> Value { + let (i1, i2) = match (v1, v2) { + (&Value::Integer(i1), &Value::Integer(i2)) => (i1, i2), + (&Value::Integer(i1), &Value::Float(f2)) => (i1, ftoi(f2).unwrap()), + (&Value::Float(f1), &Value::Float(f2)) => (ftoi(f1).unwrap(), ftoi(f2).unwrap()), + (&Value::Float(f1), &Value::Integer(i2)) => (ftoi(f1).unwrap(), i2), + (_, _) => todo!("meta"), + }; + Value::Integer(arith_i(i1, i2)) +} +fn exe_binop_int_i(v1: &Value, i2: u8, arith_i: fn(i64,i64)->i64) -> Value { + let i1 = match v1 { + &Value::Integer(i1) => i1, + &Value::Float(f1) => ftoi(f1).unwrap(), + _ => todo!("meta"), + }; + Value::Integer(arith_i(i1, i2 as i64)) +} + +fn for_check(i: T, limit: T, is_step_positive: bool) -> bool { + if is_step_positive { + i <= limit + } else { + i >= limit + } +} + +fn for_int_limit(limit: f64, is_step_positive: bool, i: &mut i64) -> i64 { + if is_step_positive { + if limit < i64::MIN as f64 { + // The limit is so negative that the for-loop should not run, + // because any initial integer value is greater than such limit. + // If we do not handle this case specially and return (limit as i64) + // as normal, which will be converted into i64::MIN, and if the + // initial integer is i64::MIN too, then the loop will run once, + // which is wrong! + // So we reset the initial integer to 0 and return limit as -1, + // to make sure the loop must not be run. + *i = 0; + -1 + } else { + limit.floor() as i64 + } + } else { + if limit > i64::MAX as f64 { + *i = 0; + 1 + } else { + limit.ceil() as i64 } } -// ANCHOR_END: set_stack } \ No newline at end of file diff --git a/tests/_env.lua b/tests/_env.lua new file mode 100644 index 0000000..402cb9f --- /dev/null +++ b/tests/_env.lua @@ -0,0 +1,19 @@ +local function my_print(a) + print("test _ENV:", a) +end + +-- _ENV as local variable +local function test_local_env() + local _ENV = { print = my_print } + print "hello, world!" -- this `print` is my_print +end + +test_local_env() + +-- _ENV as upvalue +local _ENV = { print = my_print } +local function test_upvalue_env() + print "hello, upvalue!" -- this `print` is my_print +end + +test_upvalue_env() \ No newline at end of file diff --git a/tests/ack.lua b/tests/ack.lua new file mode 100644 index 0000000..f242e24 --- /dev/null +++ b/tests/ack.lua @@ -0,0 +1,17 @@ +-- $Id: ackermann.lua,v 1.5 2000/12/09 20:07:43 doug Exp $ +-- http://www.bagley.org/~doug/shootout/ + +local function Ack(M, N) + if (M == 0) then + return N + 1 + end + if (N == 0) then + return Ack(M - 1, 1) + end + return Ack(M - 1, Ack(M, (N - 1))) +end + +print(Ack(2,10)) + +-- use `--release` to run this: +--print(Ack(3,10)) \ No newline at end of file diff --git a/tests/andxor.lua b/tests/andxor.lua new file mode 100644 index 0000000..61e4fd2 --- /dev/null +++ b/tests/andxor.lua @@ -0,0 +1,18 @@ +g1 = 1 +g2 = 2 + +if g1 or g2 and g3 then + print "test only once" +end + +if g3 or g1 and g2 then + print "test 3 times" +end + +if (g3 or g1) and (g2 or g4) then + print "test 3 times" +end + +if (g3 or g1) and (g2 and g4) then + print "test 4 times and fail" +end \ No newline at end of file diff --git a/tests/args.lua b/tests/args.lua new file mode 100644 index 0000000..d198b11 --- /dev/null +++ b/tests/args.lua @@ -0,0 +1,8 @@ +local function f(a, b) + print(a+b) +end + +f(1,2) +f(100,200) +f(1,2,3) +f(1) \ No newline at end of file diff --git a/tests/base.lua b/tests/base.lua new file mode 100644 index 0000000..477ae24 --- /dev/null +++ b/tests/base.lua @@ -0,0 +1,8 @@ +local a, b = 1, 2 + +local function hello() + local a = 4 + print (a) +end + +hello() \ No newline at end of file diff --git a/tests/binary-tree.lua b/tests/binary-tree.lua new file mode 100644 index 0000000..47ef7a4 --- /dev/null +++ b/tests/binary-tree.lua @@ -0,0 +1,47 @@ +-- The Computer Language Benchmarks Game +-- http://benchmarksgame.alioth.debian.org/ +-- contributed by Mike Pall + +local function BottomUpTree(item, depth) + if depth > 0 then + local i = item + item + depth = depth - 1 + local left, right = BottomUpTree(i-1, depth), BottomUpTree(i, depth) + return { item, left, right } + else + return { item } + end + end + + local function ItemCheck(tree) + if tree[2] then + return tree[1] + ItemCheck(tree[2]) - ItemCheck(tree[3]) + else + return tree[1] + end + end + + local N = 10 -- input argument + local mindepth = 4 + local maxdepth = mindepth + 2 + if maxdepth < N then maxdepth = N end + + do + local stretchdepth = maxdepth + 1 + local stretchtree = BottomUpTree(0, stretchdepth) + print("stretch tree of depth", stretchdepth, "check: ", ItemCheck(stretchtree)) + end + + local longlivedtree = BottomUpTree(0, maxdepth) + + for depth=mindepth,maxdepth,2 do + local iterations = 2 ^ (maxdepth - depth + mindepth) + local check = 0 + for i=1,iterations do + check = check + ItemCheck(BottomUpTree(1, depth)) + + ItemCheck(BottomUpTree(-1, depth)) + end + print(iterations*2, "trees of depth", depth, "check: ", check) + end + + print("long lived tree of depth", maxdepth, "check:", ItemCheck(longlivedtree)) \ No newline at end of file diff --git a/tests/block.lua b/tests/block.lua new file mode 100644 index 0000000..a8b1312 --- /dev/null +++ b/tests/block.lua @@ -0,0 +1,45 @@ +local f, g, h +local up1 = 1 +do + local up2 = 2 + do + local up3 = 3 + + -- closure with local variable in block + f = function() + up3 = up3 + 1 + print(up3) + end + + -- closure with local variable out of block + g = function() + up2 = up2 + 1 + print(up2) + end + + -- closure with local variable out of block 2 levels + h = function() + up1 = up1 + 1 + print(up1) + end + + -- call these closures in block + f() + g() + h() + print(up1, up2, up3) + end + + -- call these closures out of block + f() + g() + h() + print(up1, up2) + +end + +-- call these closures out of block +f() +g() +h() +print(up1) \ No newline at end of file diff --git a/tests/break.lua b/tests/break.lua new file mode 100644 index 0000000..7eec601 --- /dev/null +++ b/tests/break.lua @@ -0,0 +1,10 @@ +local z = 1 +while z do + while z do + print "break inner" + break + end + + print "break out" + break +end \ No newline at end of file diff --git a/tests/closure.lua b/tests/closure.lua new file mode 100644 index 0000000..0ad3c93 --- /dev/null +++ b/tests/closure.lua @@ -0,0 +1,6 @@ +local c1 = new_counter() +local c2 = new_counter() +c1() +c2() +c2() +c1() \ No newline at end of file diff --git a/tests/compare.lua b/tests/compare.lua new file mode 100644 index 0000000..d6dca73 --- /dev/null +++ b/tests/compare.lua @@ -0,0 +1,14 @@ +local a, b = 123, "hello" +if a >= 123 and b == "hello" then + print "yes" +end + +if b <= "world" then + print (a>100) +end + +print (a == 1000 and b == "hello") +print (ab) +print (a<=b) +print (a>=b) \ No newline at end of file diff --git a/tests/continue.lua b/tests/continue.lua new file mode 100644 index 0000000..5115016 --- /dev/null +++ b/tests/continue.lua @@ -0,0 +1,34 @@ +-- validate compatibility +continue = print -- continue as global variable name, and assign it a value +continue(continue) -- call continue as function + +-- continue in while loop +local c = true +while c do + print "hello, while" + if true then + c = false + continue + end + print "should not print this!" +end + +-- continue in repeat loop +repeat + print "hello, repeat" + local ok = true + if true then + continue -- continue after local + end + print "should not print this!" +until ok + +-- continue skip local in repeat loop +-- PANIC! +repeat + print "hello, repeat again" + if true then + continue -- skip `ok`!!! error in parsing + end + local ok = true +until ok \ No newline at end of file diff --git a/tests/do.lua b/tests/do.lua new file mode 100644 index 0000000..5f8d754 --- /dev/null +++ b/tests/do.lua @@ -0,0 +1,7 @@ +local a = 123 +do + local a = 789 + local b = 789 +end + print(a) -- 123 + print(b) -- nil \ No newline at end of file diff --git a/tests/else.lua b/tests/else.lua new file mode 100644 index 0000000..a8622c5 --- /dev/null +++ b/tests/else.lua @@ -0,0 +1,20 @@ +local a,b = 123 +if b then + print "not here" +elseif g then + print "not here" +elseif a then + print "yes, here" +else + print "not here" +end + +if b then + print "not here" +else + print "yes, here" +end + +if b then + print "yes, here" +end \ No newline at end of file diff --git a/tests/escape.lua b/tests/escape.lua new file mode 100644 index 0000000..39b2ece --- /dev/null +++ b/tests/escape.lua @@ -0,0 +1,5 @@ +print "tab:\thi" -- tab +print "\xE4\xBD\xA0\xE5\xA5\xBD" -- 你好 +print "\xE4\xBD" -- invalid UTF-8 +print "\72\101\108\108\111" -- Hello +print "null: \0." -- '\0' \ No newline at end of file diff --git a/tests/fannkuch-redux.lua b/tests/fannkuch-redux.lua new file mode 100644 index 0000000..6029147 --- /dev/null +++ b/tests/fannkuch-redux.lua @@ -0,0 +1,48 @@ +-- The Computer Language Benchmarks Game +-- http://benchmarksgame.alioth.debian.org/ +-- contributed by Mike Pall + +local function fannkuch(n) + local p, q, s, sign, maxflips, sum = {}, {}, {}, 1, 0, 0 + for i=1,n do p[i] = i; q[i] = i; s[i] = i end + repeat + -- Copy and flip. + local q1 = p[1] -- Cache 1st element. + if q1 ~= 1 then + for i=2,n do q[i] = p[i] end -- Work on a copy. + local flips = 1 + repeat + local qq = q[q1] + if qq == 1 then -- ... until 1st element is 1. + sum = sum + sign*flips + if flips > maxflips then maxflips = flips end -- New maximum? + break + end + q[q1] = q1 + if q1 >= 4 then + local i, j = 2, q1 - 1 + repeat q[i], q[j] = q[j], q[i]; i = i + 1; j = j - 1; until i >= j + end + q1 = qq; flips = flips + 1 + until false + end + -- Permute. + if sign == 1 then + p[2], p[1] = p[1], p[2]; sign = -1 -- Rotate 1<-2. + else + p[2], p[3] = p[3], p[2]; sign = 1 -- Rotate 1<-2 and 1<-2<-3. + for i=3,n do + local sx = s[i] + if sx ~= 1 then s[i] = sx-1; break end + if i == n then return sum, maxflips end -- Out of permutations. + s[i] = i + -- Rotate 1<-...<-i+1. + local t = p[1]; for j=1,i do p[j] = p[j+1] end; p[i+1] = t + end + end + until false + end + + local n = 7 -- input argument + local sum, flips = fannkuch(n) + print(sum, "\nPfannkuchen(", n, ") = ", flips, "\n") \ No newline at end of file diff --git a/tests/genericfor.lua b/tests/genericfor.lua new file mode 100644 index 0000000..29c1d8a --- /dev/null +++ b/tests/genericfor.lua @@ -0,0 +1,25 @@ +local function iter(t, i) + i = i + 1 + local v = t[i] + if v then + return i, v + end +end + +local function my_ipairs(t) + return iter, t, 0 +end + +local z = {'hello', 123, 456} +for i,v in my_ipairs(z) do + print(i, v) +end + +for i,v in iter,z,0 do + print(i, v) +end + +for i,v in my_ipairs(z) do + print(i, v) + i = i+1 -- update ctrl-var during loop +end \ No newline at end of file diff --git a/tests/goto.lua b/tests/goto.lua new file mode 100644 index 0000000..174d9d1 --- /dev/null +++ b/tests/goto.lua @@ -0,0 +1,32 @@ +local f +local first = true + +::again:: + +-- step.1 +if f then -- set after goto + -- step.4 + f('first') + f('first') + f('first') + first = false +end + +-- step.2 and step.5 +local i = 0 -- this local variable should be closed on goto +f = function (prefix) + i = i + 1 + print(prefix, i) +end + +if first then + -- step.3 + goto again -- close `local i` +end + +-- step.6 +-- `f` is a new closure, with a new `i` +-- so the following calls should print: 1,2,3 +f('after') +f('after') +f('after') \ No newline at end of file diff --git a/tests/goto2.lua b/tests/goto2.lua new file mode 100644 index 0000000..8718706 --- /dev/null +++ b/tests/goto2.lua @@ -0,0 +1,23 @@ +::label1:: +print("block: 1") +goto label2 +::label3:: +print("block: 3") +goto label4 +::label2:: +do + print("block: 2") + goto label3 -- goto outer block +end +::label4:: +print("block: 4") + + +do + goto label + local a = 'local var' + ::label:: -- skip the local var but it's OK + ::another:: -- void statment + ;; -- void statment + ::another2:: -- void statment +end \ No newline at end of file diff --git a/tests/hello_world.lua b/tests/hello_world.lua index 0351b96..2a1e66e 100644 --- a/tests/hello_world.lua +++ b/tests/hello_world.lua @@ -1,3 +1,3 @@ print "hello, world!" -print "hi" +print "你好" print "hi, 😀" \ No newline at end of file diff --git a/tests/if.lua b/tests/if.lua new file mode 100644 index 0000000..b6dd6da --- /dev/null +++ b/tests/if.lua @@ -0,0 +1,9 @@ +if a then + print "skip this" +end +if print then + local a = "I am true" + print(a) +end + +print (a) -- should be nil \ No newline at end of file diff --git a/tests/multret.lua b/tests/multret.lua new file mode 100644 index 0000000..50e2c4f --- /dev/null +++ b/tests/multret.lua @@ -0,0 +1,10 @@ +function f1(a, b) + return a+b, a-b +end +function f2(a, b) + return f1(a+b, a-b) -- return MULTRET +end + +x,y = f2(f2(3, 10)) -- MULTRET arguments +print(x) +print(y) \ No newline at end of file diff --git a/tests/number.lua b/tests/number.lua new file mode 100644 index 0000000..30708b6 --- /dev/null +++ b/tests/number.lua @@ -0,0 +1,10 @@ +print(0) +print(0.0) +print(123) +print(123.123) +print(123.0) +print(.123) +print(123.0e10) +print(123.0e-10) +print(.123E-01) +print(-.123E-01) \ No newline at end of file diff --git a/tests/print.lua b/tests/print.lua new file mode 100644 index 0000000..7791169 --- /dev/null +++ b/tests/print.lua @@ -0,0 +1,5 @@ +print(1,2,3) +function f(...) + print(print(...)) +end +f(100,200,"hello") \ No newline at end of file diff --git a/tests/queen.lua b/tests/queen.lua new file mode 100644 index 0000000..75d57c1 --- /dev/null +++ b/tests/queen.lua @@ -0,0 +1,47 @@ +--local N = tonumber(arg[1] or 8) -- board size +local N = 8 + + +-- check whether position (n,c) is free from attacks +local function isplaceok (a, n, c) + for i = 1, n - 1 do -- for each queen already placed + if (a[i] == c) or -- same column? + (a[i] - i == c - n) or -- same diagonal? + (a[i] + i == c + n) then -- same diagonal? + return false -- place can be attacked + end + end + return true -- no attacks; place is OK +end + + +-- print a board +local function printsolution (a) + for i = 1, N do + local line = "" + for j = 1, N do + line = line .. (a[i] == j and "X " or "- ") + end + print(line) + end + print("") +end + + +-- add to board 'a' all queens from 'n' to 'N' +local function addqueen (a, n) + if n > N then -- all queens have been placed? + printsolution(a) + else -- try to place n-th queen + for c = 1, N do + if isplaceok(a, n, c) then + a[n] = c -- place n-th queen at column 'c' + addqueen(a, n + 1) + end + end + end +end + + +-- run the program +addqueen({}, 1) diff --git a/tests/repeat.lua b/tests/repeat.lua new file mode 100644 index 0000000..0d9d854 --- /dev/null +++ b/tests/repeat.lua @@ -0,0 +1,5 @@ +local a = false +repeat + print(a) + a = not a +until a \ No newline at end of file diff --git a/tests/rustf.lua b/tests/rustf.lua new file mode 100644 index 0000000..ff94f81 --- /dev/null +++ b/tests/rustf.lua @@ -0,0 +1,6 @@ +print(type(123)) +print(type(123.123)) +print(type("123")) +print(type({})) +print(type(print)) +print(type(function()end)) \ No newline at end of file diff --git a/tests/self.lua b/tests/self.lua new file mode 100644 index 0000000..efcb375 --- /dev/null +++ b/tests/self.lua @@ -0,0 +1,11 @@ +local t = {11,12,13, ['methods']={7, 8, 9}} +function t.methods.foo(a,b) + print(a+b) +end +function t.methods:bar(a,b) + print(self[1]+self[2]+a+b) +end + +t.methods.foo(100, 200) +t.methods:bar(100, 200) +t.methods.bar(t, 100, 200) \ No newline at end of file diff --git a/tests/spectral-norm.lua b/tests/spectral-norm.lua new file mode 100644 index 0000000..203f4ed --- /dev/null +++ b/tests/spectral-norm.lua @@ -0,0 +1,44 @@ +-- The Computer Language Benchmarks Game +-- http://benchmarksgame.alioth.debian.org/ +-- contributed by Mike Pall + +local function A(i, j) + local ij = i+j-1 + return 1.0 / (ij * (ij-1) * 0.5 + i) + end + + local function Av(x, y, N) + for i=1,N do + local a = 0 + for j=1,N do a = a + x[j] * A(i, j) end + y[i] = a + end + end + + local function Atv(x, y, N) + for i=1,N do + local a = 0 + for j=1,N do a = a + x[j] * A(j, i) end + y[i] = a + end + end + + local function AtAv(x, y, t, N) + Av(x, t, N) + Atv(t, y, N) + end + + local N = 100 -- input argument + local u, v, t = {}, {}, {} + for i=1,N do u[i] = 1 end + + for i=1,10 do AtAv(u, v, t, N) AtAv(v, u, t, N) end + + local vBv, vv = 0, 0 + for i=1,N do + local ui, vi = u[i], v[i] + vBv = vBv + ui*vi + vv = vv + vi*vi + end + print(vBv / vv) + --print(math.sqrt(vBv / vv)) \ No newline at end of file diff --git a/tests/strings.lua b/tests/strings.lua new file mode 100644 index 0000000..2753857 --- /dev/null +++ b/tests/strings.lua @@ -0,0 +1,13 @@ +local s = "hello_world" +local m = "middle_string_middle_string" +local l = "long_string_long_string_long_string_long_string_long_string" +print(s) +print(m) +print(l) + +hello_world = 12 +middle_string_middle_string = 345 +long_string_long_string_long_string_long_string_long_string = 6789 +print(hello_world) +print(middle_string_middle_string) +print(long_string_long_string_long_string_long_string_long_string) \ No newline at end of file diff --git a/tests/upvalues.lua b/tests/upvalues.lua new file mode 100644 index 0000000..8b88fc5 --- /dev/null +++ b/tests/upvalues.lua @@ -0,0 +1,25 @@ +g1, g2 = 1, 2 +local up1, up2, up3, up4 = 11, 12, 13, 14 +local print = print +local function foo() + local l1, l2 = 101, 102 + l1, g1 = g2, l2 + print(l1, g1) + + -- assign to upvalues + up1, up2, up3 = l1, g1, up4 + print(up1, up2, up3) + + -- assign by upvalues + l1, g1, up1 = up2, up3, up4 + print(l1, g1, up1) + + local inner = function() + -- assign to upvalues + up1, up2, up3 = 101, g2, up4 + print(up1, up2, up3) + end + inner() +end + +foo() \ No newline at end of file diff --git a/tests/vargs.lua b/tests/vargs.lua new file mode 100644 index 0000000..92cb99f --- /dev/null +++ b/tests/vargs.lua @@ -0,0 +1,9 @@ +function foo(a, b, ...) + local t = {a, ...} + print(t[1], t[2], t[3], t[4]) + local t = {a, ..., b} + print(t[1], t[2], t[3], t[4]) +end + +foo(1) +foo(1,2,100,200,300) \ No newline at end of file diff --git a/tests/while.lua b/tests/while.lua new file mode 100644 index 0000000..b0e14d2 --- /dev/null +++ b/tests/while.lua @@ -0,0 +1,5 @@ +local a = 123 +while a do + print(a) + a = not a +end \ No newline at end of file