improvements and such

This commit is contained in:
trimill 2024-02-27 00:18:43 -05:00
parent 66abf928e9
commit 9996ca1574
Signed by: trimill
GPG key ID: 4F77A16E17E10BCB
28 changed files with 3384 additions and 834 deletions

53
Cargo.lock generated
View file

@ -138,7 +138,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.51",
]
[[package]]
@ -244,9 +244,9 @@ dependencies = [
[[package]]
name = "error-code"
version = "3.0.0"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
checksum = "26a147e1a6641a55d994b3e4e9fa4d9b180c8d652c09b363af8c9bf1b8e04139"
[[package]]
name = "fd-lock"
@ -290,9 +290,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.3.6"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd"
checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60"
[[package]]
name = "home"
@ -527,6 +527,12 @@ dependencies = [
"siphasher",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "precomputed-hash"
version = "0.1.1"
@ -561,6 +567,36 @@ dependencies = [
"nibble_vec",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
@ -707,9 +743,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.50"
version = "2.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
dependencies = [
"proc-macro2",
"quote",
@ -752,6 +788,7 @@ name = "talc-std"
version = "0.1.0"
dependencies = [
"lazy_static",
"rand",
"talc-lang",
"talc-macros",
]
@ -784,7 +821,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.51",
]
[[package]]

185
talc-bin/src/helper.rs Normal file
View file

@ -0,0 +1,185 @@
use std::{borrow::Cow, cell::RefCell, collections::HashMap, rc::Rc};
use rustyline::{completion::Completer, highlight::Highlighter, hint::Hinter, validate::{ValidationContext, ValidationResult, Validator}, Helper, Result};
use talc_lang::{Lexer, Vm};
#[derive(Clone, Copy)]
enum TokenType {
String, Symbol, Number, Literal
}
pub struct TalcHelper {
vm: Rc<RefCell<Vm>>,
lex: Lexer,
token_types: HashMap<usize, TokenType>,
}
macro_rules! load_tokens {
($token_types:expr, $lex:expr, {$($($tok:literal)|+ => $ty:expr,)*}) => {{
$($(
$token_types.insert($lex.lex($tok).next().unwrap().unwrap().1.0, $ty);
)*)*
}};
}
impl TalcHelper {
pub fn new(vm: Rc<RefCell<Vm>>) -> Self {
let lex = Lexer::new();
let mut token_types = HashMap::new();
load_tokens!(token_types, lex, {
"\"\"" | "''" => TokenType::String,
":a" | ":''" | ":\"\"" => TokenType::Symbol,
"0" | "0.0" | "0x0" | "0b0" | "0o0" | "0s0" => TokenType::Number,
"true" | "false" | "nil" => TokenType::Literal,
});
Self {
vm,
lex,
token_types,
}
}
}
impl Helper for TalcHelper {}
impl Completer for TalcHelper {
type Candidate = String;
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<Self::Candidate>)> {
let mut res = String::new();
for ch in line[..pos].chars().rev() {
if matches!(ch, '0'..='9' | 'a'..='z' | 'A'..='Z' | '_') {
res.push(ch);
} else {
break
}
}
let res: String = res.chars().rev().collect();
let mut keys = self.vm.borrow().globals().keys()
.map(|sym| sym.name())
.filter(|name| name.starts_with(&res))
.map(|s| s.to_string())
.collect::<Vec<String>>();
keys.sort();
Ok((pos - res.as_bytes().len(), keys))
}
}
impl Hinter for TalcHelper {
type Hint = String;
}
impl Highlighter for TalcHelper {
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
let mut tokens = self.lex.lex(line).peekable();
let mut buf = String::new();
let mut last = 0;
while let Some(Ok((l, tok, r))) = tokens.next() {
buf += &line[last..l];
last = r;
let tokty = self.token_types.get(&tok.0);
buf += match tokty {
Some(TokenType::Literal) => "\x1b[93m",
Some(TokenType::Number) => "\x1b[93m",
Some(TokenType::String) => "\x1b[92m",
Some(TokenType::Symbol) => "\x1b[96m",
None => "",
};
buf += tok.1;
if tokty.is_some() { buf += "\x1b[0m" }
}
buf += &line[last..];
Cow::Owned(buf)
}
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
_default: bool,
) -> Cow<'b, str> {
Cow::Owned(format!("\x1b[94m{prompt}\x1b[0m"))
}
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
Cow::Owned(format!("\x1b[37m{}\x1b[0m", hint))
}
fn highlight_char(&self, line: &str, _: usize, forced: bool) -> bool {
forced || !line.is_empty()
}
}
impl Validator for TalcHelper {
fn validate(&self, ctx: &mut ValidationContext) -> Result<ValidationResult> {
let tokens = self.lex.lex(ctx.input());
let mut delims = Vec::new();
let mut mismatch = None;
for token in tokens {
let token = match token {
Ok(t) => t,
Err(e) => return Ok(ValidationResult::Invalid(
Some(e.to_string()))),
};
let t = token.1.1;
match t {
"(" | "{" | "[" | "if" | "while" | "for" | "try"
=> delims.push(token.1.1),
")" => match delims.pop() {
Some("(") => (),
v => { mismatch = Some((v, t)); break }
},
"}" => match delims.pop() {
Some("{") => (),
v => { mismatch = Some((v, t)); break }
},
"]" => match delims.pop() {
Some("[") => (),
v => { mismatch = Some((v, t)); break }
},
"then" => match delims.pop() {
Some("if") => delims.push(t),
v => { mismatch = Some((v, t)); break }
}
"catch" => match delims.pop() {
Some("try") => delims.push(t),
v => { mismatch = Some((v, t)); break }
}
"do" => match delims.last().copied() {
Some("while") | Some("for") | Some("catch") => {
delims.pop();
delims.push(t);
},
_ => delims.push(t)
},
"elif" | "else" => match delims.pop() {
Some("then") => delims.push(t),
v => { mismatch = Some((v, t)); break }
},
"end" => match delims.pop() {
Some("then" | "elif" | "else" | "do" | "try") => (),
v => { mismatch = Some((v, t)); break }
},
_ => (),
}
}
match mismatch {
Some((None, b)) => return Ok(ValidationResult::Invalid(Some(
format!(" found unmatched {b}")))),
Some((Some(a), b)) => return Ok(ValidationResult::Invalid(Some(
format!(" found {a} matched with {b}")))),
_ => (),
}
if delims.is_empty() {
Ok(ValidationResult::Valid(None))
} else {
Ok(ValidationResult::Incomplete)
}
}
}

View file

@ -1,136 +1,71 @@
use clap::Parser;
use rustyline::error::ReadlineError;
use talc_lang::{compiler::{compile, compile_repl}, value::{function::disasm_recursive, Value}, symbol::Symbol, Vm};
use std::{rc::Rc, path::PathBuf, process::ExitCode};
use clap::{ColorChoice, Parser};
use talc_lang::{compiler::compile, value::function::disasm_recursive, Vm};
use std::{path::PathBuf, process::ExitCode, rc::Rc};
mod repl;
mod helper;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
file: Option<PathBuf>,
/// file to run
file: Option<PathBuf>,
#[arg(short, long)]
repl: bool,
/// start the repl
#[arg(short, long)]
repl: bool,
#[arg(short, long)]
disasm: bool,
/// show disassembled bytecode
#[arg(short, long)]
disasm: bool,
/// enable or disable color
#[arg(short, long, default_value="auto")]
color: ColorChoice,
}
fn exec(src: String, _args: &Args) -> ExitCode {
fn exec(src: String, args: &Args) -> ExitCode {
let parser = talc_lang::Parser::new();
let mut vm = Vm::new(256);
talc_std::load_all(&mut vm);
talc_std::load_all(&mut vm);
let ex = match parser.parse(&src) {
Ok(ex) => ex,
Err(e) => {
eprintln!("Error: {e}");
return ExitCode::FAILURE
},
};
let ex = match parser.parse(&src) {
Ok(ex) => ex,
Err(e) => {
eprintln!("Error: {e}");
return ExitCode::FAILURE
},
};
let func = Rc::new(compile(&ex));
let func = Rc::new(compile(&ex));
if let Err(e) = vm.call_no_args(func.clone()) {
eprintln!("{e}");
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
}
}
fn repl(args: &Args) -> ExitCode {
if args.disasm {
eprintln!("input disassembly enabled");
}
let parser = talc_lang::Parser::new();
let mut compiler_globals = Vec::new();
let mut vm = Vm::new(256);
talc_std::load_all(&mut vm);
let interrupt = vm.get_interrupt();
let ctrlc_res = ctrlc::set_handler(move || {
interrupt.fetch_or(true, std::sync::atomic::Ordering::Relaxed);
});
if let Err(e) = ctrlc_res {
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 mut rl = match rustyline::DefaultEditor::new() {
Ok(rl) => rl,
Err(ReadlineError::Io(e)) => {
eprintln!("Error: {e}");
return ExitCode::FAILURE;
},
Err(ReadlineError::Errno(e)) => {
eprintln!("Error: {e}");
return ExitCode::FAILURE;
},
Err(_) => return ExitCode::SUCCESS,
};
loop {
let line = rl.readline(">> ");
let line = match line {
Ok(line) => line,
Err(ReadlineError::Eof) => return ExitCode::SUCCESS,
Err(ReadlineError::Interrupted) => continue,
Err(e) => {
eprintln!("Error: {e}");
continue
},
};
let ex = match parser.parse(&line) {
Ok(ex) => ex,
Err(e) => { eprintln!("Error: {e}"); 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}");
return ExitCode::FAILURE
}
}
match vm.call_no_args(func) {
Ok(v) => {
vm.set_global(prev3_sym, vm.get_global(prev2_sym).unwrap().clone());
vm.set_global(prev2_sym, vm.get_global(prev1_sym).unwrap().clone());
vm.set_global(prev1_sym, v.clone());
if v != Value::Nil {
println!("{v:#}");
}
}
Err(e) => eprintln!("{e}"),
if args.disasm {
if let Err(e) = disasm_recursive(&func, &mut std::io::stderr()) {
eprintln!("Error: {e}");
return ExitCode::FAILURE
}
}
if let Err(e) = vm.run_function(func.clone(), vec![func.into()]) {
eprintln!("{e}");
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
}
}
fn main() -> ExitCode {
let args = Args::parse();
let args = Args::parse();
if args.repl || args.file.is_none() {
return repl(&args)
}
if args.repl || args.file.is_none() {
return repl::repl(&args)
}
match std::fs::read_to_string(args.file.as_ref().unwrap()) {
Ok(s) => exec(s, &args),
Err(e) => {
eprintln!("Error: {e}");
ExitCode::FAILURE
}
}
match std::fs::read_to_string(args.file.as_ref().unwrap()) {
Ok(s) => exec(s, &args),
Err(e) => {
eprintln!("Error: {e}");
ExitCode::FAILURE
}
}
}

146
talc-bin/src/repl.rs Normal file
View file

@ -0,0 +1,146 @@
use std::{cell::RefCell, io::IsTerminal, process::ExitCode, rc::Rc};
use clap::ColorChoice;
use rustyline::{error::ReadlineError, history::MemHistory, ColorMode, Config, Editor};
use talc_lang::{compiler::compile_repl, symbol::Symbol, value::{function::disasm_recursive, Value}, Vm};
use crate::{helper::TalcHelper, Args};
#[derive(Clone, Copy, Default)]
pub struct ReplColors {
pub reset: &'static str,
pub error: &'static str,
}
impl ReplColors {
fn new(colors: ColorChoice) -> Self {
let colors = match colors {
ColorChoice::Always => true,
ColorChoice::Never => false,
ColorChoice::Auto => std::io::stdout().is_terminal(),
};
if !colors {
return Self::default()
}
Self {
reset: "\x1b[0m",
error: "\x1b[91m",
}
}
}
fn get_colmode(args: &Args) -> ColorMode {
match args.color {
ColorChoice::Auto => ColorMode::Enabled,
ColorChoice::Always => ColorMode::Forced,
ColorChoice::Never => ColorMode::Disabled,
}
}
pub fn init_rustyline(args: &Args) -> Result<Editor<TalcHelper, MemHistory>, ExitCode> {
let config = Config::builder()
.auto_add_history(true)
.color_mode(get_colmode(args))
.check_cursor_position(true)
.completion_type(rustyline::CompletionType::List)
.build();
match rustyline::Editor::with_history(config, MemHistory::default()) {
Ok(rl) => Ok(rl),
Err(ReadlineError::Io(e)) => {
eprintln!("Error: {e}");
Err(ExitCode::FAILURE)
},
Err(ReadlineError::Errno(e)) => {
eprintln!("Error: {e}");
Err(ExitCode::FAILURE)
},
Err(_) => Err(ExitCode::SUCCESS)
}
}
pub fn repl(args: &Args) -> ExitCode {
if args.disasm {
eprintln!("input disassembly enabled");
}
let parser = talc_lang::Parser::new();
let mut compiler_globals = Vec::new();
let mut vm = Vm::new(256);
talc_std::load_all(&mut vm);
let interrupt = vm.get_interrupt();
let ctrlc_res = ctrlc::set_handler(move || {
interrupt.fetch_or(true, std::sync::atomic::Ordering::Relaxed);
});
if let Err(e) = ctrlc_res {
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 mut rl = match init_rustyline(args) {
Ok(rl) => rl,
Err(e) => return e,
};
let vm = Rc::new(RefCell::new(vm));
rl.set_helper(Some(TalcHelper::new(vm.clone())));
loop {
let line = rl.readline(">> ");
let line = match line {
Ok(line) => line,
Err(ReadlineError::Eof) => return ExitCode::SUCCESS,
Err(ReadlineError::Interrupted) => continue,
Err(e) => {
eprintln!("{}Error:{} {e}", c.error, c.reset);
continue
},
};
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),
}
}
}

View file

@ -33,7 +33,7 @@ pub enum Expr<'s> {
List(Vec<Expr<'s>>),
Table(Vec<(Expr<'s>, Expr<'s>)>),
Return(Box<Expr<'s>>),
Return(Box<Expr<'s>>),
And(Box<Expr<'s>>, Box<Expr<'s>>),
Or(Box<Expr<'s>>, Box<Expr<'s>>),
If(Box<Expr<'s>>, Box<Expr<'s>>, Option<Box<Expr<'s>>>),

View file

@ -117,7 +117,7 @@ pub enum Instruction {
JumpTrue(Arg24),
JumpFalse(Arg24),
IterBegin, IterNext(Arg24),
IterBegin, IterTest(Arg24),
BeginTry(Arg24), EndTry,
@ -159,7 +159,7 @@ impl std::fmt::Display for Instruction {
Self::JumpTrue(a) => write!(f, "jumptrue {}", usize::from(a)),
Self::JumpFalse(a) => write!(f, "jumpfalse {}", usize::from(a)),
Self::IterBegin => write!(f, "iterbegin"),
Self::IterNext(a) => write!(f, "iternext {}", usize::from(a)),
Self::IterTest(a) => write!(f, "itertest {}", usize::from(a)),
Self::BeginTry(t) => write!(f, "begintry {}", usize::from(t)),
Self::EndTry => write!(f, "endtry"),
Self::Call(n) => write!(f, "call {n}"),

View file

@ -3,7 +3,7 @@ use std::rc::Rc;
use crate::ast::{BinaryOp, Expr, LValue, CatchBlock};
use crate::chunk::{Instruction as I, Chunk, Arg24, Catch};
use crate::symbol::Symbol;
use crate::value::function::Function;
use crate::value::function::{FuncAttrs, Function};
use crate::value::Value;
#[derive(Debug, Clone)]
@ -20,69 +20,67 @@ enum CompilerMode {
struct Compiler<'a> {
mode: CompilerMode,
parent: Option<&'a Compiler<'a>>,
func: Function,
chunk: Chunk,
attrs: FuncAttrs,
scope: usize,
locals: Vec<Local>,
globals: Vec<Local>,
}
pub fn compile(expr: &Expr) -> Function {
let mut comp = Compiler::new_module(None);
let mut comp = Compiler::new_module(None);
comp.expr(expr);
comp.finish()
comp.finish()
}
pub fn compile_repl(expr: &Expr, globals: &[Local]) -> (Function, Vec<Local>) {
let mut comp = Compiler::new_repl(globals);
let mut comp = Compiler::new_repl(globals);
comp.expr(expr);
comp.finish_repl()
comp.finish_repl()
}
impl<'a> Default for Compiler<'a> {
fn default() -> Self {
let locals = vec![Local {
name: "self".into(),
scope: 0,
}];
Self {
mode: CompilerMode::Function,
parent: None,
func: Function { arity: 0, chunk: Chunk::new() },
scope: 0,
locals,
globals: Vec::new(),
}
}
fn default() -> Self {
let locals = vec![Local {
name: "self".into(),
scope: 0,
}];
Self {
mode: CompilerMode::Function,
parent: None,
chunk: Chunk::new(),
attrs: FuncAttrs { arity: 0, variadic: false },
scope: 0,
locals,
globals: Vec::new(),
}
}
}
impl<'a> Compiler<'a> {
fn new_repl(globals: &[Local]) -> Self {
Self {
mode: CompilerMode::Repl,
globals: globals.to_vec(),
..Self::default()
}
}
Self {
mode: CompilerMode::Repl,
globals: globals.to_vec(),
..Self::default()
}
}
fn new_module(parent: Option<&'a Self>) -> Self {
Self {
mode: CompilerMode::Module,
parent,
..Self::default()
}
}
Self {
mode: CompilerMode::Module,
parent,
..Self::default()
}
}
fn new_function(&'a self, args: &[&str]) -> Self {
let func = Function {
arity: args.len(),
chunk: Chunk::new(),
};
let mut new = Self {
let mut new = Self {
mode: CompilerMode::Function,
parent: Some(self),
func,
..Self::default()
};
..Self::default()
};
new.attrs.arity = args.len();
for arg in args {
new.locals.push(Local {
@ -96,12 +94,15 @@ impl<'a> Compiler<'a> {
pub fn finish(mut self) -> Function {
self.emit(I::Return);
self.func
Function { chunk: Rc::new(self.chunk), attrs: self.attrs }
}
pub fn finish_repl(mut self) -> (Function, Vec<Local>) {
self.emit(I::Return);
(self.func, self.globals)
(
Function { chunk: Rc::new(self.chunk), attrs: self.attrs },
self.globals
)
}
//
@ -109,16 +110,16 @@ impl<'a> Compiler<'a> {
//
fn add_const(&mut self, val: Value) -> usize {
self.func.chunk.add_const(val)
self.chunk.add_const(val)
}
fn emit(&mut self, instr: I) -> usize {
self.func.chunk.add_instr(instr)
self.chunk.add_instr(instr)
}
fn emit_discard(&mut self, mut n: usize) {
while n > 0 {
let instrs = &mut self.func.chunk.instrs;
let instrs = &mut self.chunk.instrs;
// dup followed by store: remove the dup
if instrs.len() >= 2
@ -128,9 +129,9 @@ impl<'a> Compiler<'a> {
))
{
// can't panic: checked that instrs.len() >= 2
let i = self.func.chunk.instrs.pop().unwrap();
self.func.chunk.instrs.pop().unwrap();
self.func.chunk.instrs.push(i);
let i = self.chunk.instrs.pop().unwrap();
self.chunk.instrs.pop().unwrap();
self.chunk.instrs.push(i);
n -= 1;
continue;
}
@ -160,11 +161,11 @@ impl<'a> Compiler<'a> {
}
fn ip(&self) -> usize {
self.func.chunk.instrs.len()
self.chunk.instrs.len()
}
fn update_instr(&mut self, n: usize, new: I) {
self.func.chunk.instrs[n] = new;
self.chunk.instrs[n] = new;
}
fn begin_scope(&mut self) {
@ -368,10 +369,10 @@ impl<'a> Compiler<'a> {
}
self.emit(I::Call(args.len() as u8));
},
Expr::Return(e) => {
self.expr(e);
self.emit(I::Return);
},
Expr::Return(e) => {
self.expr(e);
self.emit(I::Return);
},
Expr::Pipe(a, f) => {
self.expr(a);
self.expr(f);
@ -428,7 +429,7 @@ impl<'a> Compiler<'a> {
}
fn expr_try(&mut self, body: &Expr, catch_blocks: &[CatchBlock]) {
let (idx, mut table) = self.func.chunk.begin_try_table(self.locals.len());
let (idx, mut table) = self.chunk.begin_try_table(self.locals.len());
self.emit(I::BeginTry(Arg24::from_usize(idx)));
self.expr(body);
@ -463,7 +464,7 @@ impl<'a> Compiler<'a> {
self.update_instr(addr, I::Jump(ip));
}
self.func.chunk.finish_catch_table(idx, table);
self.chunk.finish_catch_table(idx, table);
}
fn expr_for(&mut self, name: &str, iter: &Expr, body: &Expr) {
@ -480,7 +481,9 @@ impl<'a> Compiler<'a> {
let start = self.ip();
// call iterator and jump if nil, otherwise store
let j1 = self.emit(I::IterNext(Arg24::from_usize(0)));
self.emit(I::Dup);
self.emit(I::Call(0));
let j1 = self.emit(I::IterTest(Arg24::from_usize(0)));
self.store_local(local);
// body
@ -490,7 +493,7 @@ impl<'a> Compiler<'a> {
// end loop
self.emit(I::Jump(Arg24::from_usize(start)));
self.update_instr(j1, I::IterNext(Arg24::from_usize(self.ip())));
self.update_instr(j1, I::IterTest(Arg24::from_usize(self.ip())));
self.end_scope();
self.emit(I::Nil);
}

View file

View file

@ -15,13 +15,14 @@ pub mod symbol;
pub mod ast;
pub mod value;
pub mod gc;
pub mod chunk;
pub mod compiler;
pub use parser::BlockParser as Parser;
pub use vm::Vm;
pub use parser_util::{parse_int, parse_float, parse_str_escapes};
type LexResult<'input> = Result<
(usize, Token<'input>, usize),
ParseError<usize, Token<'input>, parser_util::ParseError>

View file

@ -26,7 +26,6 @@ match {
"false",
"nil",
// kw variables
"const",
"global",
"var",
// kw logic
@ -117,9 +116,8 @@ AssignOp: Option<BinaryOp> = {
"&=" => Some(BinaryOp::Append),
}
//
// operations
// logical ops
//
// or
@ -140,12 +138,29 @@ UnaryNot: Box<Expr<'input>> = {
Pipe,
}
// |
//
// pipe
//
Pipe: Box<Expr<'input>> = {
<l:Pipe> "|" <r:BinaryCompare> => Box::new(Expr::Pipe(l, r)),
<l:Pipe> "|" <r:Lambda> => Box::new(Expr::Pipe(l, r)),
Lambda,
}
//
// lambda
//
Lambda: Box<Expr<'input>> = {
"\\" <xs:IdentList> "->" <e:BinaryCompare> => Box::new(Expr::Lambda(xs, e)),
":" <e:BinaryCompare> => Box::new(Expr::Lambda(vec!["$"], e)),
"::" <e:BinaryCompare> => Box::new(Expr::Lambda(vec!["$", "$$"], e)),
BinaryCompare,
}
//
// operations
//
// == != > < >= <=
BinaryCompare: Box<Expr<'input>> = {
@ -250,7 +265,7 @@ UnaryMinus2: Box<Expr<'input>> = {
// function call
FunctionCall: Box<Expr<'input>> = {
<l:FieldAccess> "(" <r:ExprList> ")" => Box::new(Expr::FnCall(l, r)),
<l:FunctionCall> "(" <r:ExprList> ")" => Box::new(Expr::FnCall(l, r)),
FieldAccess,
}
@ -277,8 +292,7 @@ TermNotIdent: Box<Expr<'input>> = {
"[" <ExprList> "]" => Box::new(Expr::List(<>)),
"{" <TableItems> "}" => Box::new(Expr::Table(<>)),
"$" => Box::new(Expr::Ident("$")),
":" "(" <e:Block> ")" => Box::new(Expr::Lambda(vec!["$"], e)),
"\\" <xs:IdentList> "->" <e:Term> => Box::new(Expr::Lambda(xs, e)),
"$$" => Box::new(Expr::Ident("$$")),
"do" <Block> "end" => <>,
"if" <IfStmtChain> => <>,

View file

@ -9,17 +9,31 @@ struct SymbolTable {
}
lazy_static! {
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_NIL: Symbol = symbol!(nil);
pub static ref SYM_BOOL: Symbol = symbol!(bool);
pub static ref SYM_SYMBOL: Symbol = symbol!(symbol);
pub static ref SYM_INT: Symbol = symbol!(int);
pub static ref SYM_RATIO: Symbol = symbol!(ratio);
pub static ref SYM_FLOAT: Symbol = symbol!(float);
pub static ref SYM_COMPLEX: Symbol = symbol!(complex);
pub static ref SYM_RANGE: Symbol = symbol!(range);
pub static ref SYM_CELL: Symbol = symbol!(cell);
pub static ref SYM_STRING: Symbol = symbol!(string);
pub static ref SYM_LIST: Symbol = symbol!(list);
pub static ref SYM_TABLE: Symbol = symbol!(table);
pub static ref SYM_FUNCTION: Symbol = symbol!(function);
pub static ref SYM_NATIVE_FUNC: Symbol = symbol!(native_func);
pub static ref SYM_TYPE_ERROR: Symbol = symbol!(type_error);
pub static ref SYM_NAME_ERROR: Symbol = symbol!(name_error);
pub static ref SYM_INDEX_ERROR: Symbol = symbol!(index_error);
pub static ref SYM_HASH_ERROR: Symbol = symbol!(hash_error);
pub static ref SYM_CALL_STACK_OVERFLOW: Symbol = symbol!(call_stack_overflow);
pub static ref SYM_INTERRUPTED: Symbol = symbol!(interrupted);
pub static ref SYM_STOP_ITERATOR: Symbol = symbol!(stop_iterator);
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_NAME_ERROR: Symbol = symbol!(name_error);
pub static ref SYM_INDEX_ERROR: Symbol = symbol!(index_error);
pub static ref SYM_HASH_ERROR: Symbol = symbol!(hash_error);
pub static ref SYM_CALL_STACK_OVERFLOW: Symbol = symbol!(call_stack_overflow);
pub static ref SYM_INTERRUPTED: Symbol = symbol!(interrupted);
}
static TABLE: OnceLock<Mutex<SymbolTable>> = OnceLock::new();
@ -93,12 +107,12 @@ impl Symbol {
#[macro_export]
macro_rules! symbol {
($sym:ident) => {
$crate::symbol::Symbol::get(stringify!($sym))
};
($sym:literal) => {
$crate::symbol::Symbol::get($sym)
};
($sym:ident) => {
$crate::symbol::Symbol::get(stringify!($sym))
};
($sym:literal) => {
$crate::symbol::Symbol::get($sym)
};
}
pub use symbol;

View file

@ -20,25 +20,33 @@ impl Exception {
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<str>, data: Value) -> Self {
Self { ty, msg: Some(msg), data: Some(data) }
}
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())?;
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,
}),
Some(Value::String(msg)) => Some(Self {
ty: *ty,
data,
msg: Some(msg.clone()),
}),
Some(_) => None,
}
let msg = table.get(&(*SYM_MSG).into());
match msg {
None => Some(Self {
ty: *ty,
data,
msg: None,
}),
Some(Value::String(msg)) => Some(Self {
ty: *ty,
data,
msg: Some(msg.clone()),
}),
Some(_) => None,
}
} else {
None
}
@ -59,11 +67,11 @@ impl Exception {
impl Display for Exception {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(msg) = &self.msg {
write!(f, "{}: {}", self.ty.name(), msg)
} else {
write!(f, "{}", self.ty.name())
}
if let Some(msg) = &self.msg {
write!(f, "{}: {}", self.ty.name(), msg)
} else {
write!(f, "{}", self.ty.name())
}
}
}
@ -88,7 +96,7 @@ pub use exception;
#[macro_export]
macro_rules! throw {
($($args:tt)*) => {
return Err($crate::value::exception::exception!($($args)*))
return Err($crate::value::exception::exception!($($args)*))
};
}

View file

@ -4,69 +4,97 @@ use crate::{chunk::Chunk, Vm};
use super::{Value, exception::Result};
#[derive(Debug, Default)]
pub struct Function {
#[derive(Clone, Copy, Debug, Default)]
pub struct FuncAttrs {
pub arity: usize,
pub chunk: Chunk,
pub variadic: bool,
}
#[derive(Debug, Default)]
pub struct Function {
pub attrs: FuncAttrs,
pub chunk: Rc<Chunk>,
}
impl Function {
pub fn new(chunk: Rc<Chunk>, arity: usize) -> Self {
Self { chunk, attrs: FuncAttrs { arity, variadic: false } }
}
pub fn new_variadic(chunk: Rc<Chunk>, arity: usize) -> Self {
Self { chunk, attrs: FuncAttrs { arity, variadic: true } }
}
}
type FnNative = Box<dyn Fn(&mut Vm, Vec<Value>) -> Result<Value>>;
pub struct NativeFunc {
pub arity: usize,
#[allow(clippy::type_complexity)]
pub f: Box<dyn Fn(&mut Vm, Vec<Value>) -> Result<Value>>,
pub attrs: FuncAttrs,
pub func: FnNative,
}
impl NativeFunc {
pub fn new(func: FnNative, arity: usize) -> Self {
Self { func, attrs: FuncAttrs { arity, variadic: false } }
}
pub fn new_variadic(func: FnNative, arity: usize) -> Self {
Self { func, attrs: FuncAttrs { arity, variadic: true } }
}
}
impl std::fmt::Debug for NativeFunc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NativeFunc")
.field("arity", &self.arity)
.field("attrs", &self.attrs)
.finish_non_exhaustive()
}
}
pub fn disasm_recursive(f: &Rc<Function>, w: &mut impl std::io::Write) -> std::io::Result<()> {
writeln!(w, "{} (argc={})", Value::Function(f.clone()), f.arity)?;
if !f.chunk.consts.is_empty() {
writeln!(w, "constants")?;
for (i, c) in f.chunk.consts.iter().enumerate() {
writeln!(w, " {i:04}: {c}")?;
}
}
if !f.chunk.try_tables.is_empty() {
writeln!(w, "catch tables")?;
for (i, n) in f.chunk.try_tables.iter().enumerate() {
write!(w, " {i:04}: ")?;
if n.catches.is_empty() {
writeln!(w)?;
}
for (i, catch) in n.catches.iter().enumerate() {
if i != 0 {
write!(w, " : ")?;
}
if let Some(types) = &catch.types {
write!(w, "{:04} [", catch.addr)?;
for (i, ty) in types.iter().enumerate() {
if i != 0 { write!(w, ", ")?; }
write!(w, "{}", ty.name())?;
}
writeln!(w, "]")?;
} else {
writeln!(w, "{:04} *", catch.addr)?;
}
}
}
}
writeln!(w, "instructions")?;
for (i, n) in f.chunk.instrs.iter().enumerate() {
writeln!(w, " {i:04}: {n}")?;
}
writeln!(w)?;
writeln!(w, "{} ({}{})",
Value::Function(f.clone()),
f.attrs.arity,
if f.attrs.variadic { ".." } else { "" })?;
if !f.chunk.consts.is_empty() {
writeln!(w, "constants")?;
for (i, c) in f.chunk.consts.iter().enumerate() {
writeln!(w, " {i:04}: {c}")?;
}
}
if !f.chunk.try_tables.is_empty() {
writeln!(w, "catch tables")?;
for (i, n) in f.chunk.try_tables.iter().enumerate() {
write!(w, " {i:04}: ")?;
if n.catches.is_empty() {
writeln!(w)?;
}
for (i, catch) in n.catches.iter().enumerate() {
if i != 0 {
write!(w, " : ")?;
}
if let Some(types) = &catch.types {
write!(w, "{:04} [", catch.addr)?;
for (i, ty) in types.iter().enumerate() {
if i != 0 { write!(w, ", ")?; }
write!(w, "{}", ty.name())?;
}
writeln!(w, "]")?;
} else {
writeln!(w, "{:04} *", catch.addr)?;
}
}
}
}
writeln!(w, "instructions")?;
for (i, n) in f.chunk.instrs.iter().enumerate() {
writeln!(w, " {i:04}: {n}")?;
}
writeln!(w)?;
for c in &f.chunk.consts {
if let Value::Function(f) = c {
disasm_recursive(f, w)?;
}
}
Ok(())
for c in &f.chunk.consts {
if let Value::Function(f) = c {
disasm_recursive(f, w)?;
}
}
Ok(())
}

View file

@ -0,0 +1,125 @@
use crate::{symbol::{SYM_INDEX_ERROR, SYM_TYPE_ERROR}, value::{cell_take, function::NativeFunc}, vmcalliter, Vm};
use super::{Value, exception::{Result, throw}, range::RangeType};
impl Value {
pub fn index(&self, idx: Self) -> Result<Self> {
use Value as V;
match (self, idx) {
(V::List(l), V::Int(i)) => {
let l = l.borrow();
if i >= 0 && (i as usize) < l.len() {
Ok(l[i as usize].clone())
} else {
throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for list of length {}", l.len())
}
},
(V::Range(r), V::Int(i)) => {
if i >= 0 && (
r.ty == RangeType::Endless
|| i < r.stop
|| (r.ty == RangeType::Closed && i == r.stop)
) {
Ok((r.start + i).into())
} else {
throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for range {self}")
}
},
(V::Table(t), i) if i.hashable() => {
let t = t.borrow();
let i = i.try_into()?;
Ok(t.get(&i).cloned().unwrap_or(Value::Nil))
},
(col, idx) => if let Ok(ii) = idx.clone().to_iter_function() {
let col = col.clone();
let func = move |vm: &mut Vm, _| {
match vmcalliter!(vm; ii.clone())? {
Some(i) => Ok(col.index(cell_take(i))?.to_cell()),
None => Ok(Value::Nil)
}
};
Ok(NativeFunc::new(Box::new(func), 0).into())
} else {
throw!(*SYM_TYPE_ERROR, "cannot index {col:#} with {idx:#}")
}
}
}
pub fn store_index(&self, vm: &mut Vm, idx: Self, val: Self) -> Result<()> {
use Value as V;
match (self, idx) {
(V::List(l), V::Int(i)) => {
let mut l = l.borrow_mut();
if i >= 0 && (i as usize) < l.len() {
l[i as usize] = val;
Ok(())
} else {
throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for list of length {}", l.len())
}
},
(V::Table(t), i) if i.hashable() => {
let mut t = t.borrow_mut();
let i = i.try_into()?;
t.insert(i, val);
Ok(())
},
(V::List(t), V::Range(r)) => {
let iter = val.to_iter_function()?;
let mut vals = Vec::new();
while let Some(v) = vmcalliter!(vm; iter.clone())? {
vals.push(cell_take(v));
}
let mut tm = t.borrow_mut();
match r.ty {
RangeType::Open => {
if r.start < 0 || r.start > tm.len() as i64 {
throw!(*SYM_INDEX_ERROR,
"index {} out of bounds for list of length {}", r.stop, tm.len())
}
if r.stop < 0 || r.stop > tm.len() as i64 {
throw!(*SYM_INDEX_ERROR,
"index {} out of bounds for list of length {}", r.stop, tm.len())
}
let end = tm.split_off(r.stop as usize);
tm.truncate(r.start as usize);
tm.extend(vals.into_iter().chain(end))
},
RangeType::Closed => {
if r.start < 0 || r.start > tm.len() as i64 {
throw!(*SYM_INDEX_ERROR,
"index {} out of bounds for list of length {}", r.stop, tm.len())
}
if r.stop < 0 || r.stop >= tm.len() as i64 {
throw!(*SYM_INDEX_ERROR,
"index {} out of bounds for list of length {}", r.stop, tm.len())
}
let end = tm.split_off(r.stop as usize + 1);
tm.truncate(r.start as usize);
tm.extend(vals.into_iter().chain(end))
},
RangeType::Endless => {
if r.start < 0 || r.start > tm.len() as i64 {
throw!(*SYM_INDEX_ERROR,
"index {} out of bounds for list of length {}", r.stop, tm.len())
}
tm.truncate(r.start as usize);
tm.extend(vals)
},
}
Ok(())
},
(col, idx) => if let Ok(ii) = idx.clone().to_iter_function() {
let val = val.to_iter_function()?;
while let Some(i) = vmcalliter!(vm; ii.clone())? {
let Some(v) = vmcalliter!(vm; val.clone())? else {
throw!(*SYM_INDEX_ERROR, "not enough values provided for store index")
};
col.store_index(vm, cell_take(i), cell_take(v))?;
}
Ok(())
} else {
throw!(*SYM_TYPE_ERROR, "cannot index {self:#} with {idx:#}")
},
}
}
}

View file

@ -1,9 +1,9 @@
use std::{rc::Rc, cell::RefCell, collections::HashMap, hash::Hash, fmt::Display};
use std::{cell::RefCell, collections::HashMap, fmt::Display, hash::Hash, rc::Rc};
use num_complex::Complex64;
use num_rational::Rational64;
pub use num_complex::Complex64;
pub use num_rational::Rational64;
use crate::symbol::{SYM_HASH_ERROR, Symbol};
use crate::symbol::{Symbol, SYM_HASH_ERROR};
use self::{range::{Range, RangeType}, function::{Function, NativeFunc}, exception::{Exception, throw}};
@ -11,12 +11,14 @@ pub mod exception;
pub mod function;
pub mod ops;
pub mod range;
pub mod index;
type RcList = Rc<RefCell<Vec<Value>>>;
type RcTable = Rc<RefCell<HashMap<HashValue, Value>>>;
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Default)]
pub enum Value {
#[default]
Nil,
Bool(bool),
Symbol(Symbol),
@ -27,6 +29,7 @@ pub enum Value {
Ratio(Rational64),
Complex(Complex64),
Cell(Rc<RefCell<Value>>),
String(Rc<str>),
List(RcList),
Table(RcTable),
@ -37,62 +40,82 @@ pub enum Value {
impl Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Value::Nil => write!(f, "nil"),
Value::Bool(b) => write!(f, "{b}"),
Value::Symbol(s) => write!(f, ":{}", s.name()),
Value::Range(r) => match r.ty {
Self::Nil => write!(f, "nil"),
Self::Bool(b) => write!(f, "{b}"),
Self::Symbol(s) => write!(f, ":{}", s.name()),
Self::Range(r) => match r.ty {
RangeType::Open => write!(f, "{}..{}", r.start, r.stop),
RangeType::Closed => write!(f, "{}..={}", r.start, r.stop),
RangeType::Endless => write!(f, "{}..*", r.start),
},
Value::Int(n) => write!(f, "{n}"),
Value::Float(x) => write!(f, "{x:?}"),
Value::Ratio(r) => write!(f, "{}/{}", r.numer(), r.denom()),
Value::Complex(z) => write!(f, "{z}"),
Self::Int(n) => write!(f, "{n}"),
Self::Float(x) => write!(f, "{x:?}"),
Self::Ratio(r) => write!(f, "{}/{}", r.numer(), r.denom()),
Self::Complex(z) => write!(f, "{z}"),
Value::String(s) => {
if f.alternate() {
write!(f, "{s:?}")
} else {
write!(f, "{s}")
}
},
Value::List(l) => {
Self::Cell(v) if f.alternate() => write!(f, "cell({:#})", v.borrow()),
Self::Cell(v) => write!(f, "{})", v.borrow()),
Self::String(s) if f.alternate() => write!(f, "{s:?}"),
Self::String(s) => write!(f, "{s}"),
Self::List(l) => {
write!(f, "[")?;
for (i, item) in l.borrow().iter().enumerate() {
if i != 0 {
write!(f, ", ")?;
}
if f.alternate() {
write!(f, "{item:#}")?;
} else {
write!(f, "{item}")?;
}
write!(f, "{item:#}")?;
}
write!(f, "]")
},
Value::Table(t) => {
Self::Table(t) => {
write!(f, "{{ ")?;
for (i, (k, v)) in t.borrow().iter().enumerate() {
if i != 0 {
write!(f, ", ")?;
}
if f.alternate() {
write!(f, "({:#}) = {v:#}", k.0)?;
} else {
write!(f, "({}) = {v}", k.0)?;
}
write!(f, "({:#}) = {v:#}", k.0)?;
}
write!(f, " }}")
},
Value::Function(g)
Self::Function(g)
=> write!(f, "<function {:?}>", Rc::as_ptr(g)),
Value::NativeFunc(g)
Self::NativeFunc(g)
=> write!(f, "<native function {:?}>", Rc::as_ptr(g)),
}
}
}
impl Value {
pub fn get_type(&self) -> Symbol {
use crate::symbol::*;
match self {
Value::Nil => *SYM_NIL,
Value::Bool(_) => *SYM_BOOL,
Value::Symbol(_) => *SYM_SYMBOL,
Value::Range(_) => *SYM_RANGE,
Value::Int(_) => *SYM_INT,
Value::Float(_) => *SYM_FLOAT,
Value::Ratio(_) => *SYM_RATIO,
Value::Complex(_) => *SYM_COMPLEX,
Value::Cell(_) => *SYM_CELL,
Value::String(_) => *SYM_STRING,
Value::List(_) => *SYM_LIST,
Value::Table(_) => *SYM_TABLE,
Value::Function(_) => *SYM_FUNCTION,
Value::NativeFunc(_) => *SYM_NATIVE_FUNC,
}
}
pub fn hashable(&self) -> bool {
matches!(
self,
Value::Nil | Value::Bool(_) | Value::Symbol(_)
| Value::Int(_) | Value::Ratio(_) | Value::String(_)
)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct HashValue(Value);
@ -102,15 +125,10 @@ impl Eq for HashValue {}
impl TryFrom<Value> for HashValue {
type Error = Exception;
fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
match value {
Value::Nil
| Value::Bool(_)
| Value::Symbol(_)
| Value::Int(_)
| Value::Ratio(_)
| Value::String(_) => Ok(Self(value)),
_ => throw!(*SYM_HASH_ERROR, "value {value:#} cannot be hashed"),
if !value.hashable() {
throw!(*SYM_HASH_ERROR, "value {value:#} cannot be hashed")
}
Ok(Self(value))
}
}
@ -134,43 +152,43 @@ impl Hash for HashValue {
}
macro_rules! impl_from {
($ty:ty, $var:ident) => {
impl From<$ty> for Value {
fn from(value: $ty) -> Self { Self::$var(value) }
}
};
($ty:ty, $var:ident, hash) => {
impl From<$ty> for Value {
fn from(value: $ty) -> Self { Self::$var(value) }
}
impl From<$ty> for HashValue {
fn from(value: $ty) -> Self { Self(Value::$var(value)) }
}
};
($ty:ty, $var:ident, rc) => {
impl From<$ty> for Value {
fn from(value: $ty) -> Self { Self::$var(Rc::new(value)) }
}
impl From<Rc<$ty>> for Value {
fn from(value: Rc<$ty>) -> Self { Self::$var(value) }
}
};
($ty:ty, $var:ident, rcref) => {
impl From<$ty> for Value {
fn from(value: $ty) -> Self { Self::$var(Rc::new(RefCell::new(value))) }
}
impl From<RefCell<$ty>> for Value {
fn from(value: RefCell<$ty>) -> Self { Self::$var(Rc::new(value)) }
}
impl From<Rc<RefCell<$ty>>> for Value {
fn from(value: Rc<RefCell<$ty>>) -> Self { Self::$var(value) }
}
};
($ty:ty, $var:ident, into) => {
impl From<$ty> for Value {
fn from(value: $ty) -> Self { Self::$var(value.into()) }
}
};
($ty:ty, $var:ident) => {
impl From<$ty> for Value {
fn from(value: $ty) -> Self { Self::$var(value) }
}
};
($ty:ty, $var:ident, hash) => {
impl From<$ty> for Value {
fn from(value: $ty) -> Self { Self::$var(value) }
}
impl From<$ty> for HashValue {
fn from(value: $ty) -> Self { Self(Value::$var(value)) }
}
};
($ty:ty, $var:ident, rc) => {
impl From<$ty> for Value {
fn from(value: $ty) -> Self { Self::$var(Rc::new(value)) }
}
impl From<Rc<$ty>> for Value {
fn from(value: Rc<$ty>) -> Self { Self::$var(value) }
}
};
($ty:ty, $var:ident, rcref) => {
impl From<$ty> for Value {
fn from(value: $ty) -> Self { Self::$var(Rc::new(RefCell::new(value))) }
}
impl From<RefCell<$ty>> for Value {
fn from(value: RefCell<$ty>) -> Self { Self::$var(Rc::new(value)) }
}
impl From<Rc<RefCell<$ty>>> for Value {
fn from(value: Rc<RefCell<$ty>>) -> Self { Self::$var(value) }
}
};
($ty:ty, $var:ident, into) => {
impl From<$ty> for Value {
fn from(value: $ty) -> Self { Self::$var(value.into()) }
}
};
}
impl_from!(bool, Bool, hash);
@ -185,6 +203,12 @@ impl_from!(Vec<Value>, List, rcref);
impl_from!(Function, Function, rc);
impl_from!(NativeFunc, NativeFunc, rc);
impl_from!(Rc<str>, String);
impl_from!(String, String, into);
impl_from!(&str, String, into);
impl_from!(Box<str>, String, into);
impl_from!(String, String, into);
impl_from!(RefCell<Value>, Cell, rc);
#[inline]
pub fn cell_take<T: Clone>(cell: Rc<RefCell<T>>) -> T {
Rc::unwrap_or_clone(cell).into_inner()
}

View file

@ -1,11 +1,11 @@
use std::{ops::{Neg, Add, Sub, Mul, Div}, cmp::Ordering, cell::RefCell};
use std::{cell::RefCell, cmp::Ordering, ops::{Add, Div, Mul, Neg, Sub}, rc::Rc};
use num_complex::Complex64;
use num_rational::Rational64;
use crate::{symbol::{SYM_INDEX_ERROR, SYM_STOP_ITERATOR, SYM_TYPE_ERROR}, value::range::RangeType, Vm};
use crate::{symbol::SYM_TYPE_ERROR, value::range::RangeType, Vm};
use super::{Value, exception::{Result, throw}, range::Range, function::NativeFunc, HashValue};
use super::{exception::{throw, Result}, function::{FuncAttrs, NativeFunc}, range::Range, HashValue, Value};
impl Value {
pub fn truthy(&self) -> bool {
@ -19,6 +19,7 @@ impl Value {
Value::Complex(c) => !(c.re == 0.0 && c.im == 0.0 || c.is_nan()),
Value::String(s) => s.len() > 0,
Value::List(l) => l.borrow().len() > 0,
Value::Cell(v) => v.borrow().truthy(),
Value::Symbol(_) | Value::Table(_)
| Value::Function(_) | Value::NativeFunc(_) => true,
@ -32,7 +33,7 @@ fn ratio_to_f64(r: Rational64) -> f64 {
}
#[allow(clippy::cast_precision_loss)]
fn promote(a: Value, b: Value) -> (Value, Value) {
pub fn promote(a: Value, b: Value) -> (Value, Value) {
use Value as V;
match (&a, &b) {
(V::Int(x), V::Ratio(..)) => (V::Ratio((*x).into()), b),
@ -113,6 +114,9 @@ impl Div<Value> for Value {
fn div(self, rhs: Value) -> Self::Output {
use Value as V;
match promote(self, rhs) {
(_, V::Int(0)) => throw!(*SYM_TYPE_ERROR, "integer division by 0"),
(_, V::Ratio(r)) if *r.numer() == 0 && *r.denom() != 0
=> throw!(*SYM_TYPE_ERROR, "integer division by 0"),
(V::Int(x), V::Int(y)) => Ok(V::Ratio(Rational64::new(x, y))),
(V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x / y)),
(V::Float(x), V::Float(y)) => Ok(V::Float(x / y)),
@ -123,26 +127,26 @@ impl Div<Value> for Value {
}
#[allow(clippy::cast_sign_loss)]
const fn ipow(n: i64, p: u64) -> i64 {
fn ipow(n: i64, p: u64) -> Result<i64> {
match (n, p) {
(0, 0) => panic!("power 0^0"),
(0, _) => 0,
(_, 0) => 1,
(0, 0) => throw!(*SYM_TYPE_ERROR, "integer 0 raised to power 0"),
(0, _) => Ok(0),
(_, 0) => Ok(1),
(n, p) if p > u32::MAX as u64 => {
let (lo, hi) = (p as u32, (p >> 32) as u32);
let (a, b) = (n.pow(lo), n.pow(hi));
a * b.pow(0x1_0000).pow(0x1_0000)
Ok(a * b.pow(0x1_0000).pow(0x1_0000))
}
(n, p) => n.pow(p as u32),
(n, p) => Ok(n.pow(p as u32)),
}
}
#[allow(clippy::cast_sign_loss)]
const fn rpow(n: i64, d: i64, p: i64) -> (i64, i64) {
match p {
0.. => (ipow(n, p as u64), ipow(d, p as u64)),
_ => (ipow(d, (-p) as u64), ipow(n, (-p) as u64)),
}
fn rpow(n: i64, d: i64, p: i64) -> Result<(i64, i64)> {
Ok(match p {
0.. => (ipow(n, p as u64)?, ipow(d, p as u64)?),
_ => (ipow(d, (-p) as u64)?, ipow(n, (-p) as u64)?),
})
}
impl Value {
@ -171,11 +175,11 @@ impl Value {
pub fn pow(self, rhs: Value) -> Result<Self> {
use Value as V;
if let (V::Ratio(x), V::Int(y)) = (&self, &rhs) {
return Ok(V::Ratio(rpow(*(*x).numer(), *(*x).denom(), *y).into()));
return Ok(V::Ratio(rpow(*(*x).numer(), *(*x).denom(), *y)?.into()));
}
match promote(self, rhs) {
(V::Int(x), V::Int(y))
=> Ok(V::Ratio(rpow(x, 1, y).into())),
=> Ok(V::Ratio(rpow(x, 1, y)?.into())),
(V::Float(x), V::Float(y))
=> Ok(V::Float(x.powf(y))),
(V::Ratio(x), V::Ratio(y))
@ -214,6 +218,7 @@ impl PartialEq for Value {
(V::String(a), V::String(b)) => *a == *b,
(V::List(a), V::List(b)) => *a.borrow() == *b.borrow(),
(V::Symbol(a), V::Symbol(b)) => a == b,
(V::Cell(a), V::Cell(b)) => *a.borrow() == *b.borrow(),
(V::Range(a), V::Range(b)) => match (a.ty, b.ty) {
(Rty::Open, Rty::Open) => a.start == b.start && a.stop == b.stop,
(Rty::Closed, Rty::Closed) => a.start == b.start && a.stop == b.stop,
@ -259,59 +264,6 @@ impl Value {
}
}
pub fn index(&self, idx: Self) -> Result<Self> {
use Value as V;
match (self, idx) {
(V::List(l), V::Int(i)) => {
let l = l.borrow();
if i >= 0 && (i as usize) < l.len() {
Ok(l[i as usize].clone())
} else {
throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for list of length {}", l.len())
}
},
(V::Range(r), V::Int(i)) => {
if i >= 0 && (
r.ty == RangeType::Endless
|| i < r.stop
|| (r.ty == RangeType::Closed && i == r.stop)
) {
Ok((r.start + i).into())
} else {
throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for range {self}")
}
},
(V::Table(t), i) => {
let t = t.borrow();
let i = i.try_into()?;
Ok(t.get(&i).cloned().unwrap_or(Value::Nil))
},
(col, idx) => throw!(*SYM_TYPE_ERROR, "cannot index {col:#} with {idx:#}")
}
}
pub fn store_index(&self, idx: Self, val: Self) -> Result<()> {
use Value as V;
match (self, idx) {
(V::List(l), V::Int(i)) => {
let mut l = l.borrow_mut();
if i >= 0 && (i as usize) < l.len() {
l[i as usize] = val;
Ok(())
} else {
throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for list of length {}", l.len())
}
},
(V::Table(t), i) => {
let mut t = t.borrow_mut();
let i = i.try_into()?;
t.insert(i, val);
Ok(())
},
(col, idx) => throw!(*SYM_INDEX_ERROR, "cannot index {col:#} with {idx:#}")
}
}
pub fn concat(&self, other: &Self) -> Result<Self> {
use Value as V;
match (self, other) {
@ -334,7 +286,7 @@ impl Value {
}
}
pub fn append(&self, val: Self) -> Result<Self> {
pub fn append(&self, val: Self) -> Result<Self> {
use Value as V;
match self {
V::List(list) => {
@ -343,8 +295,8 @@ impl Value {
Ok(l.into())
},
lhs => throw!(*SYM_TYPE_ERROR, "cannot append to {lhs:#}"),
}
}
}
}
pub fn range(&self, other: &Self, closed: bool) -> Result<Self> {
if let (Value::Int(start), Value::Int(stop)) = (self, other) {
@ -363,6 +315,25 @@ impl Value {
}
}
pub fn to_cell(self) -> Self {
Value::Cell(Rc::new(RefCell::new(self)))
}
pub fn iter_unpack(self) -> Result<Option<Rc<RefCell<Self>>>> {
match self {
Self::Nil => Ok(None),
Self::Cell(v) => Ok(Some(v)),
_ => throw!(*SYM_TYPE_ERROR, "expected iterator to return cell or nil")
}
}
pub fn iter_pack(v: Option<Self>) -> Self {
match v {
Some(v) => v.to_cell(),
None => Value::Nil,
}
}
pub fn to_iter_function(self) -> Result<Self> {
match self {
Self::Function(_) | Self::NativeFunc(_) => Ok(self),
@ -370,15 +341,25 @@ impl Value {
let range_iter = RefCell::new(range.into_iter());
let f = move |_: &mut Vm, _: Vec<Value>| -> Result<Value> {
if let Some(v) = range_iter.borrow_mut().next() {
Ok(v.into())
Ok(Value::from(v).to_cell())
} else {
throw!(*SYM_STOP_ITERATOR)
Ok(Value::Nil)
}
};
Ok(NativeFunc {
arity: 0,
f: Box::new(f),
}.into())
Ok(NativeFunc::new(Box::new(f), 0).into())
},
Self::String(s) => {
let byte_pos = RefCell::new(0);
let f = move |_: &mut Vm, _: Vec<Value>| -> Result<Value> {
let pos = *byte_pos.borrow();
if let Some(v) = &s[pos..].chars().next() {
*byte_pos.borrow_mut() += v.len_utf8();
Ok(Value::from(v.to_string()).to_cell())
} else {
Ok(Value::Nil)
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
},
Self::List(list) => {
let idx = RefCell::new(0);
@ -386,32 +367,34 @@ impl Value {
let i = *idx.borrow();
if let Some(v) = list.borrow().get(i) {
*idx.borrow_mut() += 1;
Ok(v.clone())
Ok(v.clone().to_cell())
} else {
throw!(*SYM_STOP_ITERATOR)
Ok(Value::Nil)
}
};
Ok(NativeFunc {
arity: 0,
f: Box::new(f),
}.into())
Ok(NativeFunc::new(Box::new(f), 0).into())
},
Self::Table(table) => {
let keys: Vec<HashValue> = table.borrow().keys().cloned().collect();
let keys = RefCell::new(keys.into_iter());
let f = move |_: &mut Vm, _: Vec<Value>| -> Result<Value> {
if let Some(v) = keys.borrow_mut().next() {
Ok(v.into_inner())
Ok(v.into_inner().to_cell())
} else {
throw!(*SYM_STOP_ITERATOR)
Ok(Value::Nil)
}
};
Ok(NativeFunc {
arity: 0,
f: Box::new(f),
}.into())
Ok(NativeFunc::new(Box::new(f), 0).into())
},
_ => throw!(*SYM_TYPE_ERROR, "cannot iterate {self:#}"),
}
}
pub fn func_attrs(&self) -> Option<FuncAttrs> {
match self {
Value::Function(f) => Some(f.attrs),
Value::NativeFunc(f) => Some(f.attrs),
_ => None,
}
}
}

View file

@ -41,3 +41,4 @@ impl IntoIterator for Range {
}
}

View file

@ -1,6 +1,6 @@
use std::{cmp::Ordering, collections::HashMap, rc::Rc, sync::{atomic::AtomicBool, Arc}};
use crate::{ast::{BinaryOp, UnaryOp}, chunk::Instruction, symbol::{Symbol, SYM_CALL_STACK_OVERFLOW, SYM_INTERRUPTED, SYM_NAME_ERROR, SYM_TYPE_ERROR}, value::{exception::{throw, Exception, Result}, function::Function, Value}};
use crate::{ast::{BinaryOp, UnaryOp}, chunk::Instruction, symbol::{Symbol, SYM_CALL_STACK_OVERFLOW, SYM_INTERRUPTED, SYM_NAME_ERROR, SYM_TYPE_ERROR}, value::{cell_take, exception::{throw, Exception, Result}, function::{FuncAttrs, Function, NativeFunc}, Value}};
struct TryFrame { idx: usize, stack_len: usize }
@ -28,7 +28,7 @@ pub struct Vm {
call_stack: Vec<CallFrame>,
stack_max: usize,
globals: HashMap<Symbol, Value>,
interrupt: Arc<AtomicBool>,
interrupt: Arc<AtomicBool>,
}
pub fn binary_op(o: BinaryOp, a: Value, b: Value) -> Result<Value> {
@ -61,6 +61,49 @@ pub fn unary_op(o: UnaryOp, a: Value) -> Result<Value> {
}
}
enum CallOutcome {
Call(Vec<Value>),
Partial(Value),
}
fn get_call_outcome(mut args: Vec<Value>) -> Result<CallOutcome> {
let f = &args[0];
let Some(attrs) = f.func_attrs() else {
throw!(*SYM_TYPE_ERROR, "cannot call non-function {f:#}")
};
let argc = args.len() - 1;
if attrs.variadic && argc >= attrs.arity {
let vararg = args.split_off(attrs.arity + 1);
args.push(vararg.into());
Ok(CallOutcome::Call(args))
} else if argc == attrs.arity {
Ok(CallOutcome::Call(args))
} else if argc > attrs.arity {
throw!(*SYM_TYPE_ERROR, "too many arguments for function")
} else {
let remaining = attrs.arity - argc;
let f = f.clone();
let nf = move |vm: &mut Vm, inner_args: Vec<Value>| {
let mut ia = inner_args.into_iter();
ia.next();
let mut args: Vec<Value> = args.clone().into_iter().chain(ia).collect();
if attrs.variadic {
let Value::List(varargs) = args.pop()
.expect("did not receive vararg") else {
panic!("did not receive vararg")
};
args.extend(cell_take(varargs));
}
vm.call_value(f.clone(), args)
};
let nf = NativeFunc {
attrs: FuncAttrs { arity: remaining, variadic: attrs.variadic },
func: Box::new(nf),
};
Ok(CallOutcome::Partial(nf.into()))
}
}
impl Vm {
pub fn new(stack_max: usize) -> Self {
Self {
@ -68,13 +111,13 @@ impl Vm {
call_stack: Vec::with_capacity(16),
globals: HashMap::with_capacity(16),
stack_max,
interrupt: Arc::new(AtomicBool::new(false)),
interrupt: Arc::new(AtomicBool::new(false)),
}
}
pub fn get_interrupt(&self) -> Arc<AtomicBool> {
self.interrupt.clone()
}
pub fn get_interrupt(&self) -> Arc<AtomicBool> {
self.interrupt.clone()
}
pub fn set_global(&mut self, name: Symbol, val: Value) {
self.globals.insert(name, val);
@ -88,246 +131,47 @@ impl Vm {
self.globals.get(&name)
}
#[inline]
fn push(&mut self, v: Value) {
self.stack.push(v);
pub fn globals(&self) -> &HashMap<Symbol, Value> {
&self.globals
}
#[inline]
fn pop(&mut self) -> Value {
self.stack.pop().expect("temporary stack underflow")
}
#[inline]
fn pop_n(&mut self, n: usize) -> Vec<Value> {
let res = self.stack.split_off(self.stack.len() - n);
assert!(res.len() == n, "temporary stack underflow");
res
}
fn check_interrupt(&mut self) -> Result<()> {
if self.interrupt.fetch_and(false, std::sync::atomic::Ordering::Relaxed) {
throw!(*SYM_INTERRUPTED)
}
Ok(())
}
fn run_instr(&mut self, frame: &mut CallFrame, instr: Instruction) -> Result<Option<Value>> {
use Instruction as I;
match instr {
// do nothing
I::Nop => (),
// [] -> [locals[n]]
I::LoadLocal(n)
=> self.push(frame.locals[usize::from(n)].clone()),
// [x] -> [], locals[n] = x
I::StoreLocal(n)
=> frame.locals[usize::from(n)] = self.pop(),
// [x] -> [], locals.push(x)
I::NewLocal
=> frame.locals.push(self.pop()),
// locals.pop_n(n)
I::DropLocal(n)
=> frame.locals.truncate(frame.locals.len() - usize::from(n)),
// [] -> [globals[s]]
I::LoadGlobal(s) => {
let sym = unsafe { s.to_symbol_unchecked() };
let v = match self.globals.get(&sym) {
Some(v) => v.clone(),
None => throw!(*SYM_NAME_ERROR, "undefined global {}", sym.name()),
};
self.push(v);
},
// [x] -> [], globals[s] = x
I::StoreGlobal(s) => {
let sym = unsafe { s.to_symbol_unchecked() };
let v = self.pop();
self.globals.insert(sym, v);
},
// [] -> [consts[n]]
I::Const(n)
=> self.push(frame.func.chunk.consts[usize::from(n)].clone()),
// [] -> [nil]
I::Nil => self.push(Value::Nil),
// [] -> [b]
I::Bool(b) => self.push(Value::Bool(b)),
// [] -> [s]
I::Symbol(s) => {
let sym = unsafe { Symbol::from_id_unchecked(u32::from(s)) };
self.push(Value::Symbol(sym));
},
// [] -> [n]
I::Int(n) => self.push(Value::Int(i64::from(n))),
// [x] -> [x,x]
I::Dup => self.push(self.stack[self.stack.len() - 1].clone()),
// [x,y] -> [x,y,x,y]
I::DupTwo => {
self.push(self.stack[self.stack.len() - 2].clone());
self.push(self.stack[self.stack.len() - 2].clone());
},
// [a0,a1...an] -> []
I::Drop(n) => for _ in 0..u32::from(n) { self.pop(); },
// [x,y] -> [y,x]
I::Swap => {
let len = self.stack.len();
self.stack.swap(len - 1, len - 2);
},
// [x,y] -> [y op x]
I::BinaryOp(op) => {
let b = self.pop();
let a = self.pop();
self.push(binary_op(op, a, b)?);
},
// [x] -> [op x]
I::UnaryOp(op) => {
let a = self.pop();
self.push(unary_op(op, a)?);
},
// [a0,a1...an] -.> [[a0,a1...an]]
I::NewList(n) => {
let list = self.pop_n(n as usize);
self.push(list.into())
},
// [l,a0,a1...an] -.> [l ++ [a0,a1...an]]
I::GrowList(n) => {
let ext = self.pop_n(n as usize);
let list = self.pop();
let Value::List(list) = list else { panic!("not a list") };
list.borrow_mut().extend(ext);
self.push(Value::List(list));
},
// [k0,v0...kn,vn] -.> [{k0=v0...kn=vn}]
I::NewTable(n) => {
let mut table = HashMap::new();
for _ in 0..n {
let v = self.pop();
let k = self.pop();
table.insert(k.try_into()?, v);
}
self.push(table.into())
},
// [t,k0,v0...kn,vn] -> [t ++ {k0=v0...kn=vn}]
I::GrowTable(n) => {
let mut ext = self.pop_n(2 * n as usize);
let table = self.pop();
let Value::Table(table) = table else { panic!("not a table") };
let mut table_ref = table.borrow_mut();
for _ in 0..n {
// can't panic: pop_n checked that ext would have len 2*n
let v = ext.pop().unwrap();
let k = ext.pop().unwrap();
table_ref.insert(k.try_into()?, v);
}
drop(table_ref);
self.push(Value::Table(table));
},
// [ct, idx] -> [ct!idx]
I::Index => {
let idx = self.pop();
let ct = self.pop();
self.push(ct.index(idx)?);
},
// [ct, idx, v] -> [v], ct!idx = v
I::StoreIndex => {
let v = self.pop();
let idx = self.pop();
let ct = self.pop();
ct.store_index(idx, v.clone())?;
self.push(v);
},
// ip = n
I::Jump(n) => {
self.check_interrupt()?;
frame.ip = usize::from(n)
},
// [v] ->, [], if v then ip = n
I::JumpTrue(n) => if self.pop().truthy() {
self.check_interrupt()?;
frame.ip = usize::from(n)
},
// [v] ->, [], if not v then ip = n
I::JumpFalse(n) => if !self.pop().truthy() {
self.check_interrupt()?;
frame.ip = usize::from(n)
},
// [v] -> [iter(v)]
I::IterBegin => {
let iter = self.pop().to_iter_function()?;
self.push(iter);
},
// [i] -> if i() succeeds then [i, i()], otherwise [] and ip = n
I::IterNext(n) => {
let v = &self.stack[self.stack.len() - 1];
self.call_stack.push(std::mem::take(frame));
let res = self.call_value(v.clone(), vec![v.clone()]);
*frame = self.call_stack.pop().expect("no frame to pop");
match res {
Ok(res) => self.push(res),
Err(e) => if e.ty == Symbol::get("stop_iterator") {
self.pop();
frame.ip = usize::from(n)
} else {
return Err(e)
}
}
},
// try_frames.push(t, stack.len())
I::BeginTry(t) => {
let tryframe = TryFrame {
idx: usize::from(t),
stack_len: self.stack.len()
};
frame.try_frames.push(tryframe);
},
// try_frames.pop()
I::EndTry => {
frame.try_frames.pop().expect("no try to pop");
},
// [f,a0,a1...an] -> [f(a0,a1...an)]
I::Call(n) => {
self.check_interrupt()?;
let n = usize::from(n);
let args = self.pop_n(n + 1);
let func = &args[0];
if let Value::NativeFunc(nf) = func {
let nf = nf.clone();
if nf.arity != n {
throw!(*SYM_TYPE_ERROR, "function call with wrong argument count");
}
self.call_stack.push(std::mem::take(frame));
let res = (nf.f)(self, args)?;
*frame = self.call_stack.pop().expect("no frame to pop");
self.stack.push(res);
} else if let Value::Function(func) = func {
if func.arity != n {
throw!(*SYM_TYPE_ERROR, "function call with wrong argument count");
}
if self.call_stack.len() + 1 >= self.stack_max {
throw!(*SYM_CALL_STACK_OVERFLOW, "call stack overflow")
}
self.call_stack.push(std::mem::take(frame));
let func = func.clone();
*frame = CallFrame::new(func, args);
} else {
throw!(*SYM_TYPE_ERROR, "attempt to call non-function {func}");
}
},
// [v] -> [], return v
I::Return if frame.root => {
return Ok(Some(self.pop()));
},
// [v] -> [], return v
I::Return => {
*frame = self.call_stack.pop().expect("no root frame");
},
pub fn call_value(&mut self, value: Value, args: Vec<Value>) -> Result<Value> {
self.check_interrupt()?;
match get_call_outcome(args)? {
CallOutcome::Partial(v) => Ok(v),
CallOutcome::Call(args) => match value {
Value::Function(f) => self.run_function(f, args),
Value::NativeFunc(f) => (f.func)(self, args),
_ => unreachable!("already verified by calling get_call_type")
}
}
}
Ok(None)
pub fn run_function(&mut self, func: Rc<Function>, args: Vec<Value>) -> Result<Value> {
if func.attrs.arity + 1 != args.len() {
throw!(*SYM_TYPE_ERROR, "function call with wrong argument count");
}
let init_stack_len = self.stack.len();
let mut frame = CallFrame::new(func, args);
frame.root = true;
loop {
let instr = frame.func.chunk.instrs[frame.ip];
frame.ip += 1;
match self.run_instr(&mut frame, instr) {
Ok(None) => (),
Ok(Some(v)) => {
self.stack.truncate(init_stack_len);
return Ok(v)
}
Err(e) => {
if let Err(e) = self.handle_exception(&mut frame, e) {
self.stack.truncate(init_stack_len);
return Err(e)
}
}
}
}
}
fn handle_exception(&mut self, frame: &mut CallFrame, exc: Exception) -> Result<()> {
@ -351,49 +195,265 @@ impl Vm {
}
}
pub fn call_value(&mut self, value: Value, args: Vec<Value>) -> Result<Value> {
match value {
Value::Function(f) => self.call(f, args),
Value::NativeFunc(f) => {
if f.arity + 1 != args.len() {
throw!(*SYM_TYPE_ERROR, "function call with wrong argument count");
}
(f.f)(self, args)
},
_ => throw!(*SYM_TYPE_ERROR, "not a function"),
}
}
pub fn call_no_args(&mut self, func: Rc<Function>) -> Result<Value> {
self.call(func.clone(), vec![func.into()])
}
pub fn call(&mut self, func: Rc<Function>, args: Vec<Value>) -> Result<Value> {
if func.arity + 1 != args.len() {
throw!(*SYM_TYPE_ERROR, "function call with wrong argument count");
}
let init_stack_len = self.stack.len();
let mut frame = CallFrame::new(func, args);
frame.root = true;
loop {
let instr = frame.func.chunk.instrs[frame.ip];
frame.ip += 1;
match self.run_instr(&mut frame, instr) {
Ok(None) => (),
Ok(Some(v)) => {
self.stack.truncate(init_stack_len);
return Ok(v)
}
Err(e) => {
if let Err(e) = self.handle_exception(&mut frame, e) {
self.stack.truncate(init_stack_len);
return Err(e)
}
}
}
}
#[inline]
fn push(&mut self, v: Value) {
self.stack.push(v);
}
#[inline]
fn pop(&mut self) -> Value {
self.stack.pop().expect("temporary stack underflow")
}
#[inline]
fn pop_n(&mut self, n: usize) -> Vec<Value> {
let res = self.stack.split_off(self.stack.len() - n);
assert!(res.len() == n, "temporary stack underflow");
res
}
fn check_interrupt(&mut self) -> Result<()> {
if self.interrupt.fetch_and(false, std::sync::atomic::Ordering::Relaxed) {
throw!(*SYM_INTERRUPTED)
}
Ok(())
}
fn run_instr(&mut self, frame: &mut CallFrame, instr: Instruction) -> Result<Option<Value>> {
use Instruction as I;
match instr {
// do nothing
I::Nop => (),
// [] -> [locals[n]]
I::LoadLocal(n)
=> self.push(frame.locals[usize::from(n)].clone()),
// [x] -> [], locals[n] = x
I::StoreLocal(n)
=> frame.locals[usize::from(n)] = self.pop(),
// [x] -> [], locals.push(x)
I::NewLocal
=> frame.locals.push(self.pop()),
// locals.pop_n(n)
I::DropLocal(n)
=> frame.locals.truncate(frame.locals.len() - usize::from(n)),
// [] -> [globals[s]]
I::LoadGlobal(s) => {
let sym = unsafe { s.to_symbol_unchecked() };
let v = match self.globals.get(&sym) {
Some(v) => v.clone(),
None => throw!(*SYM_NAME_ERROR, "undefined global {}", sym.name()),
};
self.push(v);
},
// [x] -> [], globals[s] = x
I::StoreGlobal(s) => {
let sym = unsafe { s.to_symbol_unchecked() };
let v = self.pop();
self.globals.insert(sym, v);
},
// [] -> [consts[n]]
I::Const(n)
=> self.push(frame.func.chunk.consts[usize::from(n)].clone()),
// [] -> [nil]
I::Nil => self.push(Value::Nil),
// [] -> [b]
I::Bool(b) => self.push(Value::Bool(b)),
// [] -> [s]
I::Symbol(s) => {
let sym = unsafe { Symbol::from_id_unchecked(u32::from(s)) };
self.push(Value::Symbol(sym));
},
// [] -> [n]
I::Int(n) => self.push(Value::Int(i64::from(n))),
// [x] -> [x,x]
I::Dup => self.push(self.stack[self.stack.len() - 1].clone()),
// [x,y] -> [x,y,x,y]
I::DupTwo => {
self.push(self.stack[self.stack.len() - 2].clone());
self.push(self.stack[self.stack.len() - 2].clone());
},
// [a0,a1...an] -> []
I::Drop(n) => for _ in 0..u32::from(n) { self.pop(); },
// [x,y] -> [y,x]
I::Swap => {
let len = self.stack.len();
self.stack.swap(len - 1, len - 2);
},
// [x,y] -> [y op x]
I::BinaryOp(op) => {
let b = self.pop();
let a = self.pop();
self.push(binary_op(op, a, b)?);
},
// [x] -> [op x]
I::UnaryOp(op) => {
let a = self.pop();
self.push(unary_op(op, a)?);
},
// [a0,a1...an] -.> [[a0,a1...an]]
I::NewList(n) => {
let list = self.pop_n(n as usize);
self.push(list.into())
},
// [l,a0,a1...an] -.> [l ++ [a0,a1...an]]
I::GrowList(n) => {
let ext = self.pop_n(n as usize);
let list = self.pop();
let Value::List(list) = list else { panic!("not a list") };
list.borrow_mut().extend(ext);
self.push(Value::List(list));
},
// [k0,v0...kn,vn] -.> [{k0=v0...kn=vn}]
I::NewTable(n) => {
let mut table = HashMap::new();
for _ in 0..n {
let v = self.pop();
let k = self.pop();
table.insert(k.try_into()?, v);
}
self.push(table.into())
},
// [t,k0,v0...kn,vn] -> [t ++ {k0=v0...kn=vn}]
I::GrowTable(n) => {
let mut ext = self.pop_n(2 * n as usize);
let table = self.pop();
let Value::Table(table) = table else { panic!("not a table") };
let mut table_ref = table.borrow_mut();
for _ in 0..n {
// can't panic: pop_n checked that ext would have len 2*n
let v = ext.pop().unwrap();
let k = ext.pop().unwrap();
table_ref.insert(k.try_into()?, v);
}
drop(table_ref);
self.push(Value::Table(table));
},
// [ct, idx] -> [ct!idx]
I::Index => {
let idx = self.pop();
let ct = self.pop();
self.push(ct.index(idx)?);
},
// [ct, idx, v] -> [v], ct!idx = v
I::StoreIndex => {
let v = self.pop();
let idx = self.pop();
let ct = self.pop();
ct.store_index(self, idx, v.clone())?;
self.push(v);
},
// ip = n
I::Jump(n) => {
self.check_interrupt()?;
frame.ip = usize::from(n)
},
// [v] ->, [], if v then ip = n
I::JumpTrue(n) => if self.pop().truthy() {
self.check_interrupt()?;
frame.ip = usize::from(n)
},
// [v] ->, [], if not v then ip = n
I::JumpFalse(n) => if !self.pop().truthy() {
self.check_interrupt()?;
frame.ip = usize::from(n)
},
// [v] -> [iter(v)]
I::IterBegin => {
let iter = self.pop().to_iter_function()?;
self.push(iter);
},
// [i,cell(v)] -> [i,v]
// [i,nil] -> [], ip = n
I::IterTest(n) => {
match self.pop() {
Value::Cell(c) => self.push(cell_take(c)),
Value::Nil => {
self.pop();
frame.ip = usize::from(n)
},
v => throw!(*SYM_TYPE_ERROR, "iterator returned invalid value {v}")
}
},
// try_frames.push(t, stack.len())
I::BeginTry(t) => {
let tryframe = TryFrame {
idx: usize::from(t),
stack_len: self.stack.len()
};
frame.try_frames.push(tryframe);
},
// try_frames.pop()
I::EndTry => {
frame.try_frames.pop().expect("no try to pop");
},
// [f,a0,a1...an] -> [f(a0,a1...an)]
I::Call(n) => {
let n = usize::from(n);
let args = self.pop_n(n + 1);
let args = match get_call_outcome(args)? {
CallOutcome::Call(args) => args,
CallOutcome::Partial(v) => {
self.push(v);
return Ok(None)
}
};
if let Value::NativeFunc(nf) = &args[0] {
let nf = nf.clone();
self.call_stack.push(std::mem::take(frame));
let res = (nf.func)(self, args)?;
*frame = self.call_stack.pop().expect("no frame to pop");
self.stack.push(res);
} else if let Value::Function(func) = &args[0] {
if self.call_stack.len() + 1 >= self.stack_max {
throw!(*SYM_CALL_STACK_OVERFLOW, "call stack overflow")
}
self.call_stack.push(std::mem::take(frame));
let func = func.clone();
*frame = CallFrame::new(func, args);
} 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
I::Return => {
self.check_interrupt()?;
*frame = self.call_stack.pop().expect("no root frame");
},
}
Ok(None)
}
}
#[macro_export]
macro_rules! vmcall {
($vm:expr; $func:expr, $($arg:expr),*) => {{
let f = $func;
$vm.call_value(f.clone(), vec![f, $($arg),*])
}};
($vm:expr; $func:expr) => {{
let f = $func;
$vm.call_value(f.clone(), vec![f])
}};
}
#[macro_export]
macro_rules! vmcalliter {
($($input:tt)*) => {
$crate::vmcall!($($input)*).and_then(|v| v.iter_unpack())
}
}

View file

@ -1,32 +1,52 @@
use proc_macro::TokenStream;
use syn::{LitInt, ItemFn};
use syn::{parse::Parse, parse_macro_input, ItemFn, LitInt, Token};
use quote::quote;
struct NativeFuncArgs {
arity: LitInt,
variadic: Option<Token![..]>,
}
impl Parse for NativeFuncArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let arity = input.parse()?;
let variadic = input.parse()?;
Ok(Self { arity, variadic })
}
}
#[proc_macro_attribute]
pub fn native_func(input: TokenStream, annotated_item: TokenStream) -> TokenStream {
let itemfn: ItemFn = syn::parse(annotated_item).unwrap();
let arity: LitInt = syn::parse(input).unwrap();
let Ok(itemfn) = syn::parse::<ItemFn>(annotated_item.clone()) else {
return annotated_item
};
let args: NativeFuncArgs = parse_macro_input!(input as NativeFuncArgs);
let visibility = itemfn.vis;
let block = itemfn.block;
let name = itemfn.sig.ident;
let inputs = itemfn.sig.inputs;
let output = itemfn.sig.output;
let visibility = itemfn.vis;
let block = itemfn.block;
let name = itemfn.sig.ident;
let inputs = itemfn.sig.inputs;
let output = itemfn.sig.output;
let arity = args.arity;
let variadic = args.variadic.is_some();
assert!(itemfn.sig.constness.is_none(), "item must not be const");
assert!(itemfn.sig.asyncness.is_none(), "item must not be async");
assert!(itemfn.sig.unsafety.is_none(), "item must not be unsafe");
assert!(itemfn.sig.abi.is_none(), "item must not contain an ABI specifier");
assert!(itemfn.sig.variadic.is_none(), "item must not be variadic");
assert!(itemfn.sig.constness.is_none(), "item must not be const");
assert!(itemfn.sig.asyncness.is_none(), "item must not be async");
assert!(itemfn.sig.unsafety.is_none(), "item must not be unsafe");
assert!(itemfn.sig.abi.is_none(), "item must not contain an ABI specifier");
assert!(itemfn.sig.variadic.is_none(), "item must not be variadic");
let expanded = quote! {
#visibility fn #name() -> ::talc_lang::value::function::NativeFunc {
::talc_lang::value::function::NativeFunc {
arity: #arity,
f: Box::new(|#inputs| #output #block)
}
}
};
TokenStream::from(expanded)
let expanded = quote! {
#visibility fn #name() -> ::talc_lang::value::function::NativeFunc {
::talc_lang::value::function::NativeFunc {
attrs: ::talc_lang::value::function::FuncAttrs{
arity: #arity,
variadic: #variadic,
},
func: Box::new(|#inputs| #output #block)
}
}
};
TokenStream::from(expanded)
}

View file

@ -7,3 +7,8 @@ edition = "2021"
talc-lang = { path = "../talc-lang" }
talc-macros = { path = "../talc-macros" }
lazy_static = "1.4"
rand = { version = "0.8", optional = true }
[features]
default = ["random"]
random = ["dep:rand"]

208
talc-std/src/collection.rs Normal file
View file

@ -0,0 +1,208 @@
use std::cmp::Ordering;
use talc_lang::{exception, symbol::SYM_TYPE_ERROR, throw, value::{exception::Result, function::NativeFunc, Value}, vmcall, Vm};
use talc_macros::native_func;
use crate::unpack_args;
pub fn load(vm: &mut Vm) {
vm.set_global_name("push", push().into());
vm.set_global_name("pop", pop().into());
vm.set_global_name("reverse", reverse().into());
vm.set_global_name("clear", clear().into());
vm.set_global_name("sort", sort().into());
vm.set_global_name("sort_by", sort_by().into());
vm.set_global_name("sort_key", sort_key().into());
}
#[native_func(2)]
pub fn push(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, list, item] = unpack_args!(args);
let Value::List(list) = list else {
throw!(*SYM_TYPE_ERROR, "push expected list, found {list:#}")
};
list.borrow_mut().push(item);
Ok(Value::Nil)
}
#[native_func(1)]
pub fn pop(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, list] = unpack_args!(args);
let Value::List(list) = list else {
throw!(*SYM_TYPE_ERROR, "pop expected list, found {list:#}")
};
let v = list.borrow_mut().pop();
v.ok_or_else(|| exception!(*SYM_TYPE_ERROR, "attempt to pop empty list"))
}
#[native_func(1)]
pub fn reverse(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, list] = unpack_args!(args);
let Value::List(list) = list else {
throw!(*SYM_TYPE_ERROR, "reversed expected list, found {list:#}")
};
list.borrow_mut().reverse();
Ok(Value::List(list))
}
#[native_func(1)]
pub fn clear(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, col] = unpack_args!(args);
match &col {
Value::List(list) => list.borrow_mut().clear(),
Value::Table(table) => table.borrow_mut().clear(),
_ => throw!(*SYM_TYPE_ERROR, "clear expected list or table, found {col:#}")
}
Ok(col)
}
fn call_comparison(vm: &mut Vm, by: Value, l: Value, r: Value) -> Result<i64> {
let ord = vmcall!(vm; by, l, r)?;
let Value::Int(ord) = ord else {
throw!(*SYM_TYPE_ERROR, "comparison function should return an integer")
};
Ok(ord)
}
fn partition(vals: &mut [Value]) -> (usize, usize) {
let pivot = vals[vals.len() / 2].clone();
let mut lt = 0;
let mut eq = 0;
let mut gt = vals.len() - 1;
while eq <= gt {
let ord = vals[eq].partial_cmp(&pivot);
match ord {
Some(Ordering::Less) => {
vals.swap(eq, lt);
lt += 1;
eq += 1;
},
Some(Ordering::Greater) => {
vals.swap(eq, gt);
gt -= 1;
},
Some(Ordering::Equal) | None => {
eq += 1;
},
}
}
(lt, gt)
}
fn partition_by(vals: &mut [Value], by: &Value, vm: &mut Vm) -> Result<(usize, usize)> {
let pivot = vals[vals.len() / 2].clone();
let mut lt = 0;
let mut eq = 0;
let mut gt = vals.len() - 1;
while eq <= gt {
let ord = call_comparison(vm, by.clone(), vals[eq].clone(), pivot.clone())?;
match ord {
..=-1 => {
vals.swap(eq, lt);
lt += 1;
eq += 1;
},
1.. => {
vals.swap(eq, gt);
gt -= 1;
},
0 => {
eq += 1;
},
}
}
Ok((lt, gt))
}
fn insertion(vals: &mut [Value]) {
for i in 0..vals.len() {
let mut j = i;
while j > 0 && vals[j-1] > vals[j] {
vals.swap(j, j-1);
j -= 1;
}
}
}
fn insertion_by(vals: &mut [Value], by: &Value, vm: &mut Vm) -> Result<()> {
for i in 0..vals.len() {
let mut j = i;
while j > 0 {
let ord = call_comparison(vm, by.clone(), vals[j-1].clone(), vals[j].clone())?;
if ord <= 0 {
break;
}
vals.swap(j, j-1);
j -= 1;
}
}
Ok(())
}
fn sort_inner(vals: &mut [Value], by: Option<&Value>, vm: &mut Vm) -> Result<()> {
if vals.len() <= 1 {
return Ok(())
}
if vals.len() <= 8 {
match by {
Some(by) => insertion_by(vals, by, vm)?,
None => insertion(vals),
}
return Ok(())
}
let (lt, gt) = match by {
Some(by) => partition_by(vals, by, vm)?,
None => partition(vals),
};
sort_inner(&mut vals[..lt], by, vm)?;
sort_inner(&mut vals[(gt+1)..], by, vm)
}
#[native_func(1)]
pub fn sort(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, list] = unpack_args!(args);
let Value::List(list) = list else {
throw!(*SYM_TYPE_ERROR, "sort expected list, found {list:#}")
};
sort_inner(&mut list.borrow_mut(), None, vm)?;
Ok(list.into())
}
#[native_func(2)]
pub fn sort_by(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, by, list] = unpack_args!(args);
let Value::List(list) = list else {
throw!(*SYM_TYPE_ERROR, "sort expected list, found {list:#}")
};
sort_inner(&mut list.borrow_mut(), Some(&by), vm)?;
Ok(list.into())
}
#[native_func(2)]
pub fn sort_key(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, key, list] = unpack_args!(args);
let Value::List(list) = list else {
throw!(*SYM_TYPE_ERROR, "sort expected list, found {list:#}")
};
let f = move |vm: &mut Vm, args: Vec<Value>| {
let [_, a, b] = unpack_args!(args);
let a = vmcall!(vm; key.clone(), a)?;
let b = vmcall!(vm; key.clone(), b)?;
match a.partial_cmp(&b) {
Some(Ordering::Greater) => Ok(Value::Int(1)),
Some(Ordering::Equal) => Ok(Value::Int(0)),
Some(Ordering::Less) => Ok(Value::Int(-1)),
None => throw!(*SYM_TYPE_ERROR, "values returned from sort key were incomparable"),
}
};
let nf = NativeFunc::new(Box::new(f), 2).into();
sort_inner(&mut list.borrow_mut(), Some(&nf), vm)?;
Ok(list.into())
}

View file

@ -1,31 +1,40 @@
use talc_lang::{symbol::SYM_TYPE_ERROR, value::{exception::{throw, Exception, Result}, Value}, Vm};
use talc_macros::native_func;
#[native_func(2)]
use crate::{unpack_args, unpack_varargs};
#[native_func(1..)]
pub fn throw(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, ty, msg] = args.try_into().expect("bypassed arity check");
let Value::Symbol(ty) = ty else {
throw!(*SYM_TYPE_ERROR, "exception type must be a symbol")
};
match msg {
Value::String(msg) => Err(Exception::new_with_msg(ty, msg)),
Value::Nil => Err(Exception::new(ty)),
_ => throw!(*SYM_TYPE_ERROR, "exception message must be a string")
}
let ([_, ty], varargs) = unpack_varargs!(args);
let Value::Symbol(ty) = ty else {
throw!(*SYM_TYPE_ERROR, "exception type must be a symbol")
};
Err(match &*varargs {
[] | [Value::Nil]
=> Exception::new(ty),
[Value::Nil, data]
=> Exception::new_with_data(ty, data.clone()),
[Value::String(s)]
=> Exception::new_with_msg(ty, s.clone()),
[Value::String(s), data]
=> Exception::new_with_msg_data(ty, s.clone(), data.clone()),
[_] | [_,_] => throw!(*SYM_TYPE_ERROR, "wrong arguments for throw"),
[_,_,_,..] => throw!(*SYM_TYPE_ERROR, "too many arguments for throw"),
})
}
#[native_func(1)]
pub fn rethrow(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, table] = args.try_into().expect("bypassed arity check");
if let Value::Table(table) = table {
if let Some(e) = Exception::from_table(&table) {
return Err(e)
}
}
throw!(*SYM_TYPE_ERROR, "argument not a valid exception")
let [_, table] = unpack_args!(args);
if let Value::Table(table) = table {
if let Some(e) = Exception::from_table(&table) {
return Err(e)
}
}
throw!(*SYM_TYPE_ERROR, "argument not a valid exception")
}
pub fn load(vm: &mut Vm) {
vm.set_global_name("throw", throw().into());
vm.set_global_name("rethrow", rethrow().into());
vm.set_global_name("throw", throw().into());
vm.set_global_name("rethrow", rethrow().into());
}

View file

@ -1,37 +1,70 @@
use std::io::Write;
use std::{io::Write, time::{SystemTime, UNIX_EPOCH}};
use talc_lang::{value::{exception::{throw, Result}, Value}, Vm};
use talc_lang::{value::{exception::{throw, Result}, Value}, vmcall, Vm};
use talc_macros::native_func;
use crate::SYM_IO_ERROR;
use crate::{unpack_args, SYM_IO_ERROR};
#[native_func(1)]
pub fn print(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
if let Err(e) = write!(std::io::stdout(), "{}", args[1]) {
throw!(*SYM_IO_ERROR, "{e}")
}
Ok(Value::Nil)
if let Err(e) = write!(std::io::stdout(), "{}", args[1]) {
throw!(*SYM_IO_ERROR, "{e}")
}
Ok(Value::Nil)
}
#[native_func(1)]
pub fn println(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
if let Err(e) = writeln!(std::io::stdout(), "{}", args[1]) {
throw!(*SYM_IO_ERROR, "{e}")
}
Ok(Value::Nil)
if let Err(e) = writeln!(std::io::stdout(), "{}", args[1]) {
throw!(*SYM_IO_ERROR, "{e}")
}
Ok(Value::Nil)
}
#[native_func(0)]
pub fn readln(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
let mut buf = String::new();
if let Err(e) = std::io::stdin().read_line(&mut buf) {
throw!(*SYM_IO_ERROR, "{e}")
}
Ok(buf.into())
let mut buf = String::new();
if let Err(e) = std::io::stdin().read_line(&mut buf) {
throw!(*SYM_IO_ERROR, "{e}")
}
Ok(buf.into())
}
#[native_func(0)]
pub fn time(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
let time = SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards");
Ok((time.as_secs() as i64).into())
}
#[native_func(0)]
pub fn time_ms(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
let time = SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards");
Ok((time.as_millis() as i64).into())
}
#[native_func(0)]
pub fn time_ns(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
let time = SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards");
Ok(vec![(time.as_secs() as i64).into(), (time.subsec_nanos() as i64).into()].into())
}
#[native_func(1)]
pub fn time_fn(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, func] = unpack_args!(args);
let t0 = SystemTime::now();
vmcall!(vm; func)?;
let tf = SystemTime::now();
let time = tf.duration_since(t0).expect("time went backwards");
Ok((time.as_nanos() as i64).into())
}
pub fn load(vm: &mut Vm) {
vm.set_global_name("print", print().into());
vm.set_global_name("println", println().into());
vm.set_global_name("readln", readln().into());
vm.set_global_name("print", print().into());
vm.set_global_name("println", println().into());
vm.set_global_name("readln", readln().into());
vm.set_global_name("time", time().into());
vm.set_global_name("time_ms", time_ms().into());
vm.set_global_name("time_ns", time_ns().into());
vm.set_global_name("time_fn", time_fn().into());
}

View file

@ -1,42 +1,829 @@
use talc_lang::{symbol::SYM_STOP_ITERATOR, value::{exception::Result, function::NativeFunc, Value}, Vm};
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use talc_lang::{symbol::SYM_TYPE_ERROR, throw, value::{cell_take, exception::Result, function::NativeFunc, range::RangeType, HashValue, Value}, vmcall, vmcalliter, Vm};
use talc_macros::native_func;
use crate::{unpack_args, unpack_varargs};
pub fn load(vm: &mut Vm) {
vm.set_global_name("iter", iter().into());
vm.set_global_name("pairs", pairs().into());
vm.set_global_name("once", once().into());
vm.set_global_name("forever", forever().into());
vm.set_global_name("do_forever", forever().into());
vm.set_global_name("map", map().into());
vm.set_global_name("scan", scan().into());
vm.set_global_name("tee", tee().into());
vm.set_global_name("filter", filter().into());
vm.set_global_name("filter_map", filter_map().into());
vm.set_global_name("take", take().into());
vm.set_global_name("skip", skip().into());
vm.set_global_name("enumerate", enumerate().into());
vm.set_global_name("zip", zip().into());
vm.set_global_name("alternate", alternate().into());
vm.set_global_name("intersperse", intersperse().into());
vm.set_global_name("cartprod", cartprod().into());
vm.set_global_name("cycle", cycle().into());
vm.set_global_name("chain", chain().into());
vm.set_global_name("step", step().into());
vm.set_global_name("rev", rev().into());
vm.set_global_name("list", list().into());
vm.set_global_name("table", table().into());
vm.set_global_name("len", len().into());
vm.set_global_name("fold", fold().into());
vm.set_global_name("foldi", foldi().into());
vm.set_global_name("sum", sum().into());
vm.set_global_name("prod", prod().into());
vm.set_global_name("any", any().into());
vm.set_global_name("all", all().into());
vm.set_global_name("last", last().into());
vm.set_global_name("nth", nth().into());
vm.set_global_name("contains", contains().into());
vm.set_global_name("index_of", index_of().into());
vm.set_global_name("index_if", index_if().into());
vm.set_global_name("find", find().into());
vm.set_global_name("count", count().into());
}
//
// begin iteration
//
#[native_func(1)]
pub fn iter(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = args.try_into().expect("bypassed arity check");
v.to_iter_function()
}
let [_, v] = unpack_args!(args);
#[native_func(2)]
pub fn map(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, func, iter] = args.try_into().expect("bypassed arity check");
let iter = iter.to_iter_function()?;
let f = move |vm: &mut Vm, _| {
let next = vm.call_value(iter.clone(), vec![iter.clone()])?;
vm.call_value(func.clone(), vec![func.clone(), next])
};
Ok(NativeFunc { arity: 0, f: Box::new(f) }.into())
v.to_iter_function()
}
#[native_func(1)]
pub fn list(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = args.try_into().expect("bypassed arity check");
let iter = iter.to_iter_function()?;
let mut result = Vec::new();
loop {
match vm.call_value(iter.clone(), vec![iter.clone()]) {
Ok(v) => result.push(v),
Err(e) => if e.ty == *SYM_STOP_ITERATOR {
return Ok(result.into())
} else {
return Err(e)
}
}
};
pub fn pairs(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, table] = unpack_args!(args);
let Value::Table(table) = table else {
throw!(*SYM_TYPE_ERROR, "pairs expected table, found {table:#}")
};
let keys: Vec<HashValue> = table.borrow().keys().cloned().collect();
let keys = RefCell::new(keys.into_iter());
let f = move |_: &mut Vm, _: Vec<Value>| -> Result<Value> {
if let Some(k) = keys.borrow_mut().next() {
let v = table.borrow().get(&k).cloned().unwrap_or(Value::Nil);
Ok(Value::from(vec![k.into_inner(), v]).to_cell())
} else {
Ok(Value::Nil)
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
pub fn load(vm: &mut Vm) {
vm.set_global_name("iter", iter().into());
vm.set_global_name("map", map().into());
vm.set_global_name("list", list().into());
#[native_func(1)]
pub fn once(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, val] = unpack_args!(args);
let v = RefCell::new(Some(val));
let f = move |_: &mut Vm, _| {
if let Some(v) = v.borrow_mut().take() {
Ok(v.to_cell())
} else {
Ok(Value::Nil)
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(1)]
pub fn forever(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, val] = unpack_args!(args);
let f = move |_: &mut Vm, _| {
Ok(val.clone().to_cell())
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(1)]
pub fn do_forever(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, func] = unpack_args!(args);
let f = move |vm: &mut Vm, _| {
Ok(vmcall!(vm; func.clone())?.to_cell())
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
//
// chain iteration
//
#[native_func(2)]
pub fn map(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, map, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let f = move |vm: &mut Vm, _| {
match vmcalliter!(vm; iter.clone())? {
Some(val) => Ok(vmcall!(vm; map.clone(), cell_take(val))?.to_cell()),
None => Ok(Value::Nil),
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(2)]
pub fn tee(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, tee, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let f = move |vm: &mut Vm, _| {
match vmcalliter!(vm; iter.clone())? {
Some(val) => {
vmcall!(vm; tee.clone(), cell_take(val.clone()))?;
Ok(val.into())
}
None => Ok(Value::Nil),
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(3)]
pub fn scan(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, init, func, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let result = RefCell::new(init);
let f = move |vm: &mut Vm, _| {
match vmcalliter!(vm; iter.clone())? {
Some(val) => {
let val = cell_take(val);
let r = vmcall!(vm; func.clone(), result.take(), val)?;
*result.borrow_mut() = r.clone();
Ok(r.to_cell())
},
None => {
result.take();
Ok(Value::Nil)
},
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(2)]
pub fn filter(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, filter, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let f = move |vm: &mut Vm, _| {
loop {
let next = match vmcalliter!(vm; iter.clone())? {
Some(next) => next,
None => return Ok(Value::Nil),
};
let res = vmcall!(vm; filter.clone(), cell_take(next.clone()))?;
if res.truthy() {
return Ok(next.into())
}
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(2)]
pub fn filter_map(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, fmap, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let f = move |vm: &mut Vm, _| {
loop {
let next = match vmcalliter!(vm; iter.clone())? {
Some(next) => next,
None => return Ok(Value::Nil),
};
let res = vmcall!(vm; fmap.clone(), cell_take(next))?;
if let Value::Cell(_) = res {
return Ok(res)
}
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(2)]
pub fn take(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, count, iter] = unpack_args!(args);
let Value::Int(count) = count else {
throw!(*SYM_TYPE_ERROR, "take expected integer")
};
let Ok(count) = count.try_into() else {
throw!(*SYM_TYPE_ERROR, "take expected nonnegative integer")
};
let iter = iter.to_iter_function()?;
let taken = RefCell::new(0);
let f = move |vm: &mut Vm, _| {
if *taken.borrow() >= count {
return Ok(Value::Nil)
}
let next = match vmcalliter!(vm; iter.clone())? {
Some(next) => next,
None => {
*taken.borrow_mut() = count;
return Ok(Value::Nil)
}
};
*taken.borrow_mut() += 1;
Ok(next.into())
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(2)]
pub fn skip(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, count, iter] = unpack_args!(args);
let Value::Int(count) = count else {
throw!(*SYM_TYPE_ERROR, "count expected integer")
};
let Ok(count) = count.try_into() else {
throw!(*SYM_TYPE_ERROR, "count expected nonnegative integer")
};
let iter = iter.to_iter_function()?;
let skipped = RefCell::new(false);
let f = move |vm: &mut Vm, _| {
if *skipped.borrow() {
return vmcall!(vm; iter.clone())
}
*skipped.borrow_mut() = true;
for _ in 0..count {
vmcall!(vm; iter.clone())?;
}
vmcall!(vm; iter.clone())
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(1)]
pub fn enumerate(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let counter = RefCell::new(0);
let f = move |vm: &mut Vm, _| {
let next = match vmcalliter!(vm; iter.clone())? {
Some(next) => cell_take(next),
None => return Ok(Value::Nil),
};
let mut c = counter.borrow_mut();
let n = *c;
*c += 1;
Ok(Value::from(vec![n.into(), next]).to_cell())
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(2..)]
pub fn zip(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let ([_, i1, i2], rest) = unpack_varargs!(args);
let mut iters = Vec::with_capacity(2 + rest.len());
for i in [i1, i2].into_iter().chain(rest.into_iter()) {
iters.push(i.to_iter_function()?);
}
let f = move |vm: &mut Vm, _| {
let mut res = Vec::with_capacity(iters.len());
for i in iters.iter() {
match vmcalliter!(vm; i.clone())? {
Some(v) => res.push(cell_take(v)),
None => return Ok(Value::Nil),
};
}
Ok(Value::from(res).to_cell())
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(2..)]
pub fn alternate(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let ([_, i1, i2], rest) = unpack_varargs!(args);
let mut iters = Vec::with_capacity(2 + rest.len());
for i in [i1, i2].into_iter().chain(rest.into_iter()) {
iters.push(i.to_iter_function()?);
}
let state = RefCell::new((0, false));
let f = move |vm: &mut Vm, _| {
let mut s = state.borrow_mut();
if s.1 {
return Ok(Value::Nil);
}
let n = s.0;
s.0 = (s.0 + 1) % iters.len();
drop(s);
match vmcalliter!(vm; iters[n].clone())? {
Some(v) => Ok(v.into()),
None => {
state.borrow_mut().1 = true;
Ok(Value::Nil)
}
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[derive(Default)]
enum Intersperse {
Init, Waiting, HasNext(Rc<RefCell<Value>>), #[default] End
}
#[native_func(2)]
pub fn intersperse(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, val, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let val = val.to_cell();
let state = RefCell::new(Intersperse::Init);
let f = move |vm: &mut Vm, _| {
match state.take() {
Intersperse::Init => match vmcalliter!(vm; iter.clone())? {
Some(v) => {
*state.borrow_mut() = Intersperse::Waiting;
Ok(v.into())
},
None => {
*state.borrow_mut() = Intersperse::End;
Ok(Value::Nil)
},
},
Intersperse::Waiting => match vmcalliter!(vm; iter.clone())? {
Some(v) => {
*state.borrow_mut() = Intersperse::HasNext(v);
Ok(val.clone())
},
None => {
*state.borrow_mut() = Intersperse::End;
Ok(Value::Nil)
},
},
Intersperse::HasNext(v) => {
*state.borrow_mut() = Intersperse::Waiting;
Ok(v.into())
},
Intersperse::End => Ok(Value::Nil),
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(2)]
pub fn chain(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter1, iter2] = unpack_args!(args);
let iter1 = iter1.to_iter_function()?;
let iter2 = iter2.to_iter_function()?;
let done_first = RefCell::new(false);
let f = move |vm: &mut Vm, _| {
if *done_first.borrow() {
return vmcall!(vm; iter2.clone())
}
match vmcalliter!(vm; iter1.clone())? {
Some(v) => Ok(v.into()),
None => {
*done_first.borrow_mut() = true;
vmcall!(vm; iter2.clone())
}
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[derive(Default, Debug)]
struct CartProd {
a_val: Value,
b_data: Vec<Value>,
b_idx: usize,
b_done: bool,
done: bool,
}
#[native_func(2)]
pub fn cartprod(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, a, b] = unpack_args!(args);
let a = a.to_iter_function()?;
let b = b.to_iter_function()?;
let state = RefCell::new(CartProd::default());
let f = move |vm: &mut Vm, _| {
if state.borrow().done {
return Ok(Value::Nil)
}
if state.borrow().b_idx >= state.borrow().b_data.len() {
if !state.borrow().b_done {
let v = vmcalliter!(vm; b.clone())?;
let mut s = state.borrow_mut();
match v {
Some(x) => s.b_data.push(cell_take(x)),
None => {
s.b_done = true;
if s.b_idx == 0 {
s.done = true;
return Ok(Value::Nil)
}
s.b_idx = 0;
}
};
} else {
if state.borrow().b_idx == 0 {
state.borrow_mut().done = true;
return Ok(Value::Nil)
}
state.borrow_mut().b_idx = 0;
}
}
let b_res = state.borrow().b_data[state.borrow().b_idx].clone();
if state.borrow().b_idx == 0 {
match vmcalliter!(vm; a.clone())? {
Some(v) => state.borrow_mut().a_val = cell_take(v),
None => {
state.borrow_mut().done = true;
return Ok(Value::Nil)
}
}
}
let a_res = state.borrow().a_val.clone();
state.borrow_mut().b_idx += 1;
Ok(Value::from(vec![a_res, b_res]).to_cell())
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(1)]
pub fn cycle(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let record = RefCell::new((Vec::<Rc<RefCell<Value>>>::new(), false, 0));
let f = move |vm: &mut Vm, _| {
if record.borrow().1 {
let mut r = record.borrow_mut();
if r.0.is_empty() {
return Ok(Value::Nil)
}
let val = r.0[r.2].clone();
r.2 = (r.2 + 1) % r.0.len();
return Ok(val.into())
}
match vmcalliter!(vm; iter.clone())? {
Some(v) => {
record.borrow_mut().0.push(v.clone());
Ok(v.into())
},
None => {
let mut r = record.borrow_mut();
r.1 = true;
if r.0.is_empty() {
Ok(Value::Nil)
} else {
r.2 = (r.2 + 1) % r.0.len();
Ok(r.0[0].clone().into())
}
}
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[derive(Clone, Copy, Default)]
enum Step {
First, Going, #[default] End,
}
#[native_func(2)]
pub fn step(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, by, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let Value::Int(by) = by else {
throw!(*SYM_TYPE_ERROR, "step expected integer")
};
if by <= 0 {
throw!(*SYM_TYPE_ERROR, "step expected positive integer")
}
let state = RefCell::new(Step::First);
let f = move |vm: &mut Vm, _| match state.take() {
Step::First => {
match vmcalliter!(vm; iter.clone())? {
Some(x) => {
*state.borrow_mut() = Step::Going;
Ok(x.into())
},
None => Ok(Value::Nil),
}
},
Step::Going => {
for _ in 0..(by-1) {
if vmcall!(vm; iter.clone())? == Value::Nil {
return Ok(Value::Nil)
}
}
let Some(x) = vmcalliter!(vm; iter.clone())? else {
return Ok(Value::Nil)
};
*state.borrow_mut() = Step::Going;
Ok(x.into())
},
Step::End => Ok(Value::Nil),
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
#[native_func(1)]
pub fn rev(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let lst = RefCell::new(None);
let f = move |vm: &mut Vm, _| {
if lst.borrow().is_none() {
let mut l = Vec::new();
while let Some(x) = vmcalliter!(vm; iter.clone())? {
l.push(cell_take(x))
}
l.reverse();
*lst.borrow_mut() = Some(l.into_iter());
}
match lst.borrow_mut().as_mut().unwrap().next() {
Some(v) => Ok(v.to_cell()),
None => Ok(Value::Nil),
}
};
Ok(NativeFunc::new(Box::new(f), 0).into())
}
//
// end iteration
//
#[native_func(1)]
pub fn list(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let mut result = Vec::new();
while let Some(value) = vmcalliter!(vm; iter.clone())? {
result.push(cell_take(value));
};
Ok(result.into())
}
#[native_func(1)]
pub fn table(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let mut result = HashMap::new();
while let Some(value) = vmcalliter!(vm; iter.clone())? {
let Value::List(l) = cell_take(value) else {
throw!(*SYM_TYPE_ERROR, "table expected iterator to yield list")
};
let Ok([k, v]): std::result::Result<[Value; 2], Vec<Value>> = cell_take(l).try_into() else {
throw!(*SYM_TYPE_ERROR, "table expected iterator to yield list of length 2")
};
result.insert(k.try_into()?, v);
};
Ok(result.into())
}
#[native_func(1)]
pub fn len(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, value] = unpack_args!(args);
match value {
Value::String(s) => return Ok((s.len() as i64).into()),
Value::List(l) => return Ok((l.borrow().len() as i64).into()),
Value::Table(t) => return Ok((t.borrow().len() as i64).into()),
Value::Range(r) if r.ty != RangeType::Endless
=> return Ok((r.len().unwrap() as i64).into()),
_ => (),
}
let iter = value.to_iter_function()?;
let mut len = 0;
while vmcalliter!(vm; iter.clone())?.is_some() {
len += 1;
};
Ok(len.into())
}
#[native_func(2)]
pub fn fold(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, func, iter] = unpack_args!(args);
let iter: Value = iter.to_iter_function()?;
let mut result = match vmcalliter!(vm; iter.clone())? {
Some(v) => cell_take(v),
None => return Ok(Value::Nil),
};
while let Some(value) = vmcalliter!(vm; iter.clone())? {
result = vmcall!(vm; func.clone(), result, cell_take(value))?;
}
Ok(result)
}
#[native_func(3)]
pub fn foldi(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, init, func, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let mut result = init;
while let Some(value) = vmcalliter!(vm; iter.clone())? {
result = vmcall!(vm; func.clone(), result, cell_take(value))?;
}
Ok(result)
}
#[native_func(1)]
pub fn sum(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let mut result = Value::Int(0);
while let Some(value) = vmcalliter!(vm; iter.clone())? {
result = (result + cell_take(value))?
}
Ok(result)
}
#[native_func(1)]
pub fn prod(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let mut result = Value::Int(1);
while let Some(value) = vmcalliter!(vm; iter.clone())? {
result = (result * cell_take(value))?
}
Ok(result)
}
#[native_func(1)]
pub fn any(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
while let Some(value) = vmcalliter!(vm; iter.clone())? {
if RefCell::borrow(&value).truthy() {
return Ok(true.into())
}
}
Ok(false.into())
}
#[native_func(1)]
pub fn all(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
while let Some(value) = vmcalliter!(vm; iter.clone())? {
if !RefCell::borrow(&value).truthy() {
return Ok(false.into())
}
}
Ok(true.into())
}
#[native_func(1)]
pub fn last(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let mut last = Rc::new(RefCell::new(Value::Nil));
while let Some(value) = vmcalliter!(vm; iter.clone())? {
last = value;
}
Ok(cell_take(last))
}
#[native_func(2)]
pub fn nth(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, n, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let Value::Int(n) = n else {
throw!(*SYM_TYPE_ERROR, "nth expected integer")
};
for _ in 0..n {
if vmcalliter!(vm; iter.clone())?.is_none() {
return Ok(Value::Nil)
}
}
match vmcalliter!(vm; iter)? {
Some(v) => Ok(cell_take(v)),
None => Ok(Value::Nil),
}
}
#[native_func(2)]
pub fn contains(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, val, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
for _ in 0.. {
match vmcalliter!(vm; iter.clone())? {
None => return Ok(Value::Nil),
Some(v) => if *RefCell::borrow(&v) == val {
return Ok(true.into())
}
}
}
Ok(false.into())
}
#[native_func(2)]
pub fn index_of(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, val, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
for i in 0.. {
match vmcalliter!(vm; iter.clone())? {
None => return Ok(Value::Nil),
Some(v) => if *RefCell::borrow(&v) == val {
return Ok(i.into())
}
}
}
Ok(Value::Nil)
}
#[native_func(2)]
pub fn index_if(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, func, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
for i in 0.. {
match vmcalliter!(vm; iter.clone())? {
None => return Ok(Value::Nil),
Some(v) => if vmcall!(vm; func.clone(), cell_take(v))?.truthy() {
return Ok(i.into())
}
}
}
Ok(Value::Nil)
}
#[native_func(2)]
pub fn find(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, func, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
loop {
match vmcalliter!(vm; iter.clone())? {
None => return Ok(Value::Nil),
Some(v) => {
let v = cell_take(v);
if vmcall!(vm; func.clone(), v.clone())?.truthy() {
return Ok(v)
}
}
}
}
}
#[native_func(1)]
pub fn count(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, iter] = unpack_args!(args);
let iter = iter.to_iter_function()?;
let mut map = HashMap::new();
while let Some(v) = vmcalliter!(vm; iter.clone())? {
let v = cell_take(v);
let hv = v.try_into()?;
map.entry(hv)
.and_modify(|v: &mut Value| *v = (v.clone() + Value::Int(1)).unwrap())
.or_insert(Value::Int(1));
}
Ok(map.into())
}

View file

@ -1,17 +1,47 @@
use talc_lang::{symbol::{symbol, Symbol}, Vm};
pub mod num;
pub mod value;
pub mod io;
pub mod iter;
pub mod exception;
pub mod num;
pub mod collection;
#[cfg(feature="random")]
pub mod random;
pub fn load_all(vm: &mut Vm) {
num::load(vm);
io::load(vm);
iter::load(vm);
exception::load(vm);
value::load(vm);
exception::load(vm);
iter::load(vm);
collection::load(vm);
num::load(vm);
io::load(vm);
#[cfg(feature="random")]
random::load(vm);
}
lazy_static::lazy_static! {
pub static ref SYM_IO_ERROR: Symbol = symbol!(io_error);
pub static ref SYM_IO_ERROR: Symbol = symbol!(io_error);
}
macro_rules! unpack_args {
($e:expr) => {
($e).try_into().expect("bypassed arity check")
};
}
pub(crate) use unpack_args;
macro_rules! unpack_varargs {
($e:expr) => {{
let mut args = $e;
let Value::List(varargs) = args.pop().expect("bypassed arity check") else {
panic!("bypassed arity check")
};
let varargs = ::std::rc::Rc::unwrap_or_clone(varargs).into_inner();
(args.try_into().expect("bypassed arity check"), varargs)
}};
}
pub(crate) use unpack_varargs;

View file

@ -1,5 +1,679 @@
use talc_lang::Vm;
use std::cmp::Ordering;
use lazy_static::lazy_static;
use talc_lang::{parse_int, symbol::{Symbol, SYM_TYPE_ERROR}, throw, value::{exception::Result, Complex64, Value}, Vm};
use talc_macros::native_func;
use crate::{unpack_args, unpack_varargs};
lazy_static! {
static ref SYM_NAN: Symbol = Symbol::get("nan");
static ref SYM_INFINITE: Symbol = Symbol::get("infinite");
static ref SYM_ZERO: Symbol = Symbol::get("zero");
static ref SYM_SUBNORMAL: Symbol = Symbol::get("subnormal");
static ref SYM_NORMAL: Symbol = Symbol::get("normal");
}
#[inline]
fn to_floaty(v: Value) -> Value {
match v {
Value::Int(v) => Value::Float(v as f64),
Value::Ratio(v) => Value::Float(*v.numer() as f64 / *v.denom() as f64),
Value::Float(v) => Value::Float(v),
Value::Complex(v) => Value::Complex(v),
v => v,
}
}
#[inline]
fn to_complex(v: Value) -> Value {
match v {
Value::Int(v) => Value::Complex((v as f64).into()),
Value::Ratio(v) => Value::Complex((*v.numer() as f64 / *v.denom() as f64).into()),
Value::Float(v) => Value::Complex(v.into()),
Value::Complex(v) => Value::Complex(v),
v => v,
}
}
//
// load
//
pub fn load(vm: &mut Vm) {
vm.set_global_name("e", std::f64::consts::E.into());
vm.set_global_name("tau", std::f64::consts::TAU.into());
vm.set_global_name("pi", std::f64::consts::PI.into());
vm.set_global_name("egamma", 0.5772156649015329.into());
vm.set_global_name("phi", 1.618033988749895.into());
vm.set_global_name("inf", (f64::INFINITY).into());
vm.set_global_name("NaN", (f64::NAN).into());
vm.set_global_name("infi", Complex64::new(0.0,f64::INFINITY).into());
vm.set_global_name("NaNi", Complex64::new(0.0,f64::NAN).into());
vm.set_global_name("bin", bin().into());
vm.set_global_name("sex", sex().into());
vm.set_global_name("oct", oct().into());
vm.set_global_name("hex", hex().into());
vm.set_global_name("to_radix", to_radix().into());
vm.set_global_name("from_radix", from_radix().into());
vm.set_global_name("gcd", gcd().into());
vm.set_global_name("lcm", lcm().into());
vm.set_global_name("isqrt", isqrt().into());
vm.set_global_name("isprime", isprime().into());
vm.set_global_name("factors", factors().into());
vm.set_global_name("min", min().into());
vm.set_global_name("max", max().into());
vm.set_global_name("floor", floor().into());
vm.set_global_name("ceil", ceil().into());
vm.set_global_name("round", round().into());
vm.set_global_name("trunc", trunc().into());
vm.set_global_name("fract", fract().into());
vm.set_global_name("sign", sign().into());
vm.set_global_name("signum", signum().into());
vm.set_global_name("classify", classify().into());
vm.set_global_name("isnan", isnan().into());
vm.set_global_name("isfinite", isfinite().into());
vm.set_global_name("isinfinite", isinfinite().into());
vm.set_global_name("numer", numer().into());
vm.set_global_name("denom", denom().into());
vm.set_global_name("re", re().into());
vm.set_global_name("im", im().into());
vm.set_global_name("arg", arg().into());
vm.set_global_name("abs", abs().into());
vm.set_global_name("abs_sq", abs_sq().into());
vm.set_global_name("sqrt", sqrt().into());
vm.set_global_name("cbrt", cbrt().into());
vm.set_global_name("ln", cbrt().into());
vm.set_global_name("log2", cbrt().into());
vm.set_global_name("exp", cbrt().into());
vm.set_global_name("exp2", cbrt().into());
vm.set_global_name("sin", sin().into());
vm.set_global_name("cos", cos().into());
vm.set_global_name("tan", tan().into());
vm.set_global_name("asin", asin().into());
vm.set_global_name("acos", acos().into());
vm.set_global_name("atan", atan().into());
vm.set_global_name("sinh", sinh().into());
vm.set_global_name("cosh", cosh().into());
vm.set_global_name("tanh", tanh().into());
vm.set_global_name("asinh", asinh().into());
vm.set_global_name("acosh", acosh().into());
vm.set_global_name("atanh", atanh().into());
vm.set_global_name("atanh", atanh().into());
vm.set_global_name("atan2", atan2().into());
}
//
// base conversions
//
fn to_radix_inner(n: i64, radix: u32) -> String {
let mut result = vec![];
let mut begin = 0;
let mut x;
if n < 0 {
result.push('-' as u32 as u8);
begin = 1;
x = (-n) as u64
} else {
x = n as u64
};
loop {
let m = x % (radix as u64);
x /= radix as u64;
result.push(char::from_digit(m as u32, radix).unwrap() as u8);
if x == 0 {
break;
}
}
result[begin..].reverse();
String::from_utf8(result).expect("string was not valid utf8")
}
#[native_func(1)]
pub fn bin(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "bin expected integer argument, got {x:#}")
};
Ok(to_radix_inner(x, 2).into())
}
#[native_func(1)]
pub fn sex(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "sex expected integer argument, got {x:#}")
};
Ok(to_radix_inner(x, 6).into())
}
#[native_func(1)]
pub fn oct(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "oct expected integer argument, got {x:#}")
};
Ok(to_radix_inner(x, 8).into())
}
#[native_func(1)]
pub fn hex(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "hex expected integer argument, got {x:#}")
};
Ok(to_radix_inner(x, 16).into())
}
#[native_func(2)]
pub fn to_radix(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x, radix] = unpack_args!(args);
let (Value::Int(x), Value::Int(radix)) = (&x, &radix) else {
throw!(*SYM_TYPE_ERROR, "to_radix expected integer arguments, got {x:#} and {radix:#}")
};
if *radix < 2 || *radix > 36 {
throw!(*SYM_TYPE_ERROR, "to_radix expected radix in range 0..=36")
}
Ok(to_radix_inner(*x, *radix as u32).into())
}
#[native_func(2)]
pub fn from_radix(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, s, radix] = unpack_args!(args);
let (Value::String(s), Value::Int(radix)) = (&s, &radix) else {
throw!(*SYM_TYPE_ERROR, "from_radix expected string and integer arguments, got {s:#} and {radix:#}")
};
if *radix < 2 || *radix > 36 {
throw!(*SYM_TYPE_ERROR, "from_radix expected radix in range 0..=36")
}
match parse_int(s, *radix as u32) {
Ok(v) => Ok(v.into()),
Err(_) => throw!(*SYM_TYPE_ERROR, "string was not a valid integer in given radix"),
}
}
//
// integer operations
//
fn isqrt_inner(mut n: i64) -> i64 {
assert!(n >= 0, "isqrt input should be nonnegative");
if n < 2 { return n }
let mut c = 0;
let mut d = 1 << 62;
while d > n {
d >>= 2;
}
while d != 0 {
if n >= c + d {
n -= c + d;
c = (c >> 1) + d;
}
else {
c >>= 1;
}
d >>= 2;
}
c
}
#[native_func(1)]
pub fn isqrt(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "isqrt expected integer argument, got {x:#}")
};
if x < 0 {
throw!(*SYM_TYPE_ERROR, "isqrt: argument must be positive")
}
Ok(isqrt_inner(x).into())
}
#[native_func(1)]
pub fn isprime(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "isprime expected integer argument, got {x:#}")
};
if x < 2 {
return Ok(false.into())
}
for p in [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53] {
if x % p == 0 {
return Ok((x == p).into())
}
}
if x < 59 {
return Ok(false.into())
}
let lim = isqrt_inner(x);
let mut i = 54;
while i <= lim {
if x % (i + 1) == 0 { return Ok(false.into()) }
if x % (i + 5) == 0 { return Ok(false.into()) }
i += 6;
}
Ok(true.into())
}
fn gcd_inner(a: i64, b: i64) -> i64 {
let (mut a, mut b) = (a.abs(), b.abs());
if a == 0 {
return b
}
if b == 0 {
return a
}
let az = a.trailing_zeros();
a >>= az;
let bz = b.trailing_zeros();
b >>= bz;
let z = az.min(bz);
loop {
if a > b {
std::mem::swap(&mut a, &mut b);
}
b -= a;
if b == 0 {
return a << z;
}
b >>= b.trailing_zeros();
}
}
#[native_func(2..)]
pub fn gcd(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let ([_, x, y], rest) = unpack_varargs!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "gcd expected integer argument, got {x:#}")
};
let Value::Int(y) = y else {
throw!(*SYM_TYPE_ERROR, "gcd expected integer argument, got {x:#}")
};
let mut g = gcd_inner(x, y);
for a in rest {
let Value::Int(a) = a else {
throw!(*SYM_TYPE_ERROR, "gcd expected integer argument, got {a:#}")
};
g = gcd_inner(g, a);
}
Ok(g.into())
}
#[native_func(2..)]
pub fn lcm(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let ([_, x, y], rest) = unpack_varargs!(args);
let Value::Int(x) = x else {
throw!(*SYM_TYPE_ERROR, "lcm expected integer argument, got {x:#}")
};
let Value::Int(y) = y else {
throw!(*SYM_TYPE_ERROR, "lcm expected integer argument, got {x:#}")
};
let mut g = gcd_inner(x, y);
let mut prod = y;
for a in rest {
let Value::Int(a) = a else {
throw!(*SYM_TYPE_ERROR, "lcm expected integer argument, got {a:#}")
};
prod *= a;
g = gcd_inner(g, a);
}
Ok((x/g * prod).into())
}
#[native_func(1)]
pub fn factors(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let Value::Int(mut x) = x else {
throw!(*SYM_TYPE_ERROR, "factords expected integer argument, got {x:#}")
};
let mut factors = Vec::new();
if x <= 1 {
return Ok(factors.into())
}
while x & 1 == 0 {
x >>= 1;
factors.push(Value::Int(2));
}
while x % 3 == 0 {
x /= 3;
factors.push(Value::Int(3));
}
//let lim = isqrt_inner(x);
let mut i = 5;
while x > 1 {
while x % i == 0 {
x /= i;
factors.push(Value::Int(i));
}
i += 2;
while x % i == 0 {
x /= i;
factors.push(Value::Int(i));
}
i += 4;
}
Ok(factors.into())
}
//
// numeric operations
//
#[native_func(1..)]
pub fn min(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let ([_, mut x], rest) = unpack_varargs!(args);
for val in rest {
if val < x {
x = val;
}
}
Ok(x)
}
#[native_func(1..)]
pub fn max(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let ([_, mut x], rest) = unpack_varargs!(args);
for val in rest {
if val > x {
x = val;
}
}
Ok(x)
}
#[native_func(1)]
pub fn floor(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(x) => Ok(Value::Int(x)),
Value::Float(x) => Ok(Value::Float(x.floor())),
Value::Ratio(x) => Ok(Value::Ratio(x.floor())),
x => throw!(*SYM_TYPE_ERROR, "floor expected real argument, got {x:#}"),
}
}
#[native_func(1)]
pub fn ceil(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(x) => Ok(Value::Int(x)),
Value::Float(x) => Ok(Value::Float(x.ceil())),
Value::Ratio(x) => Ok(Value::Ratio(x.ceil())),
x => throw!(*SYM_TYPE_ERROR, "ceil expected real argument, got {x:#}"),
}
}
#[native_func(1)]
pub fn round(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(x) => Ok(Value::Int(x)),
Value::Float(x) => Ok(Value::Float(x.round())),
Value::Ratio(x) => Ok(Value::Ratio(x.round())),
x => throw!(*SYM_TYPE_ERROR, "round expected real argument, got {x:#}"),
}
}
#[native_func(1)]
pub fn trunc(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(x) => Ok(Value::Int(x)),
Value::Float(x) => Ok(Value::Float(x.trunc())),
Value::Ratio(x) => Ok(Value::Ratio(x.trunc())),
x => throw!(*SYM_TYPE_ERROR, "trunc expected real argument, got {x:#}"),
}
}
#[native_func(1)]
pub fn fract(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(_) => Ok(Value::Int(0)),
Value::Float(x) => Ok(Value::Float(x.fract())),
Value::Ratio(x) => Ok(Value::Ratio(x.fract())),
x => throw!(*SYM_TYPE_ERROR, "fract expected real argument, got {x:#}"),
}
}
#[native_func(1)]
pub fn sign(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(x) => Ok(Value::Int(x.signum())),
Value::Float(x) => Ok(Value::Float(if x == 0.0 { 0.0 } else { x.signum() })),
Value::Ratio(x) => match x.cmp(&0.into()) {
Ordering::Greater => Ok(Value::Ratio(1.into())),
Ordering::Less => Ok(Value::Ratio((-1).into())),
Ordering::Equal => Ok(Value::Ratio(0.into())),
}
x => throw!(*SYM_TYPE_ERROR, "sign expected real argument, got {x:#}"),
}
}
//
// floating-point operations
//
#[native_func(1)]
pub fn signum(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match to_floaty(x) {
Value::Float(x) => Ok(Value::Float(x.signum())),
x => throw!(*SYM_TYPE_ERROR, "signum expected real argument, got {x:#}"),
}
}
#[native_func(1)]
pub fn classify(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
let x = to_floaty(x);
let Value::Float(x) = x else {
throw!(*SYM_TYPE_ERROR, "classify expected real argument, got {x:#}")
};
Ok(match x.classify() {
std::num::FpCategory::Nan => *SYM_NAN,
std::num::FpCategory::Infinite => *SYM_INFINITE,
std::num::FpCategory::Zero => *SYM_ZERO,
std::num::FpCategory::Subnormal => *SYM_SUBNORMAL,
std::num::FpCategory::Normal => *SYM_NORMAL,
}.into())
}
#[native_func(1)]
pub fn isnan(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match v {
Value::Int(_) | Value::Ratio(_) => Ok(false.into()),
Value::Float(x) => Ok(x.is_nan().into()),
Value::Complex(z) => Ok(z.is_nan().into()),
v => throw!(*SYM_TYPE_ERROR, "isnan expected numeric argument, got {v:#}"),
}
}
#[native_func(1)]
pub fn isfinite(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match v {
Value::Int(_) | Value::Ratio(_) => Ok(true.into()),
Value::Float(x) => Ok(x.is_finite().into()),
Value::Complex(z) => Ok(z.is_finite().into()),
v => throw!(*SYM_TYPE_ERROR, "isfinite expected numeric argument, got {v:#}"),
}
}
#[native_func(1)]
pub fn isinfinite(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match v {
Value::Int(_) | Value::Ratio(_) => Ok(false.into()),
Value::Float(x) => Ok(x.is_infinite().into()),
Value::Complex(z) => Ok(z.is_infinite().into()),
v => throw!(*SYM_TYPE_ERROR, "isinfinite expected numeric argument, got {v:#}"),
}
}
//
// rational operations
//
#[native_func(1)]
pub fn numer(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match v {
Value::Int(x) => Ok(Value::Int(x)),
Value::Ratio(x) => Ok(Value::Int(*x.numer())),
v => throw!(*SYM_TYPE_ERROR, "numer expected rational argument, got {v:#}"),
}
}
#[native_func(1)]
pub fn denom(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match v {
Value::Int(_) => Ok(Value::Int(1)),
Value::Ratio(x) => Ok(Value::Int(*x.denom())),
v => throw!(*SYM_TYPE_ERROR, "denom expected rational argument, got {v:#}"),
}
}
//
// complex operations
//
#[native_func(1)]
pub fn re(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match v {
Value::Int(x) => Ok(Value::Int(x)),
Value::Ratio(x) => Ok(Value::Ratio(x)),
Value::Float(x) => Ok(Value::Float(x)),
Value::Complex(z) => Ok(Value::Float(z.re)),
v => throw!(*SYM_TYPE_ERROR, "re expected numeric argument, got {v:#}"),
}
}
#[native_func(1)]
pub fn im(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match to_complex(v) {
Value::Int(_) => Ok(Value::Int(0)),
Value::Ratio(_) => Ok(Value::Ratio(0.into())),
Value::Float(_) => Ok(Value::Float(0.0)),
Value::Complex(z) => Ok(Value::Float(z.im)),
v => throw!(*SYM_TYPE_ERROR, "im expected numeric argument, got {v:#}"),
}
}
#[native_func(1)]
pub fn arg(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match to_complex(v) {
Value::Complex(z) => Ok(Value::Float(z.arg())),
v => throw!(*SYM_TYPE_ERROR, "im expected numeric argument, got {v:#}"),
}
}
#[native_func(1)]
pub fn abs(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(x) => Ok(Value::Int(x.abs())),
Value::Ratio(x) => Ok(Value::Ratio(if x < 0.into() { -x } else { x })),
Value::Float(x) => Ok(Value::Float(x.abs())),
Value::Complex(x) => Ok(Value::Float(x.norm())),
x => throw!(*SYM_TYPE_ERROR, "abs expected numeric argument, got {x:#}"),
}
}
#[native_func(1)]
pub fn abs_sq(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, x] = unpack_args!(args);
match x {
Value::Int(x) => Ok(Value::Int(x * x)),
Value::Ratio(x) => Ok(Value::Ratio(x * x)),
Value::Float(x) => Ok(Value::Float(x * x)),
Value::Complex(x) => Ok(Value::Float(x.norm_sqr())),
x => throw!(*SYM_TYPE_ERROR, "abs_sq expected numeric argument, got {x:#}"),
}
}
//
// continuous operations
//
macro_rules! float_func {
($name:ident) => {
#[native_func(1)]
pub fn $name(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, v] = unpack_args!(args);
match to_floaty(v) {
Value::Float(x) => Ok(Value::Float(x.$name())),
Value::Complex(z) => Ok(Value::Complex(z.$name())),
v => throw!(*SYM_TYPE_ERROR,
"{} expected numeric argument, got {v:#}", stringify!($name)),
}
}
};
}
float_func!(sqrt);
float_func!(cbrt);
float_func!(ln);
float_func!(log2);
float_func!(exp);
float_func!(exp2);
float_func!(sin);
float_func!(cos);
float_func!(tan);
float_func!(asin);
float_func!(acos);
float_func!(atan);
float_func!(sinh);
float_func!(cosh);
float_func!(tanh);
float_func!(asinh);
float_func!(acosh);
float_func!(atanh);
#[native_func(2)]
pub fn atan2(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, y, x] = unpack_args!(args);
match (to_floaty(y), to_floaty(x)) {
(Value::Float(y), Value::Float(x))
=> Ok(Value::Float(x.atan2(y))),
(y,x) => throw!(*SYM_TYPE_ERROR,
"atan2 expected real arguments, got {y:#} and {x:#}"),
}
}

50
talc-std/src/random.rs Normal file
View file

@ -0,0 +1,50 @@
use rand::Rng;
use talc_lang::{symbol::SYM_TYPE_ERROR, throw, value::{cell_take, exception::Result, range::RangeType, Value}, vmcalliter, Vm};
use talc_macros::native_func;
use crate::unpack_args;
pub fn load(vm: &mut Vm) {
vm.set_global_name("rand", rand().into());
vm.set_global_name("rand_in", rand_in().into());
}
#[native_func(0)]
pub fn rand(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
Ok(Value::Float(rand::thread_rng().gen()))
}
#[native_func(1)]
pub fn rand_in(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, col] = unpack_args!(args);
match col {
Value::List(l) => {
let l = l.borrow();
let i = rand::thread_rng().gen_range(0..l.len());
Ok(l[i].clone())
},
Value::Table(t) => {
let t = t.borrow();
let i = rand::thread_rng().gen_range(0..t.len());
let key = t.keys().nth(i).unwrap();
Ok(key.clone().into_inner())
},
Value::Range(r) => match r.ty {
RangeType::Open => Ok(Value::Int(
rand::thread_rng().gen_range(r.start..r.stop))),
RangeType::Closed => Ok(Value::Int(
rand::thread_rng().gen_range(r.start..=r.stop))),
RangeType::Endless => throw!(*SYM_TYPE_ERROR, "cannot select random element of endless range"),
},
col => {
let iter = col.to_iter_function()?;
let mut values = Vec::new();
while let Some(v) = vmcalliter!(vm; iter.clone())? {
values.push(v);
}
let i = rand::thread_rng().gen_range(0..values.len());
let v = values.swap_remove(i);
Ok(cell_take(v))
}
}
}

170
talc-std/src/value.rs Normal file
View file

@ -0,0 +1,170 @@
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use talc_lang::{exception, parse_float, parse_int, symbol::SYM_TYPE_ERROR, throw, value::{exception::Result, HashValue, Value}, Vm};
use talc_macros::native_func;
use crate::unpack_args;
pub fn load(vm: &mut Vm) {
vm.set_global_name("type", type_().into());
vm.set_global_name("is", is().into());
vm.set_global_name("as", as_().into());
vm.set_global_name("copy", copy().into());
vm.set_global_name("str", str_().into());
vm.set_global_name("repr", repr().into());
vm.set_global_name("cell", cell().into());
vm.set_global_name("uncell", uncell().into());
vm.set_global_name("cell_replace", cell_replace().into());
vm.set_global_name("cell_take", cell_replace().into());
}
//
// types
//
#[native_func(1)]
pub fn type_(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, val] = unpack_args!(args);
Ok(val.get_type().into())
}
#[native_func(2)]
pub fn is(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, val, ty] = unpack_args!(args);
let Value::Symbol(ty) = ty else {
throw!(*SYM_TYPE_ERROR, "type expected symbol, got {ty:#}")
};
Ok((val.get_type() == ty).into())
}
#[native_func(2)]
pub fn as_(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, val, ty] = unpack_args!(args);
let Value::Symbol(ty) = ty else {
throw!(*SYM_TYPE_ERROR, "type expected symbol, got {ty:#}")
};
if val.get_type() == ty {
return Ok(val)
}
match (val, ty.name().as_ref()) {
(_, "nil") => Ok(Value::Nil),
(v, "string") => Ok(Value::String(v.to_string().into())),
(v, "bool") => Ok(Value::Bool(v.truthy())),
(Value::Symbol(s), "int") => Ok(Value::Int(s.id() as i64)),
(Value::Int(x), "ratio") => Ok(Value::Ratio(x.into())),
(Value::Int(x), "float") => Ok(Value::Float(x as f64)),
(Value::Int(x), "complex") => Ok(Value::Complex((x as f64).into())),
(Value::Ratio(x), "float") => Ok(Value::Float(*x.numer() as f64 / *x.denom() as f64)),
(Value::Ratio(x), "complex") => Ok(Value::Complex((*x.numer() as f64 / *x.denom() as f64).into())),
(Value::Float(x), "complex") => Ok(Value::Complex(x.into())),
(Value::String(s), "int")
=> parse_int(&s, 10)
.map(|v| v.into())
.map_err(|_| exception!(*SYM_TYPE_ERROR, "could not parse {s:#} as integer")),
(Value::String(s), "float")
=> parse_float(&s)
.map(|v| v.into())
.map_err(|_| exception!(*SYM_TYPE_ERROR, "could not parse {s:#} as float")),
(v, t) => throw!(*SYM_TYPE_ERROR,
"cannot convert value of type {} to type {t}", v.get_type().name())
}
}
pub fn copy_inner(value: Value) -> Result<Value> {
match value {
Value::Nil | Value::Bool(_) | Value::Symbol(_)
| Value::Int(_) | Value::Ratio(_) | Value::Float(_)
| Value::Complex(_) | Value::Range(_) | Value::String(_)
=> Ok(value),
Value::Cell(c) => {
let c = copy_inner(talc_lang::value::cell_take(c))?;
Ok(RefCell::new(c).into())
},
Value::List(l) => {
let l = talc_lang::value::cell_take(l);
let v: Result<Vec<Value>> = l.into_iter()
.map(copy_inner)
.collect();
Ok(v?.into())
},
Value::Table(t) => {
let t = talc_lang::value::cell_take(t);
let v: Result<HashMap<HashValue, Value>> = t.into_iter()
.map(|(k, v)| copy_inner(v).map(|v| (k, v)))
.collect();
Ok(v?.into())
},
_ => throw!(*SYM_TYPE_ERROR,
"cannot copy value of type {}", value.get_type().name())
}
}
#[native_func(1)]
pub fn copy(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, val] = unpack_args!(args);
copy_inner(val)
}
//
// strings
//
#[native_func(1)]
pub fn str_(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, val] = unpack_args!(args);
Ok(val.to_string().into())
}
#[native_func(1)]
pub fn repr(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, val] = unpack_args!(args);
Ok(format!("{val:#}").into())
}
//
// cells
//
#[native_func(1)]
pub fn cell(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, value] = unpack_args!(args);
Ok(RefCell::new(value).into())
}
#[native_func(1)]
pub fn uncell(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, cell] = unpack_args!(args);
let Value::Cell(cell) = cell else {
throw!(*SYM_TYPE_ERROR, "value is not a cell")
};
Ok(Rc::unwrap_or_clone(cell).into_inner())
}
#[native_func(2)]
pub fn cell_replace(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, cell, value] = unpack_args!(args);
let Value::Cell(cell) = cell else {
throw!(*SYM_TYPE_ERROR, "value is not a cell")
};
Ok(cell.replace(value))
}
#[native_func(1)]
pub fn cell_take(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, cell] = unpack_args!(args);
let Value::Cell(cell) = cell else {
throw!(*SYM_TYPE_ERROR, "value is not a cell")
};
Ok(cell.replace(Value::Nil))
}