talc 0.2.1

This commit is contained in:
trimill 2024-11-12 15:40:51 -05:00
parent 00fa1f1d7c
commit a82a5fa1c6
30 changed files with 1444 additions and 404 deletions

21
Cargo.lock generated
View file

@ -177,9 +177,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.161" version = "0.2.162"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
@ -350,9 +350,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.4.8" version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -367,9 +367,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.39" version = "0.38.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
@ -419,10 +419,11 @@ dependencies = [
[[package]] [[package]]
name = "talc-bin" name = "talc-bin"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"clap", "clap",
"ctrlc", "ctrlc",
"lazy_static",
"rustyline", "rustyline",
"talc-lang", "talc-lang",
"talc-std", "talc-std",
@ -430,7 +431,7 @@ dependencies = [
[[package]] [[package]]
name = "talc-lang" name = "talc-lang"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"num-complex", "num-complex",
@ -441,7 +442,7 @@ dependencies = [
[[package]] [[package]]
name = "talc-macros" name = "talc-macros"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn",
@ -449,7 +450,7 @@ dependencies = [
[[package]] [[package]]
name = "talc-std" name = "talc-std"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"rand", "rand",

View file

@ -2,6 +2,8 @@
members = ["talc-lang", "talc-bin", "talc-std", "talc-macros"] members = ["talc-lang", "talc-bin", "talc-std", "talc-macros"]
resolver = "2" resolver = "2"
[profile.release]
[profile.release-opt] [profile.release-opt]
inherits = "release" inherits = "release"
lto = "fat" lto = "fat"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,6 +1,6 @@
[package] [package]
name = "talc-bin" name = "talc-bin"
version = "0.2.0" version = "0.2.1"
edition = "2021" edition = "2021"
[[bin]] [[bin]]
@ -13,3 +13,4 @@ talc-std = { path = "../talc-std" }
rustyline = "14.0" rustyline = "14.0"
clap = { version = "4.5", features = ["std", "help", "usage", "derive", "error-context"], default-features = false } clap = { version = "4.5", features = ["std", "help", "usage", "derive", "error-context"], default-features = false }
ctrlc = "3.4" ctrlc = "3.4"
lazy_static = "1.5"

View file

@ -1,7 +1,12 @@
use clap::{ColorChoice, Parser}; use clap::{ColorChoice, Parser};
use std::{path::PathBuf, process::ExitCode, rc::Rc}; use std::{path::PathBuf, process::ExitCode, rc::Rc};
use talc_lang::{ 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; mod helper;
@ -10,31 +15,40 @@ mod repl;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
struct Args { struct Args {
/// file to run /// Start the repl
file: Option<PathBuf>,
/// start the repl
#[arg(short, long)] #[arg(short, long)]
repl: bool, repl: bool,
/// show disassembled bytecode /// Show disassembled bytecode
#[arg(short, long)] #[arg(short, long)]
disasm: bool, 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)] #[arg(short = 'H', long)]
histfile: Option<PathBuf>, histfile: Option<PathBuf>,
/// enable or disable color /// enable or disable color
#[arg(short, long, default_value = "auto")] #[arg(short, long, default_value = "auto")]
color: ColorChoice, color: ColorChoice,
// file to run and arguments to pass
#[arg()]
args: Vec<LString>,
} }
fn exec(name: Symbol, src: &str, args: &Args) -> ExitCode { 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); talc_std::load_all(&mut vm);
let ex = match parser::parse(src) { let mut ex = match parser::parse(src) {
Ok(ex) => ex, Ok(ex) => ex,
Err(e) => { Err(e) => {
eprintln!("Error: {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 args.disasm {
if let Err(e) = disasm_recursive(&func, &mut std::io::stderr()) { 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 {
eprintln!("{e}"); let f = std::fs::File::options()
ExitCode::FAILURE .write(true)
} else { .truncate(true)
ExitCode::SUCCESS .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
}
Ok(Value::Bool(false)) => ExitCode::FAILURE,
Ok(Value::Int(n)) => ExitCode::from(n as u8),
_ => ExitCode::SUCCESS,
} }
} }
fn main() -> ExitCode { fn main() -> ExitCode {
let args = Args::parse(); 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) 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) { match std::fs::read_to_string(file.to_os_str()) {
Ok(s) => exec(Symbol::get(file.as_os_str()), &s, &args), Ok(s) => exec(Symbol::get(file), &s, &args),
Err(e) => { Err(e) => {
eprintln!("Error: {e}"); eprintln!("Error opening source file: {e}");
ExitCode::FAILURE ExitCode::FAILURE
} }
} }

View file

@ -8,14 +8,20 @@ use rustyline::{
}; };
use talc_lang::{ use talc_lang::{
compiler::compile_repl, compiler::compile_repl,
parser, lstr, optimize, parser,
symbol::Symbol, symbol::{symbol, Symbol},
value::{function::disasm_recursive, Value}, value::{function::disasm_recursive, Value},
Vm, Vm,
}; };
use crate::{helper::TalcHelper, Args}; 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)] #[derive(Clone, Copy, Default)]
pub struct ReplColors { pub struct ReplColors {
pub reset: &'static str, 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 { pub fn repl(args: &Args) -> ExitCode {
if args.show_ast {
eprintln!("AST printing enabled");
}
if args.disasm { if args.disasm {
eprintln!("input disassembly enabled"); eprintln!("input disassembly enabled");
} }
let mut compiler_globals = Vec::new(); let mut globals = Vec::new();
let mut vm = Vm::new(256); let mut vm = Vm::new(256, args.args.clone());
talc_std::load_all(&mut vm); talc_std::load_all(&mut vm);
let interrupt = vm.get_interrupt(); let interrupt = vm.get_interrupt();
@ -96,16 +171,20 @@ pub fn repl(args: &Args) -> ExitCode {
eprintln!("Warn: couldn't set ctrl+c handler: {e}"); 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); 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) { let mut rl = match init_rustyline(args) {
Ok(rl) => rl, Ok(rl) => rl,
Err(e) => return e, Err(e) => return e,
@ -115,6 +194,10 @@ pub fn repl(args: &Args) -> ExitCode {
rl.set_helper(Some(TalcHelper::new(vm.clone()))); rl.set_helper(Some(TalcHelper::new(vm.clone())));
if let Some(src) = init_src {
exec_line(vm.clone(), &src, args, &c, &mut globals);
}
loop { loop {
if let Some(f) = &args.histfile { if let Some(f) = &args.histfile {
let _ = rl.save_history(f); let _ = rl.save_history(f);
@ -130,37 +213,6 @@ pub fn repl(args: &Args) -> ExitCode {
} }
}; };
let ex = match parser::parse(&line) { exec_line(vm.clone(), &line, args, &c, &mut globals);
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),
}
} }
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "talc-lang" name = "talc-lang"
version = "0.2.0" version = "0.2.1"
edition = "2021" edition = "2021"
[dependencies] [dependencies]

View file

@ -4,7 +4,7 @@ use crate::{
value::Value, value::Value,
}; };
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Arg24([u8; 3]); pub struct Arg24([u8; 3]);
impl Arg24 { impl Arg24 {
@ -97,36 +97,36 @@ impl TryFrom<Arg24> for Symbol {
} }
#[repr(u8, C, align(4))] #[repr(u8, C, align(4))]
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Instruction { pub enum Instruction {
#[default] #[default]
Nop, // do nothing Nop,
LoadLocal(Arg24), // push nth local onto stack LoadLocal(Arg24),
StoreLocal(Arg24), // pop stack into nth local StoreLocal(Arg24),
NewLocal, // pop stack into a new local NewLocal,
DropLocal(Arg24), // remove last n locals DropLocal(Arg24),
LoadGlobal(Arg24), // load global by id LoadGlobal(Arg24),
StoreGlobal(Arg24), // store global by id StoreGlobal(Arg24),
CloseOver(Arg24), // load nth local and convert to cell, write back a copy CloseOver(Arg24),
Closure(Arg24), // load constant function and fill state from stack Closure(Arg24),
LoadUpvalue(Arg24), // load LoadUpvalue(Arg24),
StoreUpvalue(Arg24), // store a cell from closure state to new local StoreUpvalue(Arg24),
ContinueUpvalue(Arg24), // ContinueUpvalue(Arg24),
LoadClosedLocal(Arg24), // load through cell in nth local LoadClosedLocal(Arg24),
StoreClosedLocal(Arg24), // store through cell in nth local StoreClosedLocal(Arg24),
Const(Arg24), // push nth constant Const(Arg24),
Int(Arg24), // push integer Int(Arg24),
Symbol(Arg24), // push symbol Symbol(Arg24),
Bool(bool), // push boolean Bool(bool),
Nil, // push nil Nil,
Dup, Dup,
DupTwo, DupTwo,
Drop(Arg24), Drop,
Swap, Swap,
UnaryOp(UnaryOp), UnaryOp(UnaryOp),
@ -151,6 +151,7 @@ pub enum Instruction {
EndTry, EndTry,
Call(u8), Call(u8),
Tail(u8),
Return, Return,
} }
@ -161,7 +162,7 @@ impl std::fmt::Display for Instruction {
Self::LoadLocal(a) => write!(f, "load ${}", usize::from(a)), Self::LoadLocal(a) => write!(f, "load ${}", usize::from(a)),
Self::StoreLocal(a) => write!(f, "store ${}", usize::from(a)), Self::StoreLocal(a) => write!(f, "store ${}", usize::from(a)),
Self::NewLocal => write!(f, "newlocal"), 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!( Self::LoadGlobal(s) => write!(
f, f,
"loadglobal {}", "loadglobal {}",
@ -190,7 +191,7 @@ impl std::fmt::Display for Instruction {
Self::Nil => write!(f, "nil"), Self::Nil => write!(f, "nil"),
Self::Dup => write!(f, "dup"), Self::Dup => write!(f, "dup"),
Self::DupTwo => write!(f, "duptwo"), 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::Swap => write!(f, "swap"),
Self::UnaryOp(o) => write!(f, "unary {o:?}"), Self::UnaryOp(o) => write!(f, "unary {o:?}"),
Self::BinaryOp(o) => write!(f, "binary {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::BeginTry(t) => write!(f, "begintry #{}", usize::from(t)),
Self::EndTry => write!(f, "endtry"), Self::EndTry => write!(f, "endtry"),
Self::Call(n) => write!(f, "call #{n}"), Self::Call(n) => write!(f, "call #{n}"),
Self::Tail(n) => write!(f, "tail #{n}"),
Self::Return => write!(f, "return"), Self::Return => write!(f, "return"),
} }
} }

View file

@ -1,12 +1,37 @@
use std::collections::{BTreeMap, HashMap}; use core::fmt;
use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use crate::chunk::{Arg24, Catch, Chunk, Instruction as I}; use crate::chunk::{Arg24, Catch, Chunk, Instruction as I};
use crate::parser::ast::{BinaryOp, CatchBlock, Expr, ExprKind, LValue, LValueKind}; use crate::parser::ast::{BinaryOp, CatchBlock, Expr, ExprKind, LValue, LValueKind};
use crate::parser::Pos;
use crate::symbol::{Symbol, SYM_SELF}; use crate::symbol::{Symbol, SYM_SELF};
use crate::throw;
use crate::value::function::{FuncAttrs, Function}; use crate::value::function::{FuncAttrs, Function};
use crate::value::Value; use crate::value::Value;
#[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 { enum ResolveOutcome {
Var(VarKind), Var(VarKind),
InParent, InParent,
@ -33,27 +58,39 @@ enum CompilerMode {
Module, Module,
} }
struct BreakFrame {
// ip at beginning of frame
start: usize,
// indices of jumps to reposition to end
end_jumps: Vec<usize>,
}
struct Compiler<'a> { struct Compiler<'a> {
mode: CompilerMode, mode: CompilerMode,
parent: Option<&'a Compiler<'a>>, parent: Option<&'a Compiler<'a>>,
// function properties
chunk: Chunk, chunk: Chunk,
attrs: FuncAttrs, attrs: FuncAttrs,
// variables
scope: HashMap<Symbol, Var>, scope: HashMap<Symbol, Var>,
shadowed: Vec<(Symbol, Option<Var>)>, shadowed: Vec<(Symbol, Option<Var>)>,
closes: BTreeMap<Symbol, usize>, closes: Vec<(Symbol, usize)>,
local_count: 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); let mut comp = Compiler::new_module(name, None);
comp.expr(expr); comp.expr(expr)?;
comp.finish() 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); let mut comp = Compiler::new_repl(globals);
comp.expr(expr); comp.expr(expr)?;
comp.finish_repl() Ok(comp.finish_repl())
} }
impl<'a> Default for Compiler<'a> { impl<'a> Default for Compiler<'a> {
@ -74,7 +111,9 @@ impl<'a> Default for Compiler<'a> {
scope, scope,
shadowed: Vec::new(), shadowed: Vec::new(),
local_count: 1, 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()) 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); self.emit(I::Return);
// TODO closure // TODO closure
( (
@ -161,54 +200,47 @@ impl<'a> Compiler<'a> {
self.chunk.add_instr(instr) self.chunk.add_instr(instr)
} }
fn emit_discard(&mut self, mut n: usize) { fn emit_discard(&mut self) {
while n > 0 { if self.ip() <= self.drop_barrier {
let instrs = &mut self.chunk.instrs; self.emit(I::Drop);
return
// dup followed by store: remove the dup
if instrs.len() >= 2
&& matches!(instrs.get(instrs.len() - 2), Some(I::Dup))
&& matches!(
instrs.last(),
Some(
I::NewLocal
| I::StoreLocal(_) | I::StoreGlobal(_)
| I::StoreClosedLocal(_)
| I::StoreUpvalue(_)
)
) {
// 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
} }
if n > 0 {
self.emit(I::Drop(Arg24::from_usize(n))); let instrs = &mut self.chunk.instrs;
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::StoreClosedLocal(_)
| 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);
}
}
_ => {
self.emit(I::Drop);
}
} }
} }
@ -220,6 +252,49 @@ impl<'a> Compiler<'a> {
self.chunk.instrs[n] = new; 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] #[must_use]
fn begin_scope(&mut self) -> usize { fn begin_scope(&mut self) -> usize {
self.shadowed.len() self.shadowed.len()
@ -230,10 +305,13 @@ impl<'a> Compiler<'a> {
while self.shadowed.len() > scope { while self.shadowed.len() > scope {
let (name, var) = self.shadowed.pop().expect("scope bad"); let (name, var) = self.shadowed.pop().expect("scope bad");
if let Some(var) = var { if let Some(in_var) = self.scope.get(&name) {
if var.kind != VarKind::Global { if in_var.kind != VarKind::Global {
locals += 1; locals += 1;
} }
}
if let Some(var) = var {
self.scope.insert(name, var); self.scope.insert(name, var);
} else { } else {
self.scope.remove(&name); self.scope.remove(&name);
@ -257,13 +335,14 @@ impl<'a> Compiler<'a> {
let Some(parent) = self.parent else { let Some(parent) = self.parent else {
return ResolveOutcome::None return ResolveOutcome::None
}; };
if let ResolveOutcome::None = parent.resolve_name(name) { match parent.resolve_name(name) {
return ResolveOutcome::None 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) { match self.resolve_name(name) {
ResolveOutcome::Var(VarKind::Local(n)) => { ResolveOutcome::Var(VarKind::Local(n)) => {
self.emit(I::LoadLocal(Arg24::from_usize(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))); self.emit(I::LoadClosedLocal(Arg24::from_usize(n)));
} }
ResolveOutcome::InParent => { ResolveOutcome::InParent => {
let n = match self.closes.get(&name) { let n = match self.closes.iter().position(|(n, _)| *n == name) {
Some(n) => *n, Some(n) => n,
None => { None => {
let n = self.closes.len(); let n = self.closes.len();
self.closes.insert(name, n); self.closes.push((name, n));
n n
} }
}; };
@ -286,6 +365,7 @@ impl<'a> Compiler<'a> {
self.emit(I::LoadGlobal(Arg24::from_symbol(name))); self.emit(I::LoadGlobal(Arg24::from_symbol(name)));
} }
} }
Ok(())
} }
fn declare_local(&mut self, name: Symbol) -> usize { fn declare_local(&mut self, name: Symbol) -> usize {
@ -328,11 +408,11 @@ impl<'a> Compiler<'a> {
self.emit(I::StoreClosedLocal(Arg24::from_usize(n))); self.emit(I::StoreClosedLocal(Arg24::from_usize(n)));
} }
ResolveOutcome::InParent => { ResolveOutcome::InParent => {
let n = match self.closes.get(&name) { let n = match self.closes.iter().position(|(n, _)| *n == name) {
Some(n) => *n, Some(n) => n,
None => { None => {
let n = self.closes.len(); let n = self.closes.len();
self.closes.insert(name, n); self.closes.push((name, n));
n n
} }
}; };
@ -354,8 +434,8 @@ impl<'a> Compiler<'a> {
// Expressions // Expressions
// //
fn expr(&mut self, e: &Expr) { fn expr(&mut self, e: &Expr) -> Result<()> {
let Expr { kind, .. } = e; let Expr { kind, span } = e;
match kind { match kind {
ExprKind::Block(xs) if xs.is_empty() => { ExprKind::Block(xs) if xs.is_empty() => {
self.emit(I::Nil); self.emit(I::Nil);
@ -363,33 +443,41 @@ impl<'a> Compiler<'a> {
ExprKind::Block(xs) => { ExprKind::Block(xs) => {
let scope = self.begin_scope(); let scope = self.begin_scope();
for x in &xs[0..xs.len() - 1] { for x in &xs[0..xs.len() - 1] {
self.expr(x); self.expr(x)?;
self.emit_discard(1); self.emit_discard();
} }
self.expr(&xs[xs.len() - 1]); self.expr(&xs[xs.len() - 1])?;
self.end_scope(scope); self.end_scope(scope);
} }
ExprKind::Literal(v) => self.expr_literal(v), 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) => { ExprKind::UnaryOp(o, a) => {
self.expr(a); self.expr(a)?;
self.emit(I::UnaryOp(*o)); self.emit(I::UnaryOp(*o));
} }
ExprKind::BinaryOp(o, a, b) => { ExprKind::BinaryOp(o, a, b) => {
self.expr(a); self.expr(a)?;
self.expr(b); self.expr(b)?;
self.emit(I::BinaryOp(*o)); 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) => { 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.emit(I::Dup);
self.assign_local(*name); self.assign_local(*name);
} }
ExprKind::AssignGlobal(name, a) => { ExprKind::AssignGlobal(name, a) => {
self.expr(a); if let Some(a) = a {
self.emit(I::Dup); self.expr(a)?;
self.assign_global(*name); self.emit(I::Dup);
self.assign_global(*name);
} else {
self.declare_global(*name);
}
} }
ExprKind::List(xs) if xs.is_empty() => { ExprKind::List(xs) if xs.is_empty() => {
self.emit(I::NewList(0)); self.emit(I::NewList(0));
@ -398,7 +486,7 @@ impl<'a> Compiler<'a> {
let mut first = true; let mut first = true;
for chunk in xs.chunks(16) { for chunk in xs.chunks(16) {
for e in chunk { for e in chunk {
self.expr(e); self.expr(e)?;
} }
if first { if first {
self.emit(I::NewList(chunk.len() as u8)); self.emit(I::NewList(chunk.len() as u8));
@ -415,8 +503,8 @@ impl<'a> Compiler<'a> {
let mut first = true; let mut first = true;
for chunk in xs.chunks(8) { for chunk in xs.chunks(8) {
for (k, v) in chunk { for (k, v) in chunk {
self.expr(k); self.expr(k)?;
self.expr(v); self.expr(v)?;
} }
if first { if first {
self.emit(I::NewTable(chunk.len() as u8)); self.emit(I::NewTable(chunk.len() as u8));
@ -427,91 +515,152 @@ impl<'a> Compiler<'a> {
} }
} }
ExprKind::Index(ct, idx) => { ExprKind::Index(ct, idx) => {
self.expr(ct); self.expr(ct)?;
self.expr(idx); self.expr(idx)?;
self.emit(I::Index); self.emit(I::Index);
} }
ExprKind::FnCall(f, args) => { ExprKind::FnCall(f, args) => {
self.expr(f); self.expr(f)?;
for a in args { for a in args {
self.expr(a); self.expr(a)?;
} }
self.emit(I::Call(args.len() as u8)); 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) => { ExprKind::AssocFnCall(o, f, args) => {
self.expr(o); self.expr(o)?;
self.emit(I::Dup); self.emit(I::Dup);
self.emit(I::Symbol(Arg24::from_symbol(*f))); self.emit(I::Symbol(Arg24::from_symbol(*f)));
self.emit(I::Index); self.emit(I::Index);
self.emit(I::Swap); self.emit(I::Swap);
for a in args { for a in args {
self.expr(a); self.expr(a)?;
} }
self.emit(I::Call((args.len() + 1) as u8)); self.emit(I::Call((args.len() + 1) as u8));
} }
ExprKind::Return(e) => { ExprKind::AssocTailCall(o, f, args) => {
self.expr(e); self.expr(o)?;
self.emit(I::Return); self.emit(I::Dup);
} self.emit(I::Symbol(Arg24::from_symbol(*f)));
ExprKind::Pipe(a, f) => { self.emit(I::Index);
self.expr(a);
self.expr(f);
self.emit(I::Swap); self.emit(I::Swap);
self.emit(I::Call(1)); for a in args {
self.expr(a)?;
}
self.emit(I::Tail((args.len() + 1) as u8));
} }
ExprKind::Lambda(args, body) => self.expr_fndef(None, args, body), ExprKind::Return(e) => {
ExprKind::FnDef(name, args, body) => self.expr_fndef(*name, args, body), if let Some(e) = e {
ExprKind::And(a, b) => { self.expr(e)?;
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())));
}
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);
} else { } else {
self.emit(I::Nil); 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()))); self.update_instr(j2, I::Jump(Arg24::from_usize(self.ip())));
} }
ExprKind::While(cond, body) => { ExprKind::While(cond, body) => self.expr_while(cond, body)?,
let start = self.ip(); ExprKind::For(name, iter, body) => self.expr_for(*name, iter, body)?,
self.expr(cond); ExprKind::Try(body, catches) => self.expr_try(body, catches)?,
let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0)));
self.expr(body);
self.emit_discard(1);
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),
} }
Ok(())
} }
fn expr_try(&mut self, body: &Expr, catch_blocks: &[CatchBlock]) { fn expr_infinite_loop(&mut self, body: &Expr) -> Result<()> {
let start = self.ip();
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();
self.emit(I::Jump(Arg24::from_usize(start)));
self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip())));
self.emit(I::Nil);
self.end_break_frame();
Ok(())
}
fn expr_try(&mut self, body: &Expr, catch_blocks: &[CatchBlock]) -> Result<()> {
let (idx, mut table) = self.chunk.begin_try_table(self.local_count); let (idx, mut table) = self.chunk.begin_try_table(self.local_count);
self.emit(I::BeginTry(Arg24::from_usize(idx))); self.emit(I::BeginTry(Arg24::from_usize(idx)));
self.expr(body); self.expr(body)?;
self.emit(I::EndTry); self.emit(I::EndTry);
let body_end_addr = self.emit(I::Jump(Arg24::from_usize(0))); let body_end_addr = self.emit(I::Jump(Arg24::from_usize(0)));
let mut catch_end_addrs = Vec::new(); let mut catch_end_addrs = Vec::new();
@ -527,10 +676,10 @@ impl<'a> Compiler<'a> {
if let Some(name) = catch_block.name { if let Some(name) = catch_block.name {
self.assign_local(name); self.assign_local(name);
} else { } else {
self.emit_discard(1); self.emit_discard();
} }
self.expr(&catch_block.body); self.expr(&catch_block.body)?;
self.end_scope(scope); self.end_scope(scope);
let end_addr = self.emit(I::Jump(Arg24::from_usize(0))); 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); 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 // load iterable and convert to iterator
self.expr(iter); self.expr(iter)?;
self.emit(I::IterBegin); self.emit(I::IterBegin);
// declare loop variable // declare loop variable
@ -558,6 +709,7 @@ impl<'a> Compiler<'a> {
// begin loop // begin loop
let start = self.ip(); let start = self.ip();
self.begin_break_frame();
// call iterator and jump if nil, otherwise store // call iterator and jump if nil, otherwise store
self.emit(I::Dup); self.emit(I::Dup);
@ -566,21 +718,30 @@ impl<'a> Compiler<'a> {
self.store_var(name); self.store_var(name);
// body // body
self.expr(body); self.expr(body)?;
self.emit_discard(1); self.emit_discard();
// end loop // end loop
self.emit(I::Jump(Arg24::from_usize(start))); self.emit(I::Jump(Arg24::from_usize(start)));
self.update_instr(j1, I::IterTest(Arg24::from_usize(self.ip()))); self.update_instr(j1, I::IterTest(Arg24::from_usize(self.ip())));
self.end_scope(scope); self.end_scope(scope);
self.emit(I::Nil); 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); let mut inner = self.new_function(name, args);
inner.parent = Some(self); inner.parent = Some(self);
inner.expr(body); inner.expr(body)?;
let (func, closes) = inner.finish_inner(); let (func, closes) = inner.finish_inner();
let func_const = self.add_const(func.into()); let func_const = self.add_const(func.into());
@ -600,7 +761,7 @@ impl<'a> Compiler<'a> {
} }
ResolveOutcome::InParent => { ResolveOutcome::InParent => {
let n = self.closes.len(); let n = self.closes.len();
self.closes.insert(name, n); self.closes.push((name, n));
self.emit(I::ContinueUpvalue(Arg24::from_usize(n))); self.emit(I::ContinueUpvalue(Arg24::from_usize(n)));
} }
ResolveOutcome::None | ResolveOutcome::Var(VarKind::Global) => { ResolveOutcome::None | ResolveOutcome::Var(VarKind::Global) => {
@ -619,6 +780,8 @@ impl<'a> Compiler<'a> {
self.emit(I::Dup); self.emit(I::Dup);
self.store_var(name); self.store_var(name);
} }
Ok(())
} }
fn expr_literal(&mut self, val: &Value) { 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) { match (&lv.kind, o) {
(LValueKind::Ident(i), None) => { (LValueKind::Ident(i), None) => {
self.expr(a); self.expr(a)?;
self.emit(I::Dup); self.emit(I::Dup);
self.store_var(*i); self.store_var(*i);
} }
(LValueKind::Ident(i), Some(o)) => { (LValueKind::Ident(i), Some(o)) => {
self.load_var(*i); self.load_var(*i)?;
self.expr(a); self.expr(a)?;
self.emit(I::BinaryOp(o)); self.emit(I::BinaryOp(o));
self.emit(I::Dup); self.emit(I::Dup);
self.store_var(*i); self.store_var(*i);
} }
(LValueKind::Index(ct, i), None) => { (LValueKind::Index(ct, i), None) => {
self.expr(ct); self.expr(ct)?;
self.expr(i); self.expr(i)?;
self.expr(a); self.expr(a)?;
self.emit(I::StoreIndex); self.emit(I::StoreIndex);
} }
(LValueKind::Index(ct, i), Some(o)) => { (LValueKind::Index(ct, i), Some(o)) => {
self.expr(ct); self.expr(ct)?;
self.expr(i); self.expr(i)?;
self.emit(I::DupTwo); self.emit(I::DupTwo);
self.emit(I::Index); self.emit(I::Index);
self.expr(a); self.expr(a)?;
self.emit(I::BinaryOp(o)); self.emit(I::BinaryOp(o));
self.emit(I::StoreIndex); self.emit(I::StoreIndex);
} }
} }
Ok(())
} }
} }

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
lstring::LStr, lstring::LStr,
symbol::{Symbol, SYM_DATA, SYM_MSG, SYM_TYPE}, symbol::{Symbol, SYM_MSG, SYM_TYPE},
value::{HashValue, Value}, value::{HashValue, Value},
}; };
use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc}; 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 struct Exception {
pub ty: Symbol, pub ty: Symbol,
pub msg: Option<Rc<LStr>>, pub msg: Option<Rc<LStr>>,
pub data: Option<Value>,
} }
impl Exception { impl Exception {
pub fn new(ty: Symbol) -> Self { pub fn new(ty: Symbol) -> Self {
Self { Self { ty, msg: None }
ty,
msg: None,
data: None,
}
} }
pub fn new_with_msg(ty: Symbol, msg: Rc<LStr>) -> Self { pub fn new_with_msg(ty: Symbol, msg: Rc<LStr>) -> Self {
Self { Self { ty, msg: Some(msg) }
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),
}
} }
pub fn from_table(table: &Rc<RefCell<HashMap<HashValue, Value>>>) -> Option<Self> { pub fn from_table(table: &Rc<RefCell<HashMap<HashValue, Value>>>) -> Option<Self> {
let table = table.borrow(); let table = table.borrow();
let ty = table.get(&(*SYM_TYPE).into())?; let ty = table.get(&(*SYM_TYPE).into())?;
if let Value::Symbol(ty) = ty { if let Value::Symbol(ty) = ty {
let data = table.get(&(*SYM_DATA).into()).cloned();
let msg = table.get(&(*SYM_MSG).into()); let msg = table.get(&(*SYM_MSG).into());
match msg { match msg {
None => Some(Self { None => Some(Self { ty: *ty, msg: None }),
ty: *ty,
data,
msg: None,
}),
Some(Value::String(msg)) => Some(Self { Some(Value::String(msg)) => Some(Self {
ty: *ty, ty: *ty,
data,
msg: Some(msg.clone()), msg: Some(msg.clone()),
}), }),
Some(_) => None, Some(_) => None,
@ -77,9 +46,6 @@ impl Exception {
if let Some(msg) = self.msg { if let Some(msg) = self.msg {
table.insert((*SYM_MSG).into(), Value::String(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)) Rc::new(RefCell::new(table))
} }
} }

View file

@ -1,12 +1,17 @@
#![allow(clippy::mutable_key_type)] #![allow(clippy::mutable_key_type)]
#![warn(clippy::semicolon_if_nothing_returned)] #![warn(clippy::semicolon_if_nothing_returned)]
#![warn(clippy::allow_attributes)] #![warn(clippy::allow_attributes)]
#![warn(clippy::inconsistent_struct_constructor)]
#![warn(clippy::uninlined_format_args)]
pub mod chunk; pub mod chunk;
pub mod compiler; pub mod compiler;
pub mod exception; pub mod exception;
pub mod lstring; pub mod lstring;
mod optimize;
pub use optimize::optimize;
pub mod parser; pub mod parser;
pub mod serial;
pub mod symbol; pub mod symbol;
pub mod value; pub mod value;

View file

@ -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 // write
// //

229
talc-lang/src/optimize.rs Normal file
View 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);
}

View file

@ -4,7 +4,7 @@ use crate::{symbol::Symbol, value::Value};
use super::Span; use super::Span;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BinaryOp { pub enum BinaryOp {
Add, Add,
Sub, Sub,
@ -30,7 +30,7 @@ pub enum BinaryOp {
RangeIncl, RangeIncl,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UnaryOp { pub enum UnaryOp {
Neg, Neg,
Not, Not,
@ -52,8 +52,8 @@ pub enum ExprKind {
BinaryOp(BinaryOp, Box<Expr>, Box<Expr>), BinaryOp(BinaryOp, Box<Expr>, Box<Expr>),
Assign(Option<BinaryOp>, Box<LValue>, Box<Expr>), Assign(Option<BinaryOp>, Box<LValue>, Box<Expr>),
AssignVar(Symbol, Box<Expr>), AssignVar(Symbol, Option<Box<Expr>>),
AssignGlobal(Symbol, Box<Expr>), AssignGlobal(Symbol, Option<Box<Expr>>),
FnDef(Option<Symbol>, Vec<Symbol>, Box<Expr>), FnDef(Option<Symbol>, Vec<Symbol>, Box<Expr>),
Index(Box<Expr>, Box<Expr>), Index(Box<Expr>, Box<Expr>),
@ -65,7 +65,9 @@ pub enum ExprKind {
List(Vec<Expr>), List(Vec<Expr>),
Table(Vec<(Expr, Expr)>), Table(Vec<(Expr, Expr)>),
Return(Box<Expr>), Return(Option<Box<Expr>>),
Break(Option<Box<Expr>>),
Continue,
And(Box<Expr>, Box<Expr>), And(Box<Expr>, Box<Expr>),
Or(Box<Expr>, Box<Expr>), Or(Box<Expr>, Box<Expr>),
If(Box<Expr>, Box<Expr>, Option<Box<Expr>>), If(Box<Expr>, Box<Expr>, Option<Box<Expr>>),
@ -73,6 +75,10 @@ pub enum ExprKind {
For(Symbol, Box<Expr>, Box<Expr>), For(Symbol, Box<Expr>, Box<Expr>),
Lambda(Vec<Symbol>, Box<Expr>), Lambda(Vec<Symbol>, Box<Expr>),
Try(Box<Expr>, Vec<CatchBlock>), Try(Box<Expr>, Vec<CatchBlock>),
// Created by optimizer
TailCall(Box<Expr>, Vec<Expr>),
AssocTailCall(Box<Expr>, Symbol, Vec<Expr>),
} }
impl ExprKind { impl ExprKind {
@ -189,21 +195,28 @@ impl Expr {
} }
ExprKind::AssignVar(l, r) => { ExprKind::AssignVar(l, r) => {
writeln!(w, "var {}", l.name())?; 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) => { ExprKind::AssignGlobal(l, r) => {
writeln!(w, "global {}", l.name())?; 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) => { ExprKind::FnDef(n, p, b) => {
if let Some(n) = n { if let Some(n) = n {
writeln!(w, "fndef ${}", n.name())?; write!(w, "fndef ${}", n.name())?;
} else { } else {
writeln!(w, "fndef anon")?; write!(w, "fndef anon")?;
} }
for arg in p { for arg in p {
writeln!(w, " ${}", arg.name())?; write!(w, " ${}", arg.name())?;
} }
writeln!(w)?;
b.write_to(w, depth) b.write_to(w, depth)
} }
ExprKind::Index(l, r) => { ExprKind::Index(l, r) => {
@ -256,7 +269,20 @@ impl Expr {
} }
ExprKind::Return(e) => { ExprKind::Return(e) => {
writeln!(w, "return")?; 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) => { ExprKind::And(l, r) => {
writeln!(w, "and")?; writeln!(w, "and")?;
@ -303,6 +329,23 @@ impl Expr {
} }
Ok(()) 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(())
}
} }
} }
} }

View file

@ -219,8 +219,8 @@ impl<'s> Lexer<'s> {
fn invalid_char(&self, c: char) -> ParserError { fn invalid_char(&self, c: char) -> ParserError {
let span = Span::new(self.pos, self.pos.advance(c)); let span = Span::new(self.pos, self.pos.advance(c));
let msg = match c as u32 { let msg = match c as u32 {
c @ 0x00..=0x7f => format!("invalid character (codepoint 0x{:2x})", c), c @ 0x00..=0x7f => format!("invalid character (codepoint 0x{c:2x})"),
c => format!("invalid character (codepoint U+{:04x})", c), c => format!("invalid character (codepoint U+{c:04x})"),
}; };
ParserError { span, msg } ParserError { span, msg }
} }
@ -326,7 +326,7 @@ impl<'s> Lexer<'s> {
let c = self.peek()?; let c = self.peek()?;
if c == '_' || c.is_digit(radix) { if c == '_' || c.is_digit(radix) {
self.next()?; self.next()?;
} else if is_xid_start(c) { } else if is_xid_start(c) || c.is_ascii_digit() {
return self.unexpected() return self.unexpected()
} else { } else {
return self.emit(K::Integer) return self.emit(K::Integer)
@ -569,11 +569,13 @@ impl<'s> Lexer<'s> {
_ => self.emit(K::Dot), _ => self.emit(K::Dot),
}, },
':' => match self.and_peek()? { ':' => 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), _ => self.emit(K::Colon),
}, },
'0'..='9' => self.next_number(), '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.next_string(),
_ => self.unexpected(), _ => self.unexpected(),
} }

View file

@ -162,9 +162,10 @@ impl TokenKind {
matches!( matches!(
self, self,
T::Return T::Return
| T::Var | T::Global | T::Continue
| T::Fn | T::Not | T::Break | T::Var
| T::Backslash | T::Global | T::Fn
| T::Not | T::Backslash
| T::Colon | T::Minus | T::Colon | T::Minus
| T::Identifier | T::Identifier
| T::LParen | T::LBrack | T::LParen | T::LBrack
@ -569,7 +570,7 @@ impl<'s> Parser<'s> {
let lhs_span = lhs.span; let lhs_span = lhs.span;
if let Some(op) = self.peek()?.kind.assign_op() { if let Some(op) = self.peek()?.kind.assign_op() {
let Some(lval) = LValue::from_expr(lhs) else { let Some(lval) = LValue::from_expr(lhs) else {
throw!(lhs_span, "invalid lvalue for assingment") throw!(lhs_span, "invalid lvalue for assignment")
}; };
self.next()?; self.next()?;
let rhs = self.parse_decl()?; let rhs = self.parse_decl()?;
@ -582,16 +583,19 @@ impl<'s> Parser<'s> {
fn parse_var_decl(&mut self) -> Result<Expr> { fn parse_var_decl(&mut self) -> Result<Expr> {
let first = expect!(self, T::Var | T::Global); 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 { let kind = if first.kind == T::Global {
E::AssignGlobal E::AssignGlobal
} else { } else {
E::AssignVar 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> { fn parse_fn_decl(&mut self) -> Result<Expr> {
@ -624,10 +628,30 @@ impl<'s> Parser<'s> {
} }
fn parse_expr(&mut self) -> Result<Expr> { 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);
let expr = self.parse_decl()?; if let Some(tok) = tok {
let span = expr.span; match tok.kind {
Ok(E::Return(b(expr)).span(tok.span + span)) T::Return => {
if self.peek()?.kind.expr_first() {
let expr = self.parse_decl()?;
let span = expr.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 { } else {
self.parse_decl() self.parse_decl()
} }

216
talc-lang/src/serial/mod.rs Normal file
View 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(())
}
}

View file

@ -31,7 +31,6 @@ lazy_static! {
pub static ref SYM_END_ITERATION: Symbol = symbol!(end_iteration); pub static ref SYM_END_ITERATION: Symbol = symbol!(end_iteration);
pub static ref SYM_TYPE: Symbol = symbol!(type); pub static ref SYM_TYPE: Symbol = symbol!(type);
pub static ref SYM_MSG: Symbol = symbol!(msg); 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_TYPE_ERROR: Symbol = symbol!(type_error);
pub static ref SYM_VALUE_ERROR: Symbol = symbol!(value_error); pub static ref SYM_VALUE_ERROR: Symbol = symbol!(value_error);
pub static ref SYM_NAME_ERROR: Symbol = symbol!(name_error); pub static ref SYM_NAME_ERROR: Symbol = symbol!(name_error);

View file

@ -26,8 +26,8 @@ impl Function {
pub fn from_parts(chunk: Rc<Chunk>, attrs: FuncAttrs, state: Box<[CellValue]>) -> Self { pub fn from_parts(chunk: Rc<Chunk>, attrs: FuncAttrs, state: Box<[CellValue]>) -> Self {
Self { Self {
chunk,
attrs, attrs,
chunk,
state, state,
} }
} }

View file

@ -8,7 +8,7 @@ use std::{
use crate::{ use crate::{
chunk::Instruction, chunk::Instruction,
exception::{throw, Exception, Result}, exception::{throw, Exception, Result},
lstring::LStr, lstring::{LStr, LString},
parser::ast::{BinaryOp, UnaryOp}, parser::ast::{BinaryOp, UnaryOp},
symbol::{ symbol::{
Symbol, SYM_CALL_STACK_OVERFLOW, SYM_INTERRUPTED, SYM_NAME_ERROR, SYM_TYPE_ERROR, Symbol, SYM_CALL_STACK_OVERFLOW, SYM_INTERRUPTED, SYM_NAME_ERROR, SYM_TYPE_ERROR,
@ -50,6 +50,7 @@ pub struct Vm {
stack_max: usize, stack_max: usize,
globals: HashMap<Symbol, Value>, globals: HashMap<Symbol, Value>,
interrupt: Arc<AtomicBool>, interrupt: Arc<AtomicBool>,
args: Vec<LString>,
} }
pub fn binary_op(o: BinaryOp, a: Value, b: Value) -> Result<Value> { 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; let argc = args.len() - 1;
match argc.cmp(&attrs.arity) { match argc.cmp(&attrs.arity) {
Ordering::Equal => Ok(CallOutcome::Call(args)), 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 => { Ordering::Less => {
let remaining = attrs.arity - argc; let remaining = attrs.arity - argc;
let f = f.clone(); let f = f.clone();
@ -123,12 +126,13 @@ fn get_call_outcome(args: Vec<Value>) -> Result<CallOutcome> {
} }
impl Vm { impl Vm {
pub fn new(stack_max: usize) -> Self { pub fn new(stack_max: usize, args: Vec<LString>) -> Self {
Self { Self {
stack: Vec::with_capacity(16), stack: Vec::with_capacity(16),
call_stack: Vec::with_capacity(16), call_stack: Vec::with_capacity(16),
globals: HashMap::with_capacity(16), globals: HashMap::with_capacity(16),
stack_max, stack_max,
args,
interrupt: Arc::new(AtomicBool::new(false)), interrupt: Arc::new(AtomicBool::new(false)),
} }
} }
@ -156,6 +160,10 @@ impl Vm {
&self.globals &self.globals
} }
pub fn args(&self) -> &Vec<LString> {
&self.args
}
pub fn call_value(&mut self, value: Value, args: Vec<Value>) -> Result<Value> { pub fn call_value(&mut self, value: Value, args: Vec<Value>) -> Result<Value> {
self.check_interrupt()?; self.check_interrupt()?;
match get_call_outcome(args)? { match get_call_outcome(args)? {
@ -351,10 +359,8 @@ impl Vm {
self.push(self.stack[self.stack.len() - 2].clone()); self.push(self.stack[self.stack.len() - 2].clone());
} }
// [a0,a1...an] -> [] // [a0,a1...an] -> []
I::Drop(n) => { I::Drop => {
for _ in 0..u32::from(n) { self.pop();
self.pop();
}
} }
// [x,y] -> [y,x] // [x,y] -> [y,x]
I::Swap => { I::Swap => {
@ -476,6 +482,7 @@ impl Vm {
} }
// [f,a0,a1...an] -> [f(a0,a1...an)] // [f,a0,a1...an] -> [f(a0,a1...an)]
I::Call(n) => { I::Call(n) => {
self.check_interrupt()?;
let n = usize::from(n); let n = usize::from(n);
let args = self.pop_n(n + 1); let args = self.pop_n(n + 1);
@ -526,6 +533,44 @@ impl Vm {
unreachable!("already verified by calling get_call_type"); 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 // [v] -> [], return v
I::Return if frame.root => return Ok(Some(self.pop())), I::Return if frame.root => return Ok(Some(self.pop())),
// [v] -> [], return v // [v] -> [], return v

76
talc-lang/tests/parser.rs Normal file
View 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
View 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
}

View file

@ -1,6 +1,6 @@
[package] [package]
name = "talc-macros" name = "talc-macros"
version = "0.2.0" version = "0.2.1"
edition = "2021" edition = "2021"
[lib] [lib]

View file

@ -1,6 +1,6 @@
[package] [package]
name = "talc-std" name = "talc-std"
version = "0.2.0" version = "0.2.1"
edition = "2021" edition = "2021"
[dependencies] [dependencies]

View file

@ -15,19 +15,13 @@ pub fn throw(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
Value::Symbol(ty) => Exception::new(ty), Value::Symbol(ty) => Exception::new(ty),
Value::List(l) => match l.borrow().as_slice() { Value::List(l) => match l.borrow().as_slice() {
[Value::Symbol(ty)] | [Value::Symbol(ty), Value::Nil] => Exception::new(*ty), [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)] => { [Value::Symbol(ty), Value::String(s)] => {
Exception::new_with_msg(*ty, s.clone()) 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!(*SYM_TYPE_ERROR, "wrong argument for throw")
} }
[_, _, _, _, ..] => throw!( [_, _, _, ..] => throw!(
*SYM_VALUE_ERROR, *SYM_VALUE_ERROR,
"too many elements in list argument for throw" "too many elements in list argument for throw"
), ),

View file

@ -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> { pub fn tcp_connect_timeout(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, addr, timeout] = unpack_args!(args); let [_, addr, timeout] = unpack_args!(args);
let Value::String(addr) = addr else { let Value::String(addr) = addr else {

View file

@ -45,6 +45,33 @@ pub fn println(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
Ok(Value::Nil) 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)] #[native_func(0)]
pub fn readln(_: &mut Vm, _: Vec<Value>) -> Result<Value> { pub fn readln(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
let mut buf = Vec::new(); let mut buf = Vec::new();
@ -154,9 +181,46 @@ pub fn delenv(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
Ok(Value::Nil) 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) { pub fn load(vm: &mut Vm) {
vm.set_global_name("print", print().into()); vm.set_global_name("print", print().into());
vm.set_global_name("println", println().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("readln", readln().into());
vm.set_global_name("time", time().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("env", env().into());
vm.set_global_name("setenv", setenv().into()); vm.set_global_name("setenv", setenv().into());
vm.set_global_name("delenv", delenv().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());
} }

View file

@ -317,14 +317,19 @@ pub fn compile(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
"compile: argument must be a string, found {src:#}" "compile: argument must be a string, found {src:#}"
) )
}; };
let src = src.to_str().map_err(|e| { let src = src.to_str().map_err(|e| {
exception!( exception!(
*SYM_VALUE_ERROR, *SYM_VALUE_ERROR,
"compile: argument must be valid unicode ({e})" "compile: argument must be valid unicode ({e})"
) )
})?; })?;
let ast = talc_lang::parser::parse(src) let ast = talc_lang::parser::parse(src)
.map_err(|e| exception!(symbol!("parse_error"), "{e}"))?; .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()) Ok(func.into())
} }