talc 0.2.1
This commit is contained in:
parent
00fa1f1d7c
commit
a82a5fa1c6
30 changed files with 1444 additions and 404 deletions
21
Cargo.lock
generated
21
Cargo.lock
generated
|
@ -177,9 +177,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.161"
|
||||
version = "0.2.162"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
|
@ -350,9 +350,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -367,9 +367,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.39"
|
||||
version = "0.38.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee"
|
||||
checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
|
@ -419,10 +419,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "talc-bin"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"ctrlc",
|
||||
"lazy_static",
|
||||
"rustyline",
|
||||
"talc-lang",
|
||||
"talc-std",
|
||||
|
@ -430,7 +431,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "talc-lang"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"num-complex",
|
||||
|
@ -441,7 +442,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "talc-macros"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
|
@ -449,7 +450,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "talc-std"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"rand",
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
members = ["talc-lang", "talc-bin", "talc-std", "talc-macros"]
|
||||
resolver = "2"
|
||||
|
||||
[profile.release]
|
||||
|
||||
[profile.release-opt]
|
||||
inherits = "release"
|
||||
lto = "fat"
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
#!/usr/bin/env talc
|
||||
|
||||
-- adapted from Crafting Interpreters 10.6
|
||||
|
||||
make_counter = \-> do
|
||||
var i = 0
|
||||
\-> do
|
||||
i += 1
|
||||
println(i)
|
||||
end
|
||||
end
|
||||
|
||||
var counter1 = make_counter()
|
||||
counter1() -- 1
|
||||
counter1() -- 2
|
||||
counter1() -- 3
|
||||
counter1() -- 4
|
||||
var counter2 = make_counter()
|
||||
counter2() -- 1
|
||||
counter2() -- 2
|
||||
counter1() -- 5
|
||||
counter1() -- 6
|
||||
counter2() -- 3
|
|
@ -1,23 +0,0 @@
|
|||
#!/usr/bin/env talc
|
||||
|
||||
var i = 0;
|
||||
|
||||
var outer = \n -> do
|
||||
var inner = \-> do
|
||||
i = i + n
|
||||
end
|
||||
end
|
||||
|
||||
var by3 = outer(3)
|
||||
var by5 = outer(5)
|
||||
|
||||
by3()
|
||||
println(i) -- 3
|
||||
by3()
|
||||
println(i) -- 6
|
||||
by5()
|
||||
println(i) -- 11
|
||||
by5()
|
||||
println(i) -- 16
|
||||
by3()
|
||||
println(i) -- 19
|
|
@ -1,9 +0,0 @@
|
|||
#!/usr/bin/env talc
|
||||
|
||||
totient = \n -> do
|
||||
count(factors(n)) | pairs | map(\v -> do
|
||||
v[0]^v[1] - v[0]^(v[1] - 1)
|
||||
end) | prod
|
||||
end
|
||||
|
||||
println(totient(6615)) -- 3024
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "talc-bin"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
|
@ -13,3 +13,4 @@ talc-std = { path = "../talc-std" }
|
|||
rustyline = "14.0"
|
||||
clap = { version = "4.5", features = ["std", "help", "usage", "derive", "error-context"], default-features = false }
|
||||
ctrlc = "3.4"
|
||||
lazy_static = "1.5"
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
use clap::{ColorChoice, Parser};
|
||||
use std::{path::PathBuf, process::ExitCode, rc::Rc};
|
||||
use talc_lang::{
|
||||
compiler::compile, parser, symbol::Symbol, value::function::disasm_recursive, Vm,
|
||||
compiler::compile,
|
||||
lstring::LString,
|
||||
optimize, parser, serial,
|
||||
symbol::Symbol,
|
||||
value::{function::disasm_recursive, Value},
|
||||
Vm,
|
||||
};
|
||||
|
||||
mod helper;
|
||||
|
@ -10,31 +15,40 @@ mod repl;
|
|||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// file to run
|
||||
file: Option<PathBuf>,
|
||||
|
||||
/// start the repl
|
||||
/// Start the repl
|
||||
#[arg(short, long)]
|
||||
repl: bool,
|
||||
|
||||
/// show disassembled bytecode
|
||||
/// Show disassembled bytecode
|
||||
#[arg(short, long)]
|
||||
disasm: bool,
|
||||
|
||||
/// show disassembled bytecode
|
||||
/// Show generated AST
|
||||
#[arg(short = 'a', long)]
|
||||
show_ast: bool,
|
||||
|
||||
/// Compile to a bytecode file
|
||||
#[arg(short, long)]
|
||||
compile: Option<PathBuf>,
|
||||
|
||||
/// Set history file
|
||||
#[arg(short = 'H', long)]
|
||||
histfile: Option<PathBuf>,
|
||||
|
||||
/// enable or disable color
|
||||
#[arg(short, long, default_value = "auto")]
|
||||
color: ColorChoice,
|
||||
|
||||
// file to run and arguments to pass
|
||||
#[arg()]
|
||||
args: Vec<LString>,
|
||||
}
|
||||
|
||||
fn exec(name: Symbol, src: &str, args: &Args) -> ExitCode {
|
||||
let mut vm = Vm::new(256);
|
||||
let mut vm = Vm::new(256, args.args.clone());
|
||||
talc_std::load_all(&mut vm);
|
||||
|
||||
let ex = match parser::parse(src) {
|
||||
let mut ex = match parser::parse(src) {
|
||||
Ok(ex) => ex,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {e}");
|
||||
|
@ -42,7 +56,19 @@ fn exec(name: Symbol, src: &str, args: &Args) -> ExitCode {
|
|||
}
|
||||
};
|
||||
|
||||
let func = Rc::new(compile(&ex, Some(name)));
|
||||
optimize(&mut ex);
|
||||
|
||||
if args.show_ast {
|
||||
eprintln!("{}", ex);
|
||||
}
|
||||
|
||||
let func = match compile(&ex, Some(name)) {
|
||||
Ok(f) => Rc::new(f),
|
||||
Err(e) => {
|
||||
eprintln!("Error: {e}");
|
||||
return ExitCode::FAILURE
|
||||
}
|
||||
};
|
||||
|
||||
if args.disasm {
|
||||
if let Err(e) = disasm_recursive(&func, &mut std::io::stderr()) {
|
||||
|
@ -51,27 +77,56 @@ fn exec(name: Symbol, src: &str, args: &Args) -> ExitCode {
|
|||
}
|
||||
}
|
||||
|
||||
if let Err(e) = vm.run_function(func.clone(), vec![func.into()]) {
|
||||
if let Some(path) = &args.compile {
|
||||
let f = std::fs::File::options()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.create(true)
|
||||
.open(path);
|
||||
let mut w = match f {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
eprintln!("Error opening output file: {e}");
|
||||
return ExitCode::FAILURE
|
||||
}
|
||||
};
|
||||
if let Err(e) = serial::write_program(&mut w, &func) {
|
||||
eprintln!("Error writing bytecode: {e}");
|
||||
return ExitCode::FAILURE
|
||||
}
|
||||
return ExitCode::SUCCESS
|
||||
}
|
||||
|
||||
let res = vm.run_function(func.clone(), vec![func.into()]);
|
||||
|
||||
match res {
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
ExitCode::FAILURE
|
||||
} else {
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Ok(Value::Bool(false)) => ExitCode::FAILURE,
|
||||
Ok(Value::Int(n)) => ExitCode::from(n as u8),
|
||||
_ => ExitCode::SUCCESS,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let args = Args::parse();
|
||||
|
||||
if args.repl || args.file.is_none() {
|
||||
if args.repl || args.args.is_empty() {
|
||||
if args.compile.is_some() {
|
||||
eprintln!("Error: cannot compile in REPL mode");
|
||||
return ExitCode::FAILURE
|
||||
}
|
||||
return repl::repl(&args)
|
||||
}
|
||||
|
||||
let file = args.file.as_ref().unwrap();
|
||||
let file = args.args[0].as_ref();
|
||||
|
||||
match std::fs::read_to_string(file) {
|
||||
Ok(s) => exec(Symbol::get(file.as_os_str()), &s, &args),
|
||||
match std::fs::read_to_string(file.to_os_str()) {
|
||||
Ok(s) => exec(Symbol::get(file), &s, &args),
|
||||
Err(e) => {
|
||||
eprintln!("Error: {e}");
|
||||
eprintln!("Error opening source file: {e}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,20 @@ use rustyline::{
|
|||
};
|
||||
use talc_lang::{
|
||||
compiler::compile_repl,
|
||||
parser,
|
||||
symbol::Symbol,
|
||||
lstr, optimize, parser,
|
||||
symbol::{symbol, Symbol},
|
||||
value::{function::disasm_recursive, Value},
|
||||
Vm,
|
||||
};
|
||||
|
||||
use crate::{helper::TalcHelper, Args};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref SYM_PREV1: Symbol = symbol!("_");
|
||||
pub static ref SYM_PREV2: Symbol = symbol!("__");
|
||||
pub static ref SYM_PREV3: Symbol = symbol!("___");
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct ReplColors {
|
||||
pub reset: &'static str,
|
||||
|
@ -79,13 +85,82 @@ pub fn init_rustyline(args: &Args) -> Result<Editor<TalcHelper, FileHistory>, Ex
|
|||
}
|
||||
}
|
||||
|
||||
fn read_init_file(args: &Args) -> Result<Option<String>, std::io::Error> {
|
||||
if args.args.is_empty() {
|
||||
return Ok(None)
|
||||
}
|
||||
let file = &args.args[0];
|
||||
if file == lstr!("-") {
|
||||
return Ok(None)
|
||||
}
|
||||
std::fs::read_to_string(file.to_os_str()).map(Some)
|
||||
}
|
||||
|
||||
fn exec_line(
|
||||
vm: Rc<RefCell<Vm>>,
|
||||
line: &str,
|
||||
args: &Args,
|
||||
c: &ReplColors,
|
||||
globals: &mut Vec<Symbol>,
|
||||
) {
|
||||
let mut ex = match parser::parse(line) {
|
||||
Ok(ex) => ex,
|
||||
Err(e) => {
|
||||
eprintln!("{}Error:{} {e}", c.error, c.reset);
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
optimize(&mut ex);
|
||||
|
||||
if args.show_ast {
|
||||
eprintln!("{}", ex);
|
||||
}
|
||||
|
||||
let (f, g) = match compile_repl(&ex, globals) {
|
||||
Ok(pair) => pair,
|
||||
Err(e) => {
|
||||
eprintln!("{}Error:{} {e}", c.error, c.reset);
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
*globals = g;
|
||||
let func = Rc::new(f);
|
||||
|
||||
if args.disasm {
|
||||
if let Err(e) = disasm_recursive(&func, &mut std::io::stderr()) {
|
||||
eprintln!("{}Error:{} {e}", c.error, c.reset);
|
||||
}
|
||||
}
|
||||
|
||||
let mut vm = vm.borrow_mut();
|
||||
match vm.run_function(func.clone(), vec![func.into()]) {
|
||||
Ok(v) => {
|
||||
let prev2 = vm.get_global(*SYM_PREV2).unwrap().clone();
|
||||
vm.set_global(*SYM_PREV3, prev2);
|
||||
let prev1 = vm.get_global(*SYM_PREV1).unwrap().clone();
|
||||
vm.set_global(*SYM_PREV2, prev1);
|
||||
vm.set_global(*SYM_PREV1, v.clone());
|
||||
if v != Value::Nil {
|
||||
println!("{v:#}");
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("{}Error:{} {e}", c.error, c.reset),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn repl(args: &Args) -> ExitCode {
|
||||
if args.show_ast {
|
||||
eprintln!("AST printing enabled");
|
||||
}
|
||||
|
||||
if args.disasm {
|
||||
eprintln!("input disassembly enabled");
|
||||
}
|
||||
|
||||
let mut compiler_globals = Vec::new();
|
||||
let mut vm = Vm::new(256);
|
||||
let mut globals = Vec::new();
|
||||
let mut vm = Vm::new(256, args.args.clone());
|
||||
talc_std::load_all(&mut vm);
|
||||
|
||||
let interrupt = vm.get_interrupt();
|
||||
|
@ -96,16 +171,20 @@ pub fn repl(args: &Args) -> ExitCode {
|
|||
eprintln!("Warn: couldn't set ctrl+c handler: {e}");
|
||||
}
|
||||
|
||||
let prev1_sym = Symbol::get("_");
|
||||
let prev2_sym = Symbol::get("__");
|
||||
let prev3_sym = Symbol::get("___");
|
||||
|
||||
vm.set_global(prev1_sym, Value::Nil);
|
||||
vm.set_global(prev2_sym, Value::Nil);
|
||||
vm.set_global(prev3_sym, Value::Nil);
|
||||
|
||||
let c = ReplColors::new(args.color);
|
||||
|
||||
vm.set_global(*SYM_PREV1, Value::Nil);
|
||||
vm.set_global(*SYM_PREV2, Value::Nil);
|
||||
vm.set_global(*SYM_PREV3, Value::Nil);
|
||||
|
||||
let init_src = match read_init_file(args) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("Error opening source file: {e}");
|
||||
return ExitCode::FAILURE
|
||||
}
|
||||
};
|
||||
|
||||
let mut rl = match init_rustyline(args) {
|
||||
Ok(rl) => rl,
|
||||
Err(e) => return e,
|
||||
|
@ -115,6 +194,10 @@ pub fn repl(args: &Args) -> ExitCode {
|
|||
|
||||
rl.set_helper(Some(TalcHelper::new(vm.clone())));
|
||||
|
||||
if let Some(src) = init_src {
|
||||
exec_line(vm.clone(), &src, args, &c, &mut globals);
|
||||
}
|
||||
|
||||
loop {
|
||||
if let Some(f) = &args.histfile {
|
||||
let _ = rl.save_history(f);
|
||||
|
@ -130,37 +213,6 @@ pub fn repl(args: &Args) -> ExitCode {
|
|||
}
|
||||
};
|
||||
|
||||
let ex = match parser::parse(&line) {
|
||||
Ok(ex) => ex,
|
||||
Err(e) => {
|
||||
eprintln!("{}Error:{} {e}", c.error, c.reset);
|
||||
continue
|
||||
}
|
||||
};
|
||||
|
||||
let (f, g) = compile_repl(&ex, &compiler_globals);
|
||||
compiler_globals = g;
|
||||
let func = Rc::new(f);
|
||||
|
||||
if args.disasm {
|
||||
if let Err(e) = disasm_recursive(&func, &mut std::io::stderr()) {
|
||||
eprintln!("{}Error:{} {e}", c.error, c.reset);
|
||||
}
|
||||
}
|
||||
|
||||
let mut vm = vm.borrow_mut();
|
||||
match vm.run_function(func.clone(), vec![func.into()]) {
|
||||
Ok(v) => {
|
||||
let prev2 = vm.get_global(prev2_sym).unwrap().clone();
|
||||
vm.set_global(prev3_sym, prev2);
|
||||
let prev1 = vm.get_global(prev1_sym).unwrap().clone();
|
||||
vm.set_global(prev2_sym, prev1);
|
||||
vm.set_global(prev1_sym, v.clone());
|
||||
if v != Value::Nil {
|
||||
println!("{v:#}");
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("{}Error:{} {e}", c.error, c.reset),
|
||||
}
|
||||
exec_line(vm.clone(), &line, args, &c, &mut globals);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "talc-lang"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
value::Value,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Arg24([u8; 3]);
|
||||
|
||||
impl Arg24 {
|
||||
|
@ -97,36 +97,36 @@ impl TryFrom<Arg24> for Symbol {
|
|||
}
|
||||
|
||||
#[repr(u8, C, align(4))]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum Instruction {
|
||||
#[default]
|
||||
Nop, // do nothing
|
||||
Nop,
|
||||
|
||||
LoadLocal(Arg24), // push nth local onto stack
|
||||
StoreLocal(Arg24), // pop stack into nth local
|
||||
NewLocal, // pop stack into a new local
|
||||
DropLocal(Arg24), // remove last n locals
|
||||
LoadLocal(Arg24),
|
||||
StoreLocal(Arg24),
|
||||
NewLocal,
|
||||
DropLocal(Arg24),
|
||||
|
||||
LoadGlobal(Arg24), // load global by id
|
||||
StoreGlobal(Arg24), // store global by id
|
||||
LoadGlobal(Arg24),
|
||||
StoreGlobal(Arg24),
|
||||
|
||||
CloseOver(Arg24), // load nth local and convert to cell, write back a copy
|
||||
Closure(Arg24), // load constant function and fill state from stack
|
||||
LoadUpvalue(Arg24), // load
|
||||
StoreUpvalue(Arg24), // store a cell from closure state to new local
|
||||
ContinueUpvalue(Arg24), //
|
||||
LoadClosedLocal(Arg24), // load through cell in nth local
|
||||
StoreClosedLocal(Arg24), // store through cell in nth local
|
||||
CloseOver(Arg24),
|
||||
Closure(Arg24),
|
||||
LoadUpvalue(Arg24),
|
||||
StoreUpvalue(Arg24),
|
||||
ContinueUpvalue(Arg24),
|
||||
LoadClosedLocal(Arg24),
|
||||
StoreClosedLocal(Arg24),
|
||||
|
||||
Const(Arg24), // push nth constant
|
||||
Int(Arg24), // push integer
|
||||
Symbol(Arg24), // push symbol
|
||||
Bool(bool), // push boolean
|
||||
Nil, // push nil
|
||||
Const(Arg24),
|
||||
Int(Arg24),
|
||||
Symbol(Arg24),
|
||||
Bool(bool),
|
||||
Nil,
|
||||
|
||||
Dup,
|
||||
DupTwo,
|
||||
Drop(Arg24),
|
||||
Drop,
|
||||
Swap,
|
||||
|
||||
UnaryOp(UnaryOp),
|
||||
|
@ -151,6 +151,7 @@ pub enum Instruction {
|
|||
EndTry,
|
||||
|
||||
Call(u8),
|
||||
Tail(u8),
|
||||
Return,
|
||||
}
|
||||
|
||||
|
@ -161,7 +162,7 @@ impl std::fmt::Display for Instruction {
|
|||
Self::LoadLocal(a) => write!(f, "load ${}", usize::from(a)),
|
||||
Self::StoreLocal(a) => write!(f, "store ${}", usize::from(a)),
|
||||
Self::NewLocal => write!(f, "newlocal"),
|
||||
Self::DropLocal(n) => write!(f, "discardlocal ${}", usize::from(n)),
|
||||
Self::DropLocal(n) => write!(f, "droplocal ${}", usize::from(n)),
|
||||
Self::LoadGlobal(s) => write!(
|
||||
f,
|
||||
"loadglobal {}",
|
||||
|
@ -190,7 +191,7 @@ impl std::fmt::Display for Instruction {
|
|||
Self::Nil => write!(f, "nil"),
|
||||
Self::Dup => write!(f, "dup"),
|
||||
Self::DupTwo => write!(f, "duptwo"),
|
||||
Self::Drop(n) => write!(f, "discard #{}", usize::from(n)),
|
||||
Self::Drop => write!(f, "drop"),
|
||||
Self::Swap => write!(f, "swap"),
|
||||
Self::UnaryOp(o) => write!(f, "unary {o:?}"),
|
||||
Self::BinaryOp(o) => write!(f, "binary {o:?}"),
|
||||
|
@ -208,6 +209,7 @@ impl std::fmt::Display for Instruction {
|
|||
Self::BeginTry(t) => write!(f, "begintry #{}", usize::from(t)),
|
||||
Self::EndTry => write!(f, "endtry"),
|
||||
Self::Call(n) => write!(f, "call #{n}"),
|
||||
Self::Tail(n) => write!(f, "tail #{n}"),
|
||||
Self::Return => write!(f, "return"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,37 @@
|
|||
use std::collections::{BTreeMap, HashMap};
|
||||
use core::fmt;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::chunk::{Arg24, Catch, Chunk, Instruction as I};
|
||||
use crate::parser::ast::{BinaryOp, CatchBlock, Expr, ExprKind, LValue, LValueKind};
|
||||
use crate::parser::Pos;
|
||||
use crate::symbol::{Symbol, SYM_SELF};
|
||||
use crate::throw;
|
||||
use crate::value::function::{FuncAttrs, Function};
|
||||
use crate::value::Value;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CompileError {
|
||||
pos: Pos,
|
||||
msg: String,
|
||||
}
|
||||
|
||||
impl std::error::Error for CompileError {}
|
||||
|
||||
impl fmt::Display for CompileError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} | {}", self.pos, self.msg)
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, CompileError>;
|
||||
|
||||
macro_rules! throw {
|
||||
($pos:expr, $($t:tt)*) => {
|
||||
return Err(CompileError { pos: $pos, msg: format!($($t)*) })
|
||||
};
|
||||
}
|
||||
|
||||
enum ResolveOutcome {
|
||||
Var(VarKind),
|
||||
InParent,
|
||||
|
@ -33,27 +58,39 @@ enum CompilerMode {
|
|||
Module,
|
||||
}
|
||||
|
||||
struct BreakFrame {
|
||||
// ip at beginning of frame
|
||||
start: usize,
|
||||
// indices of jumps to reposition to end
|
||||
end_jumps: Vec<usize>,
|
||||
}
|
||||
|
||||
struct Compiler<'a> {
|
||||
mode: CompilerMode,
|
||||
parent: Option<&'a Compiler<'a>>,
|
||||
// function properties
|
||||
chunk: Chunk,
|
||||
attrs: FuncAttrs,
|
||||
// variables
|
||||
scope: HashMap<Symbol, Var>,
|
||||
shadowed: Vec<(Symbol, Option<Var>)>,
|
||||
closes: BTreeMap<Symbol, usize>,
|
||||
closes: Vec<(Symbol, usize)>,
|
||||
local_count: usize,
|
||||
// break and continue
|
||||
break_frames: Vec<BreakFrame>,
|
||||
drop_barrier: usize,
|
||||
}
|
||||
|
||||
pub fn compile(expr: &Expr, name: Option<Symbol>) -> Function {
|
||||
pub fn compile(expr: &Expr, name: Option<Symbol>) -> Result<Function> {
|
||||
let mut comp = Compiler::new_module(name, None);
|
||||
comp.expr(expr);
|
||||
comp.finish()
|
||||
comp.expr(expr)?;
|
||||
Ok(comp.finish())
|
||||
}
|
||||
|
||||
pub fn compile_repl(expr: &Expr, globals: &[Symbol]) -> (Function, Vec<Symbol>) {
|
||||
pub fn compile_repl(expr: &Expr, globals: &[Symbol]) -> Result<(Function, Vec<Symbol>)> {
|
||||
let mut comp = Compiler::new_repl(globals);
|
||||
comp.expr(expr);
|
||||
comp.finish_repl()
|
||||
comp.expr(expr)?;
|
||||
Ok(comp.finish_repl())
|
||||
}
|
||||
|
||||
impl<'a> Default for Compiler<'a> {
|
||||
|
@ -74,7 +111,9 @@ impl<'a> Default for Compiler<'a> {
|
|||
scope,
|
||||
shadowed: Vec::new(),
|
||||
local_count: 1,
|
||||
closes: BTreeMap::new(),
|
||||
closes: Vec::new(),
|
||||
break_frames: Vec::new(),
|
||||
drop_barrier: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +167,7 @@ impl<'a> Compiler<'a> {
|
|||
Function::new(Rc::new(self.chunk), self.attrs, self.closes.len())
|
||||
}
|
||||
|
||||
pub fn finish_inner(mut self) -> (Function, BTreeMap<Symbol, usize>) {
|
||||
pub fn finish_inner(mut self) -> (Function, Vec<(Symbol, usize)>) {
|
||||
self.emit(I::Return);
|
||||
// TODO closure
|
||||
(
|
||||
|
@ -161,54 +200,47 @@ impl<'a> Compiler<'a> {
|
|||
self.chunk.add_instr(instr)
|
||||
}
|
||||
|
||||
fn emit_discard(&mut self, mut n: usize) {
|
||||
while n > 0 {
|
||||
fn emit_discard(&mut self) {
|
||||
if self.ip() <= self.drop_barrier {
|
||||
self.emit(I::Drop);
|
||||
return
|
||||
}
|
||||
|
||||
let instrs = &mut self.chunk.instrs;
|
||||
|
||||
// dup followed by store: remove the dup
|
||||
if instrs.len() >= 2
|
||||
&& matches!(instrs.get(instrs.len() - 2), Some(I::Dup))
|
||||
&& matches!(
|
||||
instrs.last(),
|
||||
match instrs.last() {
|
||||
Some(
|
||||
I::Dup
|
||||
| I::Const(_)
|
||||
| I::Int(_)
|
||||
| I::Nil
|
||||
| I::Bool(_)
|
||||
| I::Symbol(_)
|
||||
| I::LoadLocal(_)
|
||||
| I::LoadClosedLocal(_)
|
||||
| I::LoadUpvalue(_),
|
||||
) => {
|
||||
// final side-effectless instruction
|
||||
instrs.pop().unwrap();
|
||||
}
|
||||
Some(
|
||||
I::NewLocal
|
||||
| I::StoreLocal(_) | I::StoreGlobal(_)
|
||||
| I::StoreLocal(_)
|
||||
| I::StoreGlobal(_)
|
||||
| I::StoreClosedLocal(_)
|
||||
| I::StoreUpvalue(_)
|
||||
)
|
||||
) {
|
||||
| I::StoreUpvalue(_),
|
||||
) if instrs.len() >= 2 => {
|
||||
if instrs[instrs.len() - 2] == I::Dup {
|
||||
// dup followed by store: eliminate the dup
|
||||
// can't panic: checked that instrs.len() >= 2
|
||||
let i = self.chunk.instrs.pop().unwrap();
|
||||
self.chunk.instrs.pop().unwrap();
|
||||
self.chunk.instrs.push(i);
|
||||
n -= 1;
|
||||
continue
|
||||
}
|
||||
|
||||
// final side-effectless instruction
|
||||
let poppable = matches!(
|
||||
instrs.last(),
|
||||
Some(
|
||||
I::Dup
|
||||
| I::Const(_) | I::Int(_)
|
||||
| I::Nil | I::Bool(_)
|
||||
| I::Symbol(_) | I::LoadLocal(_)
|
||||
| I::LoadClosedLocal(_)
|
||||
| I::LoadUpvalue(_)
|
||||
)
|
||||
);
|
||||
if poppable {
|
||||
// can't panic: checked that instrs.last() was Some
|
||||
instrs.pop().unwrap();
|
||||
n -= 1;
|
||||
continue
|
||||
}
|
||||
|
||||
// no more optimizations possible
|
||||
break
|
||||
_ => {
|
||||
self.emit(I::Drop);
|
||||
}
|
||||
if n > 0 {
|
||||
self.emit(I::Drop(Arg24::from_usize(n)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,6 +252,49 @@ impl<'a> Compiler<'a> {
|
|||
self.chunk.instrs[n] = new;
|
||||
}
|
||||
|
||||
fn set_drop_barrier(&mut self) {
|
||||
self.drop_barrier = self.ip();
|
||||
}
|
||||
|
||||
fn begin_break_frame(&mut self) {
|
||||
self.break_frames.push(BreakFrame {
|
||||
start: self.ip(),
|
||||
end_jumps: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
fn emit_break(&mut self, pos: Pos) -> Result<()> {
|
||||
let i = self.emit(I::Nop);
|
||||
let Some(bf) = self.break_frames.last_mut() else {
|
||||
throw!(pos, "break statement outside of any loop")
|
||||
};
|
||||
bf.end_jumps.push(i);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_continue(&mut self, pos: Pos) -> Result<()> {
|
||||
let Some(bf) = self.break_frames.last_mut() else {
|
||||
throw!(pos, "continue statement outside of any loop")
|
||||
};
|
||||
let start = bf.start;
|
||||
self.emit(I::Jump(Arg24::from_usize(start)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end_break_frame(&mut self) {
|
||||
let bf = self
|
||||
.break_frames
|
||||
.pop()
|
||||
.expect("attempt to close unopened break frame");
|
||||
let count = bf.end_jumps.len();
|
||||
for j in bf.end_jumps {
|
||||
self.update_instr(j, I::Jump(Arg24::from_usize(self.ip())));
|
||||
}
|
||||
if count > 0 {
|
||||
self.set_drop_barrier();
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn begin_scope(&mut self) -> usize {
|
||||
self.shadowed.len()
|
||||
|
@ -230,10 +305,13 @@ impl<'a> Compiler<'a> {
|
|||
while self.shadowed.len() > scope {
|
||||
let (name, var) = self.shadowed.pop().expect("scope bad");
|
||||
|
||||
if let Some(var) = var {
|
||||
if var.kind != VarKind::Global {
|
||||
if let Some(in_var) = self.scope.get(&name) {
|
||||
if in_var.kind != VarKind::Global {
|
||||
locals += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(var) = var {
|
||||
self.scope.insert(name, var);
|
||||
} else {
|
||||
self.scope.remove(&name);
|
||||
|
@ -257,13 +335,14 @@ impl<'a> Compiler<'a> {
|
|||
let Some(parent) = self.parent else {
|
||||
return ResolveOutcome::None
|
||||
};
|
||||
if let ResolveOutcome::None = parent.resolve_name(name) {
|
||||
return ResolveOutcome::None
|
||||
match parent.resolve_name(name) {
|
||||
ResolveOutcome::Var(VarKind::Global) => ResolveOutcome::Var(VarKind::Global),
|
||||
ResolveOutcome::None => ResolveOutcome::None,
|
||||
_ => ResolveOutcome::InParent,
|
||||
}
|
||||
ResolveOutcome::InParent
|
||||
}
|
||||
|
||||
fn load_var(&mut self, name: Symbol) {
|
||||
fn load_var(&mut self, name: Symbol) -> Result<()> {
|
||||
match self.resolve_name(name) {
|
||||
ResolveOutcome::Var(VarKind::Local(n)) => {
|
||||
self.emit(I::LoadLocal(Arg24::from_usize(n)));
|
||||
|
@ -272,11 +351,11 @@ impl<'a> Compiler<'a> {
|
|||
self.emit(I::LoadClosedLocal(Arg24::from_usize(n)));
|
||||
}
|
||||
ResolveOutcome::InParent => {
|
||||
let n = match self.closes.get(&name) {
|
||||
Some(n) => *n,
|
||||
let n = match self.closes.iter().position(|(n, _)| *n == name) {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
let n = self.closes.len();
|
||||
self.closes.insert(name, n);
|
||||
self.closes.push((name, n));
|
||||
n
|
||||
}
|
||||
};
|
||||
|
@ -286,6 +365,7 @@ impl<'a> Compiler<'a> {
|
|||
self.emit(I::LoadGlobal(Arg24::from_symbol(name)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn declare_local(&mut self, name: Symbol) -> usize {
|
||||
|
@ -328,11 +408,11 @@ impl<'a> Compiler<'a> {
|
|||
self.emit(I::StoreClosedLocal(Arg24::from_usize(n)));
|
||||
}
|
||||
ResolveOutcome::InParent => {
|
||||
let n = match self.closes.get(&name) {
|
||||
Some(n) => *n,
|
||||
let n = match self.closes.iter().position(|(n, _)| *n == name) {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
let n = self.closes.len();
|
||||
self.closes.insert(name, n);
|
||||
self.closes.push((name, n));
|
||||
n
|
||||
}
|
||||
};
|
||||
|
@ -354,8 +434,8 @@ impl<'a> Compiler<'a> {
|
|||
// Expressions
|
||||
//
|
||||
|
||||
fn expr(&mut self, e: &Expr) {
|
||||
let Expr { kind, .. } = e;
|
||||
fn expr(&mut self, e: &Expr) -> Result<()> {
|
||||
let Expr { kind, span } = e;
|
||||
match kind {
|
||||
ExprKind::Block(xs) if xs.is_empty() => {
|
||||
self.emit(I::Nil);
|
||||
|
@ -363,33 +443,41 @@ impl<'a> Compiler<'a> {
|
|||
ExprKind::Block(xs) => {
|
||||
let scope = self.begin_scope();
|
||||
for x in &xs[0..xs.len() - 1] {
|
||||
self.expr(x);
|
||||
self.emit_discard(1);
|
||||
self.expr(x)?;
|
||||
self.emit_discard();
|
||||
}
|
||||
self.expr(&xs[xs.len() - 1]);
|
||||
self.expr(&xs[xs.len() - 1])?;
|
||||
self.end_scope(scope);
|
||||
}
|
||||
ExprKind::Literal(v) => self.expr_literal(v),
|
||||
ExprKind::Ident(ident) => self.load_var(*ident),
|
||||
ExprKind::Ident(ident) => self.load_var(*ident)?,
|
||||
ExprKind::UnaryOp(o, a) => {
|
||||
self.expr(a);
|
||||
self.expr(a)?;
|
||||
self.emit(I::UnaryOp(*o));
|
||||
}
|
||||
ExprKind::BinaryOp(o, a, b) => {
|
||||
self.expr(a);
|
||||
self.expr(b);
|
||||
self.expr(a)?;
|
||||
self.expr(b)?;
|
||||
self.emit(I::BinaryOp(*o));
|
||||
}
|
||||
ExprKind::Assign(o, lv, a) => self.expr_assign(*o, lv, a),
|
||||
ExprKind::Assign(o, lv, a) => self.expr_assign(*o, lv, a)?,
|
||||
ExprKind::AssignVar(name, a) => {
|
||||
self.expr(a);
|
||||
if let Some(a) = a {
|
||||
self.expr(a)?;
|
||||
} else {
|
||||
self.emit(I::Nil);
|
||||
}
|
||||
self.emit(I::Dup);
|
||||
self.assign_local(*name);
|
||||
}
|
||||
ExprKind::AssignGlobal(name, a) => {
|
||||
self.expr(a);
|
||||
if let Some(a) = a {
|
||||
self.expr(a)?;
|
||||
self.emit(I::Dup);
|
||||
self.assign_global(*name);
|
||||
} else {
|
||||
self.declare_global(*name);
|
||||
}
|
||||
}
|
||||
ExprKind::List(xs) if xs.is_empty() => {
|
||||
self.emit(I::NewList(0));
|
||||
|
@ -398,7 +486,7 @@ impl<'a> Compiler<'a> {
|
|||
let mut first = true;
|
||||
for chunk in xs.chunks(16) {
|
||||
for e in chunk {
|
||||
self.expr(e);
|
||||
self.expr(e)?;
|
||||
}
|
||||
if first {
|
||||
self.emit(I::NewList(chunk.len() as u8));
|
||||
|
@ -415,8 +503,8 @@ impl<'a> Compiler<'a> {
|
|||
let mut first = true;
|
||||
for chunk in xs.chunks(8) {
|
||||
for (k, v) in chunk {
|
||||
self.expr(k);
|
||||
self.expr(v);
|
||||
self.expr(k)?;
|
||||
self.expr(v)?;
|
||||
}
|
||||
if first {
|
||||
self.emit(I::NewTable(chunk.len() as u8));
|
||||
|
@ -427,91 +515,152 @@ impl<'a> Compiler<'a> {
|
|||
}
|
||||
}
|
||||
ExprKind::Index(ct, idx) => {
|
||||
self.expr(ct);
|
||||
self.expr(idx);
|
||||
self.expr(ct)?;
|
||||
self.expr(idx)?;
|
||||
self.emit(I::Index);
|
||||
}
|
||||
ExprKind::FnCall(f, args) => {
|
||||
self.expr(f);
|
||||
self.expr(f)?;
|
||||
for a in args {
|
||||
self.expr(a);
|
||||
self.expr(a)?;
|
||||
}
|
||||
self.emit(I::Call(args.len() as u8));
|
||||
}
|
||||
ExprKind::TailCall(f, args) => {
|
||||
self.expr(f)?;
|
||||
for a in args {
|
||||
self.expr(a)?;
|
||||
}
|
||||
self.emit(I::Tail(args.len() as u8));
|
||||
}
|
||||
ExprKind::AssocFnCall(o, f, args) => {
|
||||
self.expr(o);
|
||||
self.expr(o)?;
|
||||
self.emit(I::Dup);
|
||||
self.emit(I::Symbol(Arg24::from_symbol(*f)));
|
||||
self.emit(I::Index);
|
||||
self.emit(I::Swap);
|
||||
for a in args {
|
||||
self.expr(a);
|
||||
self.expr(a)?;
|
||||
}
|
||||
self.emit(I::Call((args.len() + 1) as u8));
|
||||
}
|
||||
ExprKind::Return(e) => {
|
||||
self.expr(e);
|
||||
self.emit(I::Return);
|
||||
}
|
||||
ExprKind::Pipe(a, f) => {
|
||||
self.expr(a);
|
||||
self.expr(f);
|
||||
ExprKind::AssocTailCall(o, f, args) => {
|
||||
self.expr(o)?;
|
||||
self.emit(I::Dup);
|
||||
self.emit(I::Symbol(Arg24::from_symbol(*f)));
|
||||
self.emit(I::Index);
|
||||
self.emit(I::Swap);
|
||||
self.emit(I::Call(1));
|
||||
for a in args {
|
||||
self.expr(a)?;
|
||||
}
|
||||
ExprKind::Lambda(args, body) => self.expr_fndef(None, args, body),
|
||||
ExprKind::FnDef(name, args, body) => self.expr_fndef(*name, args, body),
|
||||
ExprKind::And(a, b) => {
|
||||
self.expr(a);
|
||||
self.emit(I::Dup);
|
||||
let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0)));
|
||||
self.emit_discard(1);
|
||||
self.expr(b);
|
||||
self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip())));
|
||||
self.emit(I::Tail((args.len() + 1) as u8));
|
||||
}
|
||||
ExprKind::Or(a, b) => {
|
||||
self.expr(a);
|
||||
self.emit(I::Dup);
|
||||
let j1 = self.emit(I::JumpTrue(Arg24::from_usize(0)));
|
||||
self.emit_discard(1);
|
||||
self.expr(b);
|
||||
self.update_instr(j1, I::JumpTrue(Arg24::from_usize(self.ip())));
|
||||
}
|
||||
ExprKind::If(cond, b1, b2) => {
|
||||
self.expr(cond);
|
||||
let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0)));
|
||||
self.expr(b1);
|
||||
let j2 = self.emit(I::Jump(Arg24::from_usize(0)));
|
||||
self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip())));
|
||||
if let Some(b2) = b2 {
|
||||
self.expr(b2);
|
||||
ExprKind::Return(e) => {
|
||||
if let Some(e) = e {
|
||||
self.expr(e)?;
|
||||
} else {
|
||||
self.emit(I::Nil);
|
||||
}
|
||||
self.emit(I::Return);
|
||||
}
|
||||
ExprKind::Break(e) => {
|
||||
if let Some(e) = e {
|
||||
self.expr(e)?;
|
||||
} else {
|
||||
self.emit(I::Nil);
|
||||
}
|
||||
self.emit_break(span.start)?;
|
||||
}
|
||||
ExprKind::Continue => {
|
||||
self.emit_continue(span.start)?;
|
||||
}
|
||||
ExprKind::Pipe(a, f) => {
|
||||
self.expr(a)?;
|
||||
self.expr(f)?;
|
||||
self.emit(I::Swap);
|
||||
self.emit(I::Call(1));
|
||||
}
|
||||
ExprKind::Lambda(args, body) => self.expr_fndef(None, args, body)?,
|
||||
ExprKind::FnDef(name, args, body) => self.expr_fndef(*name, args, body)?,
|
||||
ExprKind::And(a, b) => {
|
||||
self.expr(a)?;
|
||||
self.emit(I::Dup);
|
||||
let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0)));
|
||||
self.emit_discard();
|
||||
self.expr(b)?;
|
||||
self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip())));
|
||||
}
|
||||
ExprKind::Or(a, b) => {
|
||||
self.expr(a)?;
|
||||
self.emit(I::Dup);
|
||||
let j1 = self.emit(I::JumpTrue(Arg24::from_usize(0)));
|
||||
self.emit_discard();
|
||||
self.expr(b)?;
|
||||
self.update_instr(j1, I::JumpTrue(Arg24::from_usize(self.ip())));
|
||||
}
|
||||
ExprKind::If(cond, b1, b2) => {
|
||||
self.expr(cond)?;
|
||||
let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0)));
|
||||
self.expr(b1)?;
|
||||
let j2 = self.emit(I::Jump(Arg24::from_usize(0)));
|
||||
self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip())));
|
||||
if let Some(b2) = b2 {
|
||||
self.expr(b2)?;
|
||||
} else {
|
||||
self.emit(I::Nil);
|
||||
}
|
||||
self.set_drop_barrier();
|
||||
self.update_instr(j2, I::Jump(Arg24::from_usize(self.ip())));
|
||||
}
|
||||
ExprKind::While(cond, body) => {
|
||||
ExprKind::While(cond, body) => self.expr_while(cond, body)?,
|
||||
ExprKind::For(name, iter, body) => self.expr_for(*name, iter, body)?,
|
||||
ExprKind::Try(body, catches) => self.expr_try(body, catches)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expr_infinite_loop(&mut self, body: &Expr) -> Result<()> {
|
||||
let start = self.ip();
|
||||
self.expr(cond);
|
||||
self.begin_break_frame();
|
||||
|
||||
self.expr(body)?;
|
||||
self.emit_discard();
|
||||
self.emit(I::Jump(Arg24::from_usize(start)));
|
||||
|
||||
self.end_break_frame();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expr_while(&mut self, cond: &Expr, body: &Expr) -> Result<()> {
|
||||
if let ExprKind::Literal(v) = &cond.kind {
|
||||
if v.truthy() {
|
||||
self.expr_infinite_loop(body)?;
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
let start = self.ip();
|
||||
self.begin_break_frame();
|
||||
|
||||
self.expr(cond)?;
|
||||
|
||||
let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0)));
|
||||
self.expr(body);
|
||||
self.emit_discard(1);
|
||||
self.expr(body)?;
|
||||
self.emit_discard();
|
||||
self.emit(I::Jump(Arg24::from_usize(start)));
|
||||
self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip())));
|
||||
|
||||
self.emit(I::Nil);
|
||||
}
|
||||
ExprKind::For(name, iter, body) => self.expr_for(*name, iter, body),
|
||||
ExprKind::Try(body, catches) => self.expr_try(body, catches),
|
||||
}
|
||||
self.end_break_frame();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expr_try(&mut self, body: &Expr, catch_blocks: &[CatchBlock]) {
|
||||
fn expr_try(&mut self, body: &Expr, catch_blocks: &[CatchBlock]) -> Result<()> {
|
||||
let (idx, mut table) = self.chunk.begin_try_table(self.local_count);
|
||||
|
||||
self.emit(I::BeginTry(Arg24::from_usize(idx)));
|
||||
self.expr(body);
|
||||
self.expr(body)?;
|
||||
self.emit(I::EndTry);
|
||||
let body_end_addr = self.emit(I::Jump(Arg24::from_usize(0)));
|
||||
let mut catch_end_addrs = Vec::new();
|
||||
|
@ -527,10 +676,10 @@ impl<'a> Compiler<'a> {
|
|||
if let Some(name) = catch_block.name {
|
||||
self.assign_local(name);
|
||||
} else {
|
||||
self.emit_discard(1);
|
||||
self.emit_discard();
|
||||
}
|
||||
|
||||
self.expr(&catch_block.body);
|
||||
self.expr(&catch_block.body)?;
|
||||
self.end_scope(scope);
|
||||
|
||||
let end_addr = self.emit(I::Jump(Arg24::from_usize(0)));
|
||||
|
@ -544,11 +693,13 @@ impl<'a> Compiler<'a> {
|
|||
}
|
||||
|
||||
self.chunk.finish_catch_table(idx, table);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expr_for(&mut self, name: Symbol, iter: &Expr, body: &Expr) {
|
||||
fn expr_for(&mut self, name: Symbol, iter: &Expr, body: &Expr) -> Result<()> {
|
||||
// load iterable and convert to iterator
|
||||
self.expr(iter);
|
||||
self.expr(iter)?;
|
||||
self.emit(I::IterBegin);
|
||||
|
||||
// declare loop variable
|
||||
|
@ -558,6 +709,7 @@ impl<'a> Compiler<'a> {
|
|||
|
||||
// begin loop
|
||||
let start = self.ip();
|
||||
self.begin_break_frame();
|
||||
|
||||
// call iterator and jump if nil, otherwise store
|
||||
self.emit(I::Dup);
|
||||
|
@ -566,21 +718,30 @@ impl<'a> Compiler<'a> {
|
|||
self.store_var(name);
|
||||
|
||||
// body
|
||||
self.expr(body);
|
||||
self.emit_discard(1);
|
||||
self.expr(body)?;
|
||||
self.emit_discard();
|
||||
|
||||
// end loop
|
||||
self.emit(I::Jump(Arg24::from_usize(start)));
|
||||
|
||||
self.update_instr(j1, I::IterTest(Arg24::from_usize(self.ip())));
|
||||
self.end_scope(scope);
|
||||
|
||||
self.emit(I::Nil);
|
||||
self.end_break_frame();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expr_fndef(&mut self, name: Option<Symbol>, args: &[Symbol], body: &Expr) {
|
||||
fn expr_fndef(
|
||||
&mut self,
|
||||
name: Option<Symbol>,
|
||||
args: &[Symbol],
|
||||
body: &Expr,
|
||||
) -> Result<()> {
|
||||
let mut inner = self.new_function(name, args);
|
||||
inner.parent = Some(self);
|
||||
inner.expr(body);
|
||||
inner.expr(body)?;
|
||||
|
||||
let (func, closes) = inner.finish_inner();
|
||||
let func_const = self.add_const(func.into());
|
||||
|
@ -600,7 +761,7 @@ impl<'a> Compiler<'a> {
|
|||
}
|
||||
ResolveOutcome::InParent => {
|
||||
let n = self.closes.len();
|
||||
self.closes.insert(name, n);
|
||||
self.closes.push((name, n));
|
||||
self.emit(I::ContinueUpvalue(Arg24::from_usize(n)));
|
||||
}
|
||||
ResolveOutcome::None | ResolveOutcome::Var(VarKind::Global) => {
|
||||
|
@ -619,6 +780,8 @@ impl<'a> Compiler<'a> {
|
|||
self.emit(I::Dup);
|
||||
self.store_var(name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expr_literal(&mut self, val: &Value) {
|
||||
|
@ -642,35 +805,36 @@ impl<'a> Compiler<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn expr_assign(&mut self, o: Option<BinaryOp>, lv: &LValue, a: &Expr) {
|
||||
fn expr_assign(&mut self, o: Option<BinaryOp>, lv: &LValue, a: &Expr) -> Result<()> {
|
||||
match (&lv.kind, o) {
|
||||
(LValueKind::Ident(i), None) => {
|
||||
self.expr(a);
|
||||
self.expr(a)?;
|
||||
self.emit(I::Dup);
|
||||
self.store_var(*i);
|
||||
}
|
||||
(LValueKind::Ident(i), Some(o)) => {
|
||||
self.load_var(*i);
|
||||
self.expr(a);
|
||||
self.load_var(*i)?;
|
||||
self.expr(a)?;
|
||||
self.emit(I::BinaryOp(o));
|
||||
self.emit(I::Dup);
|
||||
self.store_var(*i);
|
||||
}
|
||||
(LValueKind::Index(ct, i), None) => {
|
||||
self.expr(ct);
|
||||
self.expr(i);
|
||||
self.expr(a);
|
||||
self.expr(ct)?;
|
||||
self.expr(i)?;
|
||||
self.expr(a)?;
|
||||
self.emit(I::StoreIndex);
|
||||
}
|
||||
(LValueKind::Index(ct, i), Some(o)) => {
|
||||
self.expr(ct);
|
||||
self.expr(i);
|
||||
self.expr(ct)?;
|
||||
self.expr(i)?;
|
||||
self.emit(I::DupTwo);
|
||||
self.emit(I::Index);
|
||||
self.expr(a);
|
||||
self.expr(a)?;
|
||||
self.emit(I::BinaryOp(o));
|
||||
self.emit(I::StoreIndex);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
lstring::LStr,
|
||||
symbol::{Symbol, SYM_DATA, SYM_MSG, SYM_TYPE},
|
||||
symbol::{Symbol, SYM_MSG, SYM_TYPE},
|
||||
value::{HashValue, Value},
|
||||
};
|
||||
use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc};
|
||||
|
@ -11,57 +11,26 @@ pub type Result<T> = std::result::Result<T, Exception>;
|
|||
pub struct Exception {
|
||||
pub ty: Symbol,
|
||||
pub msg: Option<Rc<LStr>>,
|
||||
pub data: Option<Value>,
|
||||
}
|
||||
|
||||
impl Exception {
|
||||
pub fn new(ty: Symbol) -> Self {
|
||||
Self {
|
||||
ty,
|
||||
msg: None,
|
||||
data: None,
|
||||
}
|
||||
Self { ty, msg: None }
|
||||
}
|
||||
|
||||
pub fn new_with_msg(ty: Symbol, msg: Rc<LStr>) -> Self {
|
||||
Self {
|
||||
ty,
|
||||
msg: Some(msg),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_data(ty: Symbol, data: Value) -> Self {
|
||||
Self {
|
||||
ty,
|
||||
msg: None,
|
||||
data: Some(data),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_msg_data(ty: Symbol, msg: Rc<LStr>, data: Value) -> Self {
|
||||
Self {
|
||||
ty,
|
||||
msg: Some(msg),
|
||||
data: Some(data),
|
||||
}
|
||||
Self { ty, msg: Some(msg) }
|
||||
}
|
||||
|
||||
pub fn from_table(table: &Rc<RefCell<HashMap<HashValue, Value>>>) -> Option<Self> {
|
||||
let table = table.borrow();
|
||||
let ty = table.get(&(*SYM_TYPE).into())?;
|
||||
if let Value::Symbol(ty) = ty {
|
||||
let data = table.get(&(*SYM_DATA).into()).cloned();
|
||||
let msg = table.get(&(*SYM_MSG).into());
|
||||
match msg {
|
||||
None => Some(Self {
|
||||
ty: *ty,
|
||||
data,
|
||||
msg: None,
|
||||
}),
|
||||
None => Some(Self { ty: *ty, msg: None }),
|
||||
Some(Value::String(msg)) => Some(Self {
|
||||
ty: *ty,
|
||||
data,
|
||||
msg: Some(msg.clone()),
|
||||
}),
|
||||
Some(_) => None,
|
||||
|
@ -77,9 +46,6 @@ impl Exception {
|
|||
if let Some(msg) = self.msg {
|
||||
table.insert((*SYM_MSG).into(), Value::String(msg));
|
||||
}
|
||||
if let Some(data) = self.data {
|
||||
table.insert((*SYM_DATA).into(), data);
|
||||
}
|
||||
Rc::new(RefCell::new(table))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
#![allow(clippy::mutable_key_type)]
|
||||
#![warn(clippy::semicolon_if_nothing_returned)]
|
||||
#![warn(clippy::allow_attributes)]
|
||||
#![warn(clippy::inconsistent_struct_constructor)]
|
||||
#![warn(clippy::uninlined_format_args)]
|
||||
|
||||
pub mod chunk;
|
||||
pub mod compiler;
|
||||
pub mod exception;
|
||||
pub mod lstring;
|
||||
mod optimize;
|
||||
pub use optimize::optimize;
|
||||
pub mod parser;
|
||||
pub mod serial;
|
||||
pub mod symbol;
|
||||
pub mod value;
|
||||
|
||||
|
|
|
@ -515,6 +515,22 @@ impl<'a> Extend<&'a char> for LString {
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Equality
|
||||
//
|
||||
|
||||
impl PartialEq<LString> for LStr {
|
||||
fn eq(&self, other: &LString) -> bool {
|
||||
self == other.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<LStr> for LString {
|
||||
fn eq(&self, other: &LStr) -> bool {
|
||||
self.as_ref() == other
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// write
|
||||
//
|
||||
|
|
229
talc-lang/src/optimize.rs
Normal file
229
talc-lang/src/optimize.rs
Normal file
|
@ -0,0 +1,229 @@
|
|||
use core::panic;
|
||||
|
||||
use crate::{
|
||||
parser::ast::{CatchBlock, Expr, ExprKind, LValue, LValueKind},
|
||||
value::Value,
|
||||
vm,
|
||||
};
|
||||
|
||||
impl Expr {
|
||||
fn value(&self) -> Option<&Value> {
|
||||
if let ExprKind::Literal(v) = &self.kind {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_take(e: &mut Expr) -> Expr {
|
||||
std::mem::replace(e, ExprKind::Literal(Value::Nil).span(e.span))
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct OptState {
|
||||
ret: bool,
|
||||
}
|
||||
|
||||
impl OptState {
|
||||
#[inline]
|
||||
pub const fn empty() -> Self {
|
||||
Self { ret: false }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn ret(ret: bool) -> Self {
|
||||
Self { ret }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn optimize(body: &mut Expr) {
|
||||
let state = OptState::ret(true);
|
||||
optimize_ex_with(body, state);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn optimize_ex(expr: &mut Expr) {
|
||||
optimize_ex_with(expr, OptState::empty());
|
||||
}
|
||||
|
||||
fn optimize_ex_with(expr: &mut Expr, state: OptState) {
|
||||
let span = expr.span;
|
||||
match &mut expr.kind {
|
||||
ExprKind::Literal(_) => (),
|
||||
ExprKind::Ident(_) => (),
|
||||
ExprKind::UnaryOp(o, e) => {
|
||||
optimize_ex(e);
|
||||
if let Some(a) = e.value() {
|
||||
if let Ok(v) = vm::unary_op(*o, a.clone()) {
|
||||
*expr = ExprKind::Literal(v).span(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::BinaryOp(o, l, r) => {
|
||||
optimize_ex(l);
|
||||
optimize_ex(r);
|
||||
if let (Some(a), Some(b)) = (l.value(), r.value()) {
|
||||
if let Ok(v) = vm::binary_op(*o, a.clone(), b.clone()) {
|
||||
*expr = ExprKind::Literal(v).span(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Assign(_, l, r) => {
|
||||
optimize_lv(l);
|
||||
optimize_ex(r);
|
||||
}
|
||||
ExprKind::AssignVar(_, e) => {
|
||||
if let Some(e) = e {
|
||||
optimize_ex(e);
|
||||
}
|
||||
}
|
||||
ExprKind::AssignGlobal(_, e) => {
|
||||
if let Some(e) = e {
|
||||
optimize_ex(e);
|
||||
}
|
||||
}
|
||||
ExprKind::FnDef(_, _, e) => {
|
||||
optimize_ex_with(e, OptState::ret(true));
|
||||
}
|
||||
ExprKind::Index(l, r) => {
|
||||
optimize_ex(l);
|
||||
optimize_ex(r);
|
||||
}
|
||||
ExprKind::FnCall(f, xs) => {
|
||||
optimize_ex(f);
|
||||
for e in &mut *xs {
|
||||
optimize_ex(e);
|
||||
}
|
||||
if state.ret {
|
||||
*expr = ExprKind::TailCall(Box::new(expr_take(f)), std::mem::take(xs))
|
||||
.span(span);
|
||||
}
|
||||
}
|
||||
ExprKind::AssocFnCall(f, a, xs) => {
|
||||
optimize_ex(f);
|
||||
for e in &mut *xs {
|
||||
optimize_ex(e);
|
||||
}
|
||||
if state.ret {
|
||||
*expr =
|
||||
ExprKind::AssocTailCall(Box::new(expr_take(f)), *a, std::mem::take(xs))
|
||||
.span(span);
|
||||
}
|
||||
}
|
||||
ExprKind::Pipe(l, r) => {
|
||||
optimize_ex(l);
|
||||
optimize_ex(r);
|
||||
}
|
||||
ExprKind::Block(xs) => {
|
||||
let len = xs.len();
|
||||
if len > 1 {
|
||||
for e in &mut xs[..len - 1] {
|
||||
optimize_ex(e);
|
||||
}
|
||||
}
|
||||
if let Some(e) = xs.last_mut() {
|
||||
optimize_ex_with(e, state);
|
||||
}
|
||||
}
|
||||
ExprKind::List(xs) => {
|
||||
for e in xs {
|
||||
optimize_ex(e);
|
||||
}
|
||||
}
|
||||
ExprKind::Table(t) => {
|
||||
for (k, v) in t {
|
||||
optimize_ex(k);
|
||||
optimize_ex(v);
|
||||
}
|
||||
}
|
||||
ExprKind::Return(e) => {
|
||||
if let Some(e) = e {
|
||||
optimize_ex_with(e, OptState::ret(true));
|
||||
}
|
||||
}
|
||||
ExprKind::Break(e) => {
|
||||
if let Some(e) = e {
|
||||
optimize_ex(e);
|
||||
}
|
||||
}
|
||||
ExprKind::Continue => (),
|
||||
ExprKind::And(l, r) => {
|
||||
optimize_ex(l);
|
||||
optimize_ex(r);
|
||||
if let Some(v) = l.value() {
|
||||
if v.truthy() {
|
||||
*expr = expr_take(r);
|
||||
} else {
|
||||
*expr = expr_take(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Or(l, r) => {
|
||||
optimize_ex(l);
|
||||
optimize_ex(r);
|
||||
if let Some(v) = l.value() {
|
||||
if v.truthy() {
|
||||
*expr = expr_take(l);
|
||||
} else {
|
||||
*expr = expr_take(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::If(c, t, e) => {
|
||||
optimize_ex(c);
|
||||
optimize_ex_with(t, state);
|
||||
if let Some(e) = e {
|
||||
optimize_ex_with(e, state);
|
||||
}
|
||||
if let Some(v) = c.value() {
|
||||
if v.truthy() {
|
||||
*expr = expr_take(t);
|
||||
} else if let Some(e) = e {
|
||||
*expr = expr_take(e);
|
||||
} else {
|
||||
*expr = ExprKind::Literal(Value::Nil).span(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::While(c, b) => {
|
||||
optimize_ex(c);
|
||||
optimize_ex(b);
|
||||
if let Some(v) = c.value() {
|
||||
if !v.truthy() {
|
||||
*expr = ExprKind::Literal(Value::Nil).span(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::For(_, i, b) => {
|
||||
optimize_ex(i);
|
||||
optimize_ex(b);
|
||||
}
|
||||
ExprKind::Lambda(_, b) => {
|
||||
optimize_ex_with(b, OptState::ret(true));
|
||||
}
|
||||
ExprKind::Try(b, cx) => {
|
||||
optimize_ex_with(b, state);
|
||||
for c in cx {
|
||||
optimize_cb(c);
|
||||
}
|
||||
}
|
||||
ExprKind::TailCall(..) | ExprKind::AssocTailCall(..) => {
|
||||
panic!("cannot optimize expression generated by optimizer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn optimize_lv(e: &mut LValue) {
|
||||
match &mut e.kind {
|
||||
LValueKind::Ident(_) => (),
|
||||
LValueKind::Index(l, r) => {
|
||||
optimize_ex(l);
|
||||
optimize_ex(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn optimize_cb(e: &mut CatchBlock) {
|
||||
optimize_ex(&mut e.body);
|
||||
}
|
|
@ -4,7 +4,7 @@ use crate::{symbol::Symbol, value::Value};
|
|||
|
||||
use super::Span;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum BinaryOp {
|
||||
Add,
|
||||
Sub,
|
||||
|
@ -30,7 +30,7 @@ pub enum BinaryOp {
|
|||
RangeIncl,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum UnaryOp {
|
||||
Neg,
|
||||
Not,
|
||||
|
@ -52,8 +52,8 @@ pub enum ExprKind {
|
|||
BinaryOp(BinaryOp, Box<Expr>, Box<Expr>),
|
||||
|
||||
Assign(Option<BinaryOp>, Box<LValue>, Box<Expr>),
|
||||
AssignVar(Symbol, Box<Expr>),
|
||||
AssignGlobal(Symbol, Box<Expr>),
|
||||
AssignVar(Symbol, Option<Box<Expr>>),
|
||||
AssignGlobal(Symbol, Option<Box<Expr>>),
|
||||
FnDef(Option<Symbol>, Vec<Symbol>, Box<Expr>),
|
||||
|
||||
Index(Box<Expr>, Box<Expr>),
|
||||
|
@ -65,7 +65,9 @@ pub enum ExprKind {
|
|||
List(Vec<Expr>),
|
||||
Table(Vec<(Expr, Expr)>),
|
||||
|
||||
Return(Box<Expr>),
|
||||
Return(Option<Box<Expr>>),
|
||||
Break(Option<Box<Expr>>),
|
||||
Continue,
|
||||
And(Box<Expr>, Box<Expr>),
|
||||
Or(Box<Expr>, Box<Expr>),
|
||||
If(Box<Expr>, Box<Expr>, Option<Box<Expr>>),
|
||||
|
@ -73,6 +75,10 @@ pub enum ExprKind {
|
|||
For(Symbol, Box<Expr>, Box<Expr>),
|
||||
Lambda(Vec<Symbol>, Box<Expr>),
|
||||
Try(Box<Expr>, Vec<CatchBlock>),
|
||||
|
||||
// Created by optimizer
|
||||
TailCall(Box<Expr>, Vec<Expr>),
|
||||
AssocTailCall(Box<Expr>, Symbol, Vec<Expr>),
|
||||
}
|
||||
|
||||
impl ExprKind {
|
||||
|
@ -189,21 +195,28 @@ impl Expr {
|
|||
}
|
||||
ExprKind::AssignVar(l, r) => {
|
||||
writeln!(w, "var {}", l.name())?;
|
||||
r.write_to(w, depth)
|
||||
if let Some(r) = r {
|
||||
r.write_to(w, depth)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ExprKind::AssignGlobal(l, r) => {
|
||||
writeln!(w, "global {}", l.name())?;
|
||||
r.write_to(w, depth)
|
||||
if let Some(r) = r {
|
||||
r.write_to(w, depth)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ExprKind::FnDef(n, p, b) => {
|
||||
if let Some(n) = n {
|
||||
writeln!(w, "fndef ${}", n.name())?;
|
||||
write!(w, "fndef ${}", n.name())?;
|
||||
} else {
|
||||
writeln!(w, "fndef anon")?;
|
||||
write!(w, "fndef anon")?;
|
||||
}
|
||||
for arg in p {
|
||||
writeln!(w, " ${}", arg.name())?;
|
||||
write!(w, " ${}", arg.name())?;
|
||||
}
|
||||
writeln!(w)?;
|
||||
b.write_to(w, depth)
|
||||
}
|
||||
ExprKind::Index(l, r) => {
|
||||
|
@ -256,7 +269,20 @@ impl Expr {
|
|||
}
|
||||
ExprKind::Return(e) => {
|
||||
writeln!(w, "return")?;
|
||||
e.write_to(w, depth)
|
||||
if let Some(e) = e {
|
||||
e.write_to(w, depth)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ExprKind::Break(e) => {
|
||||
writeln!(w, "break")?;
|
||||
if let Some(e) = e {
|
||||
e.write_to(w, depth)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ExprKind::Continue => {
|
||||
writeln!(w, "continue")
|
||||
}
|
||||
ExprKind::And(l, r) => {
|
||||
writeln!(w, "and")?;
|
||||
|
@ -303,6 +329,23 @@ impl Expr {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
// generated by optimized
|
||||
ExprKind::TailCall(f, a) => {
|
||||
writeln!(w, "tail")?;
|
||||
f.write_to(w, depth)?;
|
||||
for arg in a {
|
||||
arg.write_to(w, depth)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ExprKind::AssocTailCall(d, f, a) => {
|
||||
writeln!(w, "assoc tail {}", f.name())?;
|
||||
d.write_to(w, depth)?;
|
||||
for arg in a {
|
||||
arg.write_to(w, depth)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -219,8 +219,8 @@ impl<'s> Lexer<'s> {
|
|||
fn invalid_char(&self, c: char) -> ParserError {
|
||||
let span = Span::new(self.pos, self.pos.advance(c));
|
||||
let msg = match c as u32 {
|
||||
c @ 0x00..=0x7f => format!("invalid character (codepoint 0x{:2x})", c),
|
||||
c => format!("invalid character (codepoint U+{:04x})", c),
|
||||
c @ 0x00..=0x7f => format!("invalid character (codepoint 0x{c:2x})"),
|
||||
c => format!("invalid character (codepoint U+{c:04x})"),
|
||||
};
|
||||
ParserError { span, msg }
|
||||
}
|
||||
|
@ -326,7 +326,7 @@ impl<'s> Lexer<'s> {
|
|||
let c = self.peek()?;
|
||||
if c == '_' || c.is_digit(radix) {
|
||||
self.next()?;
|
||||
} else if is_xid_start(c) {
|
||||
} else if is_xid_start(c) || c.is_ascii_digit() {
|
||||
return self.unexpected()
|
||||
} else {
|
||||
return self.emit(K::Integer)
|
||||
|
@ -569,11 +569,13 @@ impl<'s> Lexer<'s> {
|
|||
_ => self.emit(K::Dot),
|
||||
},
|
||||
':' => match self.and_peek()? {
|
||||
c if is_xid_start(c) || c == '"' || c == '\'' => self.next_symbol(),
|
||||
c if is_xid_start(c) || c == '_' || c == '"' || c == '\'' => {
|
||||
self.next_symbol()
|
||||
}
|
||||
_ => self.emit(K::Colon),
|
||||
},
|
||||
'0'..='9' => self.next_number(),
|
||||
c if is_xid_start(c) => self.next_ident(),
|
||||
c if c == '_' || is_xid_start(c) => self.next_ident(),
|
||||
'"' | '\'' => self.next_string(),
|
||||
_ => self.unexpected(),
|
||||
}
|
||||
|
|
|
@ -162,9 +162,10 @@ impl TokenKind {
|
|||
matches!(
|
||||
self,
|
||||
T::Return
|
||||
| T::Var | T::Global
|
||||
| T::Fn | T::Not
|
||||
| T::Backslash
|
||||
| T::Continue
|
||||
| T::Break | T::Var
|
||||
| T::Global | T::Fn
|
||||
| T::Not | T::Backslash
|
||||
| T::Colon | T::Minus
|
||||
| T::Identifier
|
||||
| T::LParen | T::LBrack
|
||||
|
@ -569,7 +570,7 @@ impl<'s> Parser<'s> {
|
|||
let lhs_span = lhs.span;
|
||||
if let Some(op) = self.peek()?.kind.assign_op() {
|
||||
let Some(lval) = LValue::from_expr(lhs) else {
|
||||
throw!(lhs_span, "invalid lvalue for assingment")
|
||||
throw!(lhs_span, "invalid lvalue for assignment")
|
||||
};
|
||||
self.next()?;
|
||||
let rhs = self.parse_decl()?;
|
||||
|
@ -582,16 +583,19 @@ impl<'s> Parser<'s> {
|
|||
|
||||
fn parse_var_decl(&mut self) -> Result<Expr> {
|
||||
let first = expect!(self, T::Var | T::Global);
|
||||
let name = expect!(self, T::Identifier);
|
||||
expect!(self, T::Equal);
|
||||
let val = self.parse_decl()?;
|
||||
let val_span = val.span;
|
||||
let kind = if first.kind == T::Global {
|
||||
E::AssignGlobal
|
||||
} else {
|
||||
E::AssignVar
|
||||
};
|
||||
Ok(kind(Symbol::get(name.content), b(val)).span(first.span + val_span))
|
||||
let name = expect!(self, T::Identifier);
|
||||
if try_next!(self, T::Equal).is_some() {
|
||||
let val = self.parse_decl()?;
|
||||
let val_span = val.span;
|
||||
Ok(kind(Symbol::get(name.content), Some(b(val))).span(first.span + val_span))
|
||||
} else {
|
||||
Ok(kind(Symbol::get(name.content), None).span(first.span + name.span))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_fn_decl(&mut self) -> Result<Expr> {
|
||||
|
@ -624,10 +628,30 @@ impl<'s> Parser<'s> {
|
|||
}
|
||||
|
||||
fn parse_expr(&mut self) -> Result<Expr> {
|
||||
if let Some(tok) = try_next!(self, T::Return) {
|
||||
let tok = try_next!(self, T::Return | T::Break | T::Continue);
|
||||
if let Some(tok) = tok {
|
||||
match tok.kind {
|
||||
T::Return => {
|
||||
if self.peek()?.kind.expr_first() {
|
||||
let expr = self.parse_decl()?;
|
||||
let span = expr.span;
|
||||
Ok(E::Return(b(expr)).span(tok.span + span))
|
||||
Ok(E::Return(Some(b(expr))).span(tok.span + span))
|
||||
} else {
|
||||
Ok(E::Return(None).span(tok.span))
|
||||
}
|
||||
}
|
||||
T::Break => {
|
||||
if self.peek()?.kind.expr_first() {
|
||||
let expr = self.parse_decl()?;
|
||||
let span = expr.span;
|
||||
Ok(E::Break(Some(b(expr))).span(tok.span + span))
|
||||
} else {
|
||||
Ok(E::Break(None).span(tok.span))
|
||||
}
|
||||
}
|
||||
T::Continue => Ok(E::Continue.span(tok.span)),
|
||||
_ => unreachable!("parse_expr: guaranteed by try_next!"),
|
||||
}
|
||||
} else {
|
||||
self.parse_decl()
|
||||
}
|
||||
|
|
216
talc-lang/src/serial/mod.rs
Normal file
216
talc-lang/src/serial/mod.rs
Normal file
|
@ -0,0 +1,216 @@
|
|||
use core::fmt;
|
||||
use std::io::{self, Write};
|
||||
|
||||
use crate::{
|
||||
chunk::{Chunk, Instruction, TryTable},
|
||||
lstring::LStr,
|
||||
symbol::Symbol,
|
||||
value::{function::Function, range::RangeType, Value},
|
||||
};
|
||||
|
||||
const MAGIC: [u8; 8] = *b"\x7fTALC\0\0\0";
|
||||
const VERSION: u32 = 1;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SerialError {
|
||||
Io(io::Error),
|
||||
Serial(String),
|
||||
}
|
||||
|
||||
impl std::error::Error for SerialError {}
|
||||
|
||||
impl fmt::Display for SerialError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
SerialError::Io(e) => e.fmt(f),
|
||||
SerialError::Serial(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, SerialError>;
|
||||
|
||||
impl From<io::Error> for SerialError {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self::Io(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for SerialError {
|
||||
fn from(value: String) -> Self {
|
||||
Self::Serial(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for SerialError {
|
||||
fn from(value: &str) -> Self {
|
||||
Self::Serial(value.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_program(w: &mut impl Write, prog: &Function) -> Result<()> {
|
||||
ProgramWriter::new(w).write_program(prog)
|
||||
}
|
||||
|
||||
struct ProgramWriter<'w, W: Write> {
|
||||
w: &'w mut W,
|
||||
}
|
||||
|
||||
impl<'w, W: Write> ProgramWriter<'w, W> {
|
||||
fn new(w: &'w mut W) -> Self {
|
||||
Self { w }
|
||||
}
|
||||
|
||||
fn write_program(&mut self, prog: &Function) -> Result<()> {
|
||||
self.w.write_all(&MAGIC)?;
|
||||
self.w.write_all(&VERSION.to_le_bytes())?;
|
||||
self.write_function(prog)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_function(&mut self, func: &Function) -> Result<()> {
|
||||
self.write_bool(func.attrs.name.is_some())?;
|
||||
if let Some(name) = func.attrs.name {
|
||||
self.write_sym(name)?;
|
||||
}
|
||||
self.write_u32(func.attrs.arity as u32)?;
|
||||
self.write_u32(func.state.len() as u32)?;
|
||||
self.write_chunk(&func.chunk)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_chunk(&mut self, chunk: &Chunk) -> Result<()> {
|
||||
self.write_u32(chunk.consts.len() as u32)?;
|
||||
self.write_u32(chunk.instrs.len() as u32)?;
|
||||
self.write_u32(chunk.try_tables.len() as u32)?;
|
||||
|
||||
for c in &chunk.consts {
|
||||
self.write_value(c)?;
|
||||
}
|
||||
for i in &chunk.instrs {
|
||||
self.write_instr(*i)?;
|
||||
}
|
||||
for t in &chunk.try_tables {
|
||||
self.write_try_table(t)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_value(&mut self, val: &Value) -> Result<()> {
|
||||
match val {
|
||||
Value::Nil => self.write_u8(0),
|
||||
Value::Bool(b) => {
|
||||
self.write_u8(1)?;
|
||||
self.write_bool(*b)
|
||||
}
|
||||
Value::Symbol(sym) => {
|
||||
self.write_u8(2)?;
|
||||
self.write_sym(*sym)
|
||||
}
|
||||
Value::Int(n) => {
|
||||
self.write_u8(3)?;
|
||||
self.write_i64(*n)
|
||||
}
|
||||
Value::Ratio(r) => {
|
||||
self.write_u8(4)?;
|
||||
self.write_i64(*r.numer())?;
|
||||
self.write_i64(*r.denom())
|
||||
}
|
||||
Value::Float(f) => {
|
||||
self.write_u8(5)?;
|
||||
self.write_f64(*f)
|
||||
}
|
||||
Value::Complex(z) => {
|
||||
self.write_u8(6)?;
|
||||
self.write_f64(z.re)?;
|
||||
self.write_f64(z.im)
|
||||
}
|
||||
Value::Range(r) => {
|
||||
self.write_u8(7)?;
|
||||
match r.ty {
|
||||
RangeType::Open => self.write_u8(0)?,
|
||||
RangeType::Closed => self.write_u8(1)?,
|
||||
RangeType::Endless => self.write_u8(2)?,
|
||||
}
|
||||
self.write_i64(r.start)?;
|
||||
self.write_i64(r.stop)
|
||||
}
|
||||
Value::String(s) => {
|
||||
self.write_u8(8)?;
|
||||
self.write_str(s)
|
||||
}
|
||||
Value::Function(func) => {
|
||||
self.write_u8(9)?;
|
||||
self.write_function(func)
|
||||
}
|
||||
Value::Cell(_)
|
||||
| Value::List(_)
|
||||
| Value::Table(_)
|
||||
| Value::NativeFunc(_)
|
||||
| Value::Native(_) => Err(format!(
|
||||
"cannot serialize value of type {}",
|
||||
val.get_type().name()
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_instr(&mut self, i: Instruction) -> Result<()> {
|
||||
let n: u32 = unsafe { std::mem::transmute(i) };
|
||||
self.write_u32(n)
|
||||
}
|
||||
|
||||
fn write_try_table(&mut self, t: &TryTable) -> Result<()> {
|
||||
self.write_u32(t.local_count as u32)?;
|
||||
self.write_u32(t.catches.len() as u32)?;
|
||||
for catch in &t.catches {
|
||||
self.write_u32(catch.addr as u32)?;
|
||||
self.write_bool(catch.types.is_some())?;
|
||||
if let Some(tys) = &catch.types {
|
||||
self.write_u32(tys.len() as u32)?;
|
||||
for ty in tys {
|
||||
self.write_sym(*ty)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_sym(&mut self, sym: Symbol) -> Result<()> {
|
||||
self.write_str(sym.name())
|
||||
}
|
||||
|
||||
fn write_str(&mut self, s: &LStr) -> Result<()> {
|
||||
let Ok(n) = s.len().try_into() else {
|
||||
return Err("string to long to serialize".into())
|
||||
};
|
||||
self.write_u32(n)?;
|
||||
self.w.write_all(s.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_f64(&mut self, x: f64) -> Result<()> {
|
||||
self.w.write_all(&x.to_le_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_i64(&mut self, n: i64) -> Result<()> {
|
||||
self.w.write_all(&n.to_le_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_u32(&mut self, n: u32) -> Result<()> {
|
||||
self.w.write_all(&n.to_le_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_u8(&mut self, n: u8) -> Result<()> {
|
||||
self.w.write_all(&[n])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_bool(&mut self, n: bool) -> Result<()> {
|
||||
self.w.write_all(&[n as u8])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -31,7 +31,6 @@ lazy_static! {
|
|||
pub static ref SYM_END_ITERATION: Symbol = symbol!(end_iteration);
|
||||
pub static ref SYM_TYPE: Symbol = symbol!(type);
|
||||
pub static ref SYM_MSG: Symbol = symbol!(msg);
|
||||
pub static ref SYM_DATA: Symbol = symbol!(data);
|
||||
pub static ref SYM_TYPE_ERROR: Symbol = symbol!(type_error);
|
||||
pub static ref SYM_VALUE_ERROR: Symbol = symbol!(value_error);
|
||||
pub static ref SYM_NAME_ERROR: Symbol = symbol!(name_error);
|
||||
|
|
|
@ -26,8 +26,8 @@ impl Function {
|
|||
|
||||
pub fn from_parts(chunk: Rc<Chunk>, attrs: FuncAttrs, state: Box<[CellValue]>) -> Self {
|
||||
Self {
|
||||
chunk,
|
||||
attrs,
|
||||
chunk,
|
||||
state,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::{
|
|||
use crate::{
|
||||
chunk::Instruction,
|
||||
exception::{throw, Exception, Result},
|
||||
lstring::LStr,
|
||||
lstring::{LStr, LString},
|
||||
parser::ast::{BinaryOp, UnaryOp},
|
||||
symbol::{
|
||||
Symbol, SYM_CALL_STACK_OVERFLOW, SYM_INTERRUPTED, SYM_NAME_ERROR, SYM_TYPE_ERROR,
|
||||
|
@ -50,6 +50,7 @@ pub struct Vm {
|
|||
stack_max: usize,
|
||||
globals: HashMap<Symbol, Value>,
|
||||
interrupt: Arc<AtomicBool>,
|
||||
args: Vec<LString>,
|
||||
}
|
||||
|
||||
pub fn binary_op(o: BinaryOp, a: Value, b: Value) -> Result<Value> {
|
||||
|
@ -100,7 +101,9 @@ fn get_call_outcome(args: Vec<Value>) -> Result<CallOutcome> {
|
|||
let argc = args.len() - 1;
|
||||
match argc.cmp(&attrs.arity) {
|
||||
Ordering::Equal => Ok(CallOutcome::Call(args)),
|
||||
Ordering::Greater => throw!(*SYM_TYPE_ERROR, "too many arguments for function"),
|
||||
Ordering::Greater => {
|
||||
throw!(*SYM_TYPE_ERROR, "too many arguments for function {}", f)
|
||||
}
|
||||
Ordering::Less => {
|
||||
let remaining = attrs.arity - argc;
|
||||
let f = f.clone();
|
||||
|
@ -123,12 +126,13 @@ fn get_call_outcome(args: Vec<Value>) -> Result<CallOutcome> {
|
|||
}
|
||||
|
||||
impl Vm {
|
||||
pub fn new(stack_max: usize) -> Self {
|
||||
pub fn new(stack_max: usize, args: Vec<LString>) -> Self {
|
||||
Self {
|
||||
stack: Vec::with_capacity(16),
|
||||
call_stack: Vec::with_capacity(16),
|
||||
globals: HashMap::with_capacity(16),
|
||||
stack_max,
|
||||
args,
|
||||
interrupt: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
|
@ -156,6 +160,10 @@ impl Vm {
|
|||
&self.globals
|
||||
}
|
||||
|
||||
pub fn args(&self) -> &Vec<LString> {
|
||||
&self.args
|
||||
}
|
||||
|
||||
pub fn call_value(&mut self, value: Value, args: Vec<Value>) -> Result<Value> {
|
||||
self.check_interrupt()?;
|
||||
match get_call_outcome(args)? {
|
||||
|
@ -351,11 +359,9 @@ impl Vm {
|
|||
self.push(self.stack[self.stack.len() - 2].clone());
|
||||
}
|
||||
// [a0,a1...an] -> []
|
||||
I::Drop(n) => {
|
||||
for _ in 0..u32::from(n) {
|
||||
I::Drop => {
|
||||
self.pop();
|
||||
}
|
||||
}
|
||||
// [x,y] -> [y,x]
|
||||
I::Swap => {
|
||||
let len = self.stack.len();
|
||||
|
@ -476,6 +482,7 @@ impl Vm {
|
|||
}
|
||||
// [f,a0,a1...an] -> [f(a0,a1...an)]
|
||||
I::Call(n) => {
|
||||
self.check_interrupt()?;
|
||||
let n = usize::from(n);
|
||||
|
||||
let args = self.pop_n(n + 1);
|
||||
|
@ -526,6 +533,44 @@ impl Vm {
|
|||
unreachable!("already verified by calling get_call_type");
|
||||
}
|
||||
}
|
||||
// [f,a0,a1...an] -> [], return f(a0,a1...an)
|
||||
I::Tail(n) => {
|
||||
self.check_interrupt()?;
|
||||
let n = usize::from(n);
|
||||
|
||||
let args = self.pop_n(n + 1);
|
||||
|
||||
let args = match get_call_outcome(args)? {
|
||||
CallOutcome::Call(args) => args,
|
||||
CallOutcome::Partial(v) => {
|
||||
if frame.root {
|
||||
return Ok(Some(v))
|
||||
}
|
||||
self.check_interrupt()?;
|
||||
*frame = self.call_stack.pop().expect("no root frame");
|
||||
return Ok(None)
|
||||
}
|
||||
};
|
||||
|
||||
if let Value::NativeFunc(nf) = &args[0] {
|
||||
let nf = nf.clone();
|
||||
|
||||
let res = (nf.func)(self, args)?;
|
||||
|
||||
if frame.root {
|
||||
return Ok(Some(res))
|
||||
}
|
||||
|
||||
self.push(res);
|
||||
*frame = self.call_stack.pop().expect("no root frame");
|
||||
} else if let Value::Function(func) = &args[0] {
|
||||
let mut new_frame = CallFrame::new(func.clone(), args);
|
||||
new_frame.root = frame.root;
|
||||
*frame = new_frame;
|
||||
} else {
|
||||
unreachable!("already verified by calling get_call_type");
|
||||
}
|
||||
}
|
||||
// [v] -> [], return v
|
||||
I::Return if frame.root => return Ok(Some(self.pop())),
|
||||
// [v] -> [], return v
|
||||
|
|
76
talc-lang/tests/parser.rs
Normal file
76
talc-lang/tests/parser.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
use talc_lang::parser::{Lexer, TokenKind};
|
||||
|
||||
use TokenKind as T;
|
||||
|
||||
fn assert_tokens(src: &str, tokens: &[(TokenKind, &str)]) {
|
||||
let lexer = Lexer::new(src);
|
||||
for (i, tok) in lexer.enumerate() {
|
||||
let tok = if i >= tokens.len() {
|
||||
tok.expect("end of tokens")
|
||||
} else {
|
||||
tok.expect(&format!("token {} {}", tokens[i].0, tokens[i].1))
|
||||
};
|
||||
assert_eq!(tok.kind, tokens[i].0, "token kind did not match");
|
||||
assert_eq!(tok.content, tokens[i].1, "token content did not match");
|
||||
if tok.kind == TokenKind::Eof {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_error(src: &str) {
|
||||
let lexer = Lexer::new(src);
|
||||
for tok in lexer {
|
||||
match tok {
|
||||
Err(_) => return,
|
||||
Ok(t) if t.kind == T::Eof => break,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
panic!("expected error in source '{}'", src)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_literals() {
|
||||
let src = "1 100 98_765_432 0x0 0x551 0x_12_34 0xabcABC 0o127 0b01100110 0s012345";
|
||||
let tokens = vec![
|
||||
(T::Integer, "1"),
|
||||
(T::Integer, "100"),
|
||||
(T::Integer, "98_765_432"),
|
||||
(T::Integer, "0x0"),
|
||||
(T::Integer, "0x551"),
|
||||
(T::Integer, "0x_12_34"),
|
||||
(T::Integer, "0xabcABC"),
|
||||
(T::Integer, "0o127"),
|
||||
(T::Integer, "0b01100110"),
|
||||
(T::Integer, "0s012345"),
|
||||
(T::Eof, ""),
|
||||
];
|
||||
assert_tokens(src, &tokens);
|
||||
|
||||
assert_error("0m123");
|
||||
assert_error("55p");
|
||||
assert_error("0xabcdefg");
|
||||
assert_error("0o178");
|
||||
assert_error("0s156");
|
||||
assert_error("0b012");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_literals() {
|
||||
let src = "1. 1.0 1e2 1.e2 1.0e2 1e+2 1.e+2 1.0e+2 1e-2 1___2_3_e+_5__";
|
||||
let tokens = vec![
|
||||
(T::Float, "1."),
|
||||
(T::Float, "1.0"),
|
||||
(T::Float, "1e2"),
|
||||
(T::Float, "1.e2"),
|
||||
(T::Float, "1.0e2"),
|
||||
(T::Float, "1e+2"),
|
||||
(T::Float, "1.e+2"),
|
||||
(T::Float, "1.0e+2"),
|
||||
(T::Float, "1e-2"),
|
||||
(T::Float, "1___2_3_e+_5__"),
|
||||
(T::Eof, ""),
|
||||
];
|
||||
assert_tokens(src, &tokens);
|
||||
}
|
130
talc-lang/tests/vm.rs
Normal file
130
talc-lang/tests/vm.rs
Normal file
|
@ -0,0 +1,130 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use talc_lang::{compiler::compile, optimize, parser, value::Value, Vm};
|
||||
|
||||
fn assert_eval(src: &str, value: Value) {
|
||||
let mut ex = parser::parse(src).expect(&format!("failed to parse expression"));
|
||||
optimize(&mut ex);
|
||||
let f = compile(&ex, None).expect("failed to compile expression");
|
||||
let f = Rc::new(f);
|
||||
let mut vm = Vm::new(16, Vec::new());
|
||||
let res = vm
|
||||
.run_function(f.clone(), vec![f.into()])
|
||||
.expect("vm produced an exception");
|
||||
|
||||
assert_eq!(
|
||||
res.get_type().name(),
|
||||
value.get_type().name(),
|
||||
"result and target types differ"
|
||||
);
|
||||
assert_eq!(res, value, "result and target differ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope() {
|
||||
assert_eval(
|
||||
"
|
||||
var x = 7
|
||||
var y = 10
|
||||
do
|
||||
var x = 200
|
||||
y = 100
|
||||
end
|
||||
x + y
|
||||
",
|
||||
Value::Int(7 + 100),
|
||||
);
|
||||
assert_eval(
|
||||
"
|
||||
var cond = true
|
||||
var z = 2
|
||||
if cond then var z = 5 else var z = 6 end
|
||||
z
|
||||
",
|
||||
Value::Int(2),
|
||||
);
|
||||
assert_eval(
|
||||
"
|
||||
var i = 55
|
||||
var j = 66
|
||||
for i in 0..10 do
|
||||
j = i
|
||||
end
|
||||
i + j
|
||||
",
|
||||
Value::Int(55 + 9),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forloop() {
|
||||
assert_eval(
|
||||
"
|
||||
sum = 0
|
||||
for i in 0..5 do
|
||||
for j in 0..10 do
|
||||
sum += i*j
|
||||
end
|
||||
end
|
||||
sum
|
||||
",
|
||||
Value::Int(45 * 10),
|
||||
);
|
||||
assert_eval(
|
||||
"
|
||||
map = {a=3, b=2, c=4, d=7}
|
||||
prod = 1
|
||||
for k in map do
|
||||
prod *= map[k]
|
||||
end
|
||||
prod
|
||||
",
|
||||
Value::Int(3 * 2 * 4 * 7),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closures() {
|
||||
assert_eval(
|
||||
"
|
||||
var x = 2
|
||||
next = \\-> do x = x * 2 + 1 end
|
||||
next() + next() + next()
|
||||
",
|
||||
Value::Int(5 + 11 + 23),
|
||||
);
|
||||
assert_eval(
|
||||
"
|
||||
var x = 0
|
||||
fn outer(n) do
|
||||
fn inner() do
|
||||
x += n
|
||||
n += 1
|
||||
x
|
||||
end
|
||||
end
|
||||
|
||||
var f = outer(2)
|
||||
var g = outer(6)
|
||||
f() + f() + g() + g() + f()
|
||||
",
|
||||
Value::Int(2 + 5 + 11 + 18 + 22),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tailcall() {
|
||||
assert_eval(
|
||||
"
|
||||
fn test(n, a) do
|
||||
if n <= 0 then
|
||||
a
|
||||
else
|
||||
self(n-1, n+a)
|
||||
end
|
||||
end
|
||||
test(24, 0)
|
||||
",
|
||||
Value::Int(300),
|
||||
) // = 24 + 23 + ... + 1
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "talc-macros"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "talc-std"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -15,19 +15,13 @@ pub fn throw(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
Value::Symbol(ty) => Exception::new(ty),
|
||||
Value::List(l) => match l.borrow().as_slice() {
|
||||
[Value::Symbol(ty)] | [Value::Symbol(ty), Value::Nil] => Exception::new(*ty),
|
||||
[Value::Symbol(ty), Value::Nil, data] => {
|
||||
Exception::new_with_data(*ty, data.clone())
|
||||
}
|
||||
[Value::Symbol(ty), Value::String(s)] => {
|
||||
Exception::new_with_msg(*ty, s.clone())
|
||||
}
|
||||
[Value::Symbol(ty), Value::String(s), data] => {
|
||||
Exception::new_with_msg_data(*ty, s.clone(), data.clone())
|
||||
}
|
||||
[] | [_] | [_, _] | [_, _, _] => {
|
||||
[] | [_] | [_, _] => {
|
||||
throw!(*SYM_TYPE_ERROR, "wrong argument for throw")
|
||||
}
|
||||
[_, _, _, _, ..] => throw!(
|
||||
[_, _, _, ..] => throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"too many elements in list argument for throw"
|
||||
),
|
||||
|
|
|
@ -451,7 +451,7 @@ pub fn tcp_connect(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
}
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
#[native_func(2)]
|
||||
pub fn tcp_connect_timeout(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, addr, timeout] = unpack_args!(args);
|
||||
let Value::String(addr) = addr else {
|
||||
|
|
|
@ -45,6 +45,33 @@ pub fn println(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
Ok(Value::Nil)
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn eprint(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let res = match &args[1] {
|
||||
Value::String(s) => std::io::stderr().write_all(s.as_bytes()),
|
||||
v => write!(std::io::stderr(), "{v}"),
|
||||
};
|
||||
if let Err(e) = res {
|
||||
throw!(*SYM_IO_ERROR, "{e}")
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn eprintln(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let res = match &args[1] {
|
||||
Value::String(s) => std::io::stderr().write_all(s.as_bytes()),
|
||||
v => write!(std::io::stderr(), "{v}"),
|
||||
};
|
||||
if let Err(e) = res {
|
||||
throw!(*SYM_IO_ERROR, "{e}")
|
||||
}
|
||||
if let Err(e) = std::io::stderr().write_all(b"\n") {
|
||||
throw!(*SYM_IO_ERROR, "{e}")
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
|
||||
#[native_func(0)]
|
||||
pub fn readln(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
|
||||
let mut buf = Vec::new();
|
||||
|
@ -154,9 +181,46 @@ pub fn delenv(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
Ok(Value::Nil)
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn arg(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, n] = unpack_args!(args);
|
||||
let Value::Int(n) = n else {
|
||||
throw!(*SYM_TYPE_ERROR, "arg expected integer, got {n:#}")
|
||||
};
|
||||
let cmd_args = vm.args();
|
||||
if n < 0 || (n as usize) >= cmd_args.len() {
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"arg number {} out of range for {} arguments",
|
||||
n,
|
||||
cmd_args.len()
|
||||
)
|
||||
}
|
||||
Ok(Value::from(cmd_args[n as usize].clone()))
|
||||
}
|
||||
|
||||
#[native_func(0)]
|
||||
pub fn argc(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_] = unpack_args!(args);
|
||||
Ok(Value::from(vm.args().len() as i64))
|
||||
}
|
||||
|
||||
#[native_func(0)]
|
||||
pub fn args(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_] = unpack_args!(args);
|
||||
let cmd_args: Vec<Value> = vm
|
||||
.args()
|
||||
.iter()
|
||||
.map(|v| Value::from(v.to_owned()))
|
||||
.collect();
|
||||
Ok(Value::from(cmd_args))
|
||||
}
|
||||
|
||||
pub fn load(vm: &mut Vm) {
|
||||
vm.set_global_name("print", print().into());
|
||||
vm.set_global_name("println", println().into());
|
||||
vm.set_global_name("eprint", eprint().into());
|
||||
vm.set_global_name("eprintln", eprintln().into());
|
||||
vm.set_global_name("readln", readln().into());
|
||||
|
||||
vm.set_global_name("time", time().into());
|
||||
|
@ -166,4 +230,8 @@ pub fn load(vm: &mut Vm) {
|
|||
vm.set_global_name("env", env().into());
|
||||
vm.set_global_name("setenv", setenv().into());
|
||||
vm.set_global_name("delenv", delenv().into());
|
||||
|
||||
vm.set_global_name("arg", arg().into());
|
||||
vm.set_global_name("argc", argc().into());
|
||||
vm.set_global_name("args", args().into());
|
||||
}
|
||||
|
|
|
@ -317,14 +317,19 @@ pub fn compile(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
"compile: argument must be a string, found {src:#}"
|
||||
)
|
||||
};
|
||||
|
||||
let src = src.to_str().map_err(|e| {
|
||||
exception!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"compile: argument must be valid unicode ({e})"
|
||||
)
|
||||
})?;
|
||||
|
||||
let ast = talc_lang::parser::parse(src)
|
||||
.map_err(|e| exception!(symbol!("parse_error"), "{e}"))?;
|
||||
let func = talc_lang::compiler::compile(&ast, None);
|
||||
|
||||
let func = talc_lang::compiler::compile(&ast, None)
|
||||
.map_err(|e| exception!(symbol!("compile_error"), "{e}"))?;
|
||||
|
||||
Ok(func.into())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue