improvements and such
This commit is contained in:
parent
66abf928e9
commit
9996ca1574
28 changed files with 3384 additions and 834 deletions
53
Cargo.lock
generated
53
Cargo.lock
generated
|
@ -138,7 +138,7 @@ dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.50",
|
"syn 2.0.51",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -244,9 +244,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "error-code"
|
name = "error-code"
|
||||||
version = "3.0.0"
|
version = "3.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
|
checksum = "26a147e1a6641a55d994b3e4e9fa4d9b180c8d652c09b363af8c9bf1b8e04139"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fd-lock"
|
name = "fd-lock"
|
||||||
|
@ -290,9 +290,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.6"
|
version = "0.3.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd"
|
checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "home"
|
name = "home"
|
||||||
|
@ -527,6 +527,12 @@ dependencies = [
|
||||||
"siphasher",
|
"siphasher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "precomputed-hash"
|
name = "precomputed-hash"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -561,6 +567,36 @@ dependencies = [
|
||||||
"nibble_vec",
|
"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]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -707,9 +743,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.50"
|
version = "2.0.51"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
|
checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -752,6 +788,7 @@ name = "talc-std"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"rand",
|
||||||
"talc-lang",
|
"talc-lang",
|
||||||
"talc-macros",
|
"talc-macros",
|
||||||
]
|
]
|
||||||
|
@ -784,7 +821,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.50",
|
"syn 2.0.51",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
185
talc-bin/src/helper.rs
Normal file
185
talc-bin/src/helper.rs
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,30 @@
|
||||||
use clap::Parser;
|
use clap::{ColorChoice, Parser};
|
||||||
use rustyline::error::ReadlineError;
|
use talc_lang::{compiler::compile, value::function::disasm_recursive, Vm};
|
||||||
use talc_lang::{compiler::{compile, compile_repl}, value::{function::disasm_recursive, Value}, symbol::Symbol, Vm};
|
use std::{path::PathBuf, process::ExitCode, rc::Rc};
|
||||||
use std::{rc::Rc, path::PathBuf, process::ExitCode};
|
|
||||||
|
mod repl;
|
||||||
|
mod helper;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
/// file to run
|
||||||
file: Option<PathBuf>,
|
file: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// start the repl
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
repl: bool,
|
repl: bool,
|
||||||
|
|
||||||
|
/// show disassembled bytecode
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
disasm: bool,
|
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 parser = talc_lang::Parser::new();
|
||||||
let mut vm = Vm::new(256);
|
let mut vm = Vm::new(256);
|
||||||
talc_std::load_all(&mut vm);
|
talc_std::load_all(&mut vm);
|
||||||
|
@ -30,74 +39,6 @@ fn exec(src: String, _args: &Args) -> ExitCode {
|
||||||
|
|
||||||
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 args.disasm {
|
||||||
if let Err(e) = disasm_recursive(&func, &mut std::io::stderr()) {
|
if let Err(e) = disasm_recursive(&func, &mut std::io::stderr()) {
|
||||||
eprintln!("Error: {e}");
|
eprintln!("Error: {e}");
|
||||||
|
@ -105,17 +46,11 @@ fn repl(args: &Args) -> ExitCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match vm.call_no_args(func) {
|
if let Err(e) = vm.run_function(func.clone(), vec![func.into()]) {
|
||||||
Ok(v) => {
|
eprintln!("{e}");
|
||||||
vm.set_global(prev3_sym, vm.get_global(prev2_sym).unwrap().clone());
|
ExitCode::FAILURE
|
||||||
vm.set_global(prev2_sym, vm.get_global(prev1_sym).unwrap().clone());
|
} else {
|
||||||
vm.set_global(prev1_sym, v.clone());
|
ExitCode::SUCCESS
|
||||||
if v != Value::Nil {
|
|
||||||
println!("{v:#}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => eprintln!("{e}"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +58,7 @@ fn main() -> ExitCode {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
if args.repl || args.file.is_none() {
|
if args.repl || args.file.is_none() {
|
||||||
return repl(&args)
|
return repl::repl(&args)
|
||||||
}
|
}
|
||||||
|
|
||||||
match std::fs::read_to_string(args.file.as_ref().unwrap()) {
|
match std::fs::read_to_string(args.file.as_ref().unwrap()) {
|
||||||
|
|
146
talc-bin/src/repl.rs
Normal file
146
talc-bin/src/repl.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -117,7 +117,7 @@ pub enum Instruction {
|
||||||
JumpTrue(Arg24),
|
JumpTrue(Arg24),
|
||||||
JumpFalse(Arg24),
|
JumpFalse(Arg24),
|
||||||
|
|
||||||
IterBegin, IterNext(Arg24),
|
IterBegin, IterTest(Arg24),
|
||||||
|
|
||||||
BeginTry(Arg24), EndTry,
|
BeginTry(Arg24), EndTry,
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ impl std::fmt::Display for Instruction {
|
||||||
Self::JumpTrue(a) => write!(f, "jumptrue {}", usize::from(a)),
|
Self::JumpTrue(a) => write!(f, "jumptrue {}", usize::from(a)),
|
||||||
Self::JumpFalse(a) => write!(f, "jumpfalse {}", usize::from(a)),
|
Self::JumpFalse(a) => write!(f, "jumpfalse {}", usize::from(a)),
|
||||||
Self::IterBegin => write!(f, "iterbegin"),
|
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::BeginTry(t) => write!(f, "begintry {}", usize::from(t)),
|
||||||
Self::EndTry => write!(f, "endtry"),
|
Self::EndTry => write!(f, "endtry"),
|
||||||
Self::Call(n) => write!(f, "call {n}"),
|
Self::Call(n) => write!(f, "call {n}"),
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::rc::Rc;
|
||||||
use crate::ast::{BinaryOp, Expr, LValue, CatchBlock};
|
use crate::ast::{BinaryOp, Expr, LValue, CatchBlock};
|
||||||
use crate::chunk::{Instruction as I, Chunk, Arg24, Catch};
|
use crate::chunk::{Instruction as I, Chunk, Arg24, Catch};
|
||||||
use crate::symbol::Symbol;
|
use crate::symbol::Symbol;
|
||||||
use crate::value::function::Function;
|
use crate::value::function::{FuncAttrs, Function};
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -20,7 +20,8 @@ enum CompilerMode {
|
||||||
struct Compiler<'a> {
|
struct Compiler<'a> {
|
||||||
mode: CompilerMode,
|
mode: CompilerMode,
|
||||||
parent: Option<&'a Compiler<'a>>,
|
parent: Option<&'a Compiler<'a>>,
|
||||||
func: Function,
|
chunk: Chunk,
|
||||||
|
attrs: FuncAttrs,
|
||||||
scope: usize,
|
scope: usize,
|
||||||
locals: Vec<Local>,
|
locals: Vec<Local>,
|
||||||
globals: Vec<Local>,
|
globals: Vec<Local>,
|
||||||
|
@ -47,7 +48,8 @@ impl<'a> Default for Compiler<'a> {
|
||||||
Self {
|
Self {
|
||||||
mode: CompilerMode::Function,
|
mode: CompilerMode::Function,
|
||||||
parent: None,
|
parent: None,
|
||||||
func: Function { arity: 0, chunk: Chunk::new() },
|
chunk: Chunk::new(),
|
||||||
|
attrs: FuncAttrs { arity: 0, variadic: false },
|
||||||
scope: 0,
|
scope: 0,
|
||||||
locals,
|
locals,
|
||||||
globals: Vec::new(),
|
globals: Vec::new(),
|
||||||
|
@ -73,16 +75,12 @@ impl<'a> Compiler<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_function(&'a self, args: &[&str]) -> Self {
|
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,
|
mode: CompilerMode::Function,
|
||||||
parent: Some(self),
|
parent: Some(self),
|
||||||
func,
|
|
||||||
..Self::default()
|
..Self::default()
|
||||||
};
|
};
|
||||||
|
new.attrs.arity = args.len();
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
new.locals.push(Local {
|
new.locals.push(Local {
|
||||||
|
@ -96,12 +94,15 @@ impl<'a> Compiler<'a> {
|
||||||
|
|
||||||
pub fn finish(mut self) -> Function {
|
pub fn finish(mut self) -> Function {
|
||||||
self.emit(I::Return);
|
self.emit(I::Return);
|
||||||
self.func
|
Function { chunk: Rc::new(self.chunk), attrs: self.attrs }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish_repl(mut self) -> (Function, Vec<Local>) {
|
pub fn finish_repl(mut self) -> (Function, Vec<Local>) {
|
||||||
self.emit(I::Return);
|
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 {
|
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 {
|
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) {
|
fn emit_discard(&mut self, mut n: usize) {
|
||||||
while n > 0 {
|
while n > 0 {
|
||||||
let instrs = &mut self.func.chunk.instrs;
|
let instrs = &mut self.chunk.instrs;
|
||||||
|
|
||||||
// dup followed by store: remove the dup
|
// dup followed by store: remove the dup
|
||||||
if instrs.len() >= 2
|
if instrs.len() >= 2
|
||||||
|
@ -128,9 +129,9 @@ impl<'a> Compiler<'a> {
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
// can't panic: checked that instrs.len() >= 2
|
// can't panic: checked that instrs.len() >= 2
|
||||||
let i = self.func.chunk.instrs.pop().unwrap();
|
let i = self.chunk.instrs.pop().unwrap();
|
||||||
self.func.chunk.instrs.pop().unwrap();
|
self.chunk.instrs.pop().unwrap();
|
||||||
self.func.chunk.instrs.push(i);
|
self.chunk.instrs.push(i);
|
||||||
n -= 1;
|
n -= 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -160,11 +161,11 @@ impl<'a> Compiler<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ip(&self) -> usize {
|
fn ip(&self) -> usize {
|
||||||
self.func.chunk.instrs.len()
|
self.chunk.instrs.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_instr(&mut self, n: usize, new: I) {
|
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) {
|
fn begin_scope(&mut self) {
|
||||||
|
@ -428,7 +429,7 @@ impl<'a> Compiler<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expr_try(&mut self, body: &Expr, catch_blocks: &[CatchBlock]) {
|
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.emit(I::BeginTry(Arg24::from_usize(idx)));
|
||||||
self.expr(body);
|
self.expr(body);
|
||||||
|
@ -463,7 +464,7 @@ impl<'a> Compiler<'a> {
|
||||||
self.update_instr(addr, I::Jump(ip));
|
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) {
|
fn expr_for(&mut self, name: &str, iter: &Expr, body: &Expr) {
|
||||||
|
@ -480,7 +481,9 @@ impl<'a> Compiler<'a> {
|
||||||
let start = self.ip();
|
let start = self.ip();
|
||||||
|
|
||||||
// call iterator and jump if nil, otherwise store
|
// 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);
|
self.store_local(local);
|
||||||
|
|
||||||
// body
|
// body
|
||||||
|
@ -490,7 +493,7 @@ impl<'a> Compiler<'a> {
|
||||||
// end loop
|
// end loop
|
||||||
self.emit(I::Jump(Arg24::from_usize(start)));
|
self.emit(I::Jump(Arg24::from_usize(start)));
|
||||||
|
|
||||||
self.update_instr(j1, I::IterNext(Arg24::from_usize(self.ip())));
|
self.update_instr(j1, I::IterTest(Arg24::from_usize(self.ip())));
|
||||||
self.end_scope();
|
self.end_scope();
|
||||||
self.emit(I::Nil);
|
self.emit(I::Nil);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,14 @@ pub mod symbol;
|
||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
pub mod value;
|
pub mod value;
|
||||||
pub mod gc;
|
|
||||||
pub mod chunk;
|
pub mod chunk;
|
||||||
pub mod compiler;
|
pub mod compiler;
|
||||||
|
|
||||||
pub use parser::BlockParser as Parser;
|
pub use parser::BlockParser as Parser;
|
||||||
pub use vm::Vm;
|
pub use vm::Vm;
|
||||||
|
|
||||||
|
pub use parser_util::{parse_int, parse_float, parse_str_escapes};
|
||||||
|
|
||||||
type LexResult<'input> = Result<
|
type LexResult<'input> = Result<
|
||||||
(usize, Token<'input>, usize),
|
(usize, Token<'input>, usize),
|
||||||
ParseError<usize, Token<'input>, parser_util::ParseError>
|
ParseError<usize, Token<'input>, parser_util::ParseError>
|
||||||
|
|
|
@ -26,7 +26,6 @@ match {
|
||||||
"false",
|
"false",
|
||||||
"nil",
|
"nil",
|
||||||
// kw variables
|
// kw variables
|
||||||
"const",
|
|
||||||
"global",
|
"global",
|
||||||
"var",
|
"var",
|
||||||
// kw logic
|
// kw logic
|
||||||
|
@ -117,9 +116,8 @@ AssignOp: Option<BinaryOp> = {
|
||||||
"&=" => Some(BinaryOp::Append),
|
"&=" => Some(BinaryOp::Append),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// operations
|
// logical ops
|
||||||
//
|
//
|
||||||
|
|
||||||
// or
|
// or
|
||||||
|
@ -140,12 +138,29 @@ UnaryNot: Box<Expr<'input>> = {
|
||||||
Pipe,
|
Pipe,
|
||||||
}
|
}
|
||||||
|
|
||||||
// |
|
//
|
||||||
|
// pipe
|
||||||
|
//
|
||||||
|
|
||||||
Pipe: Box<Expr<'input>> = {
|
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,
|
BinaryCompare,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// operations
|
||||||
|
//
|
||||||
|
|
||||||
// == != > < >= <=
|
// == != > < >= <=
|
||||||
BinaryCompare: Box<Expr<'input>> = {
|
BinaryCompare: Box<Expr<'input>> = {
|
||||||
|
@ -250,7 +265,7 @@ UnaryMinus2: Box<Expr<'input>> = {
|
||||||
|
|
||||||
// function call
|
// function call
|
||||||
FunctionCall: Box<Expr<'input>> = {
|
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,
|
FieldAccess,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,8 +292,7 @@ TermNotIdent: Box<Expr<'input>> = {
|
||||||
"[" <ExprList> "]" => Box::new(Expr::List(<>)),
|
"[" <ExprList> "]" => Box::new(Expr::List(<>)),
|
||||||
"{" <TableItems> "}" => Box::new(Expr::Table(<>)),
|
"{" <TableItems> "}" => Box::new(Expr::Table(<>)),
|
||||||
"$" => Box::new(Expr::Ident("$")),
|
"$" => Box::new(Expr::Ident("$")),
|
||||||
":" "(" <e:Block> ")" => Box::new(Expr::Lambda(vec!["$"], e)),
|
"$$" => Box::new(Expr::Ident("$$")),
|
||||||
"\\" <xs:IdentList> "->" <e:Term> => Box::new(Expr::Lambda(xs, e)),
|
|
||||||
|
|
||||||
"do" <Block> "end" => <>,
|
"do" <Block> "end" => <>,
|
||||||
"if" <IfStmtChain> => <>,
|
"if" <IfStmtChain> => <>,
|
||||||
|
|
|
@ -9,6 +9,21 @@ struct SymbolTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
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: Symbol = symbol!(type);
|
pub static ref SYM_TYPE: Symbol = symbol!(type);
|
||||||
pub static ref SYM_MSG: Symbol = symbol!(msg);
|
pub static ref SYM_MSG: Symbol = symbol!(msg);
|
||||||
pub static ref SYM_DATA: Symbol = symbol!(data);
|
pub static ref SYM_DATA: Symbol = symbol!(data);
|
||||||
|
@ -19,7 +34,6 @@ lazy_static! {
|
||||||
pub static ref SYM_HASH_ERROR: Symbol = symbol!(hash_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_CALL_STACK_OVERFLOW: Symbol = symbol!(call_stack_overflow);
|
||||||
pub static ref SYM_INTERRUPTED: Symbol = symbol!(interrupted);
|
pub static ref SYM_INTERRUPTED: Symbol = symbol!(interrupted);
|
||||||
pub static ref SYM_STOP_ITERATOR: Symbol = symbol!(stop_iterator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static TABLE: OnceLock<Mutex<SymbolTable>> = OnceLock::new();
|
static TABLE: OnceLock<Mutex<SymbolTable>> = OnceLock::new();
|
||||||
|
|
|
@ -20,6 +20,14 @@ impl Exception {
|
||||||
Self { ty, msg: Some(msg), data: None }
|
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> {
|
pub fn from_table(table: &Rc<RefCell<HashMap<HashValue, Value>>>) -> Option<Self> {
|
||||||
let table = table.borrow();
|
let table = table.borrow();
|
||||||
let ty = table.get(&(*SYM_TYPE).into())?;
|
let ty = table.get(&(*SYM_TYPE).into())?;
|
||||||
|
|
|
@ -4,28 +4,56 @@ use crate::{chunk::Chunk, Vm};
|
||||||
|
|
||||||
use super::{Value, exception::Result};
|
use super::{Value, exception::Result};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub struct Function {
|
pub struct FuncAttrs {
|
||||||
pub arity: usize,
|
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 struct NativeFunc {
|
||||||
pub arity: usize,
|
pub attrs: FuncAttrs,
|
||||||
#[allow(clippy::type_complexity)]
|
pub func: FnNative,
|
||||||
pub f: Box<dyn Fn(&mut Vm, Vec<Value>) -> Result<Value>>,
|
}
|
||||||
|
|
||||||
|
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 {
|
impl std::fmt::Debug for NativeFunc {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("NativeFunc")
|
f.debug_struct("NativeFunc")
|
||||||
.field("arity", &self.arity)
|
.field("attrs", &self.attrs)
|
||||||
.finish_non_exhaustive()
|
.finish_non_exhaustive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disasm_recursive(f: &Rc<Function>, w: &mut impl std::io::Write) -> std::io::Result<()> {
|
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)?;
|
writeln!(w, "{} ({}{})",
|
||||||
|
Value::Function(f.clone()),
|
||||||
|
f.attrs.arity,
|
||||||
|
if f.attrs.variadic { ".." } else { "" })?;
|
||||||
if !f.chunk.consts.is_empty() {
|
if !f.chunk.consts.is_empty() {
|
||||||
writeln!(w, "constants")?;
|
writeln!(w, "constants")?;
|
||||||
for (i, c) in f.chunk.consts.iter().enumerate() {
|
for (i, c) in f.chunk.consts.iter().enumerate() {
|
||||||
|
|
125
talc-lang/src/value/index.rs
Normal file
125
talc-lang/src/value/index.rs
Normal 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:#}")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
pub use num_complex::Complex64;
|
||||||
use num_rational::Rational64;
|
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}};
|
use self::{range::{Range, RangeType}, function::{Function, NativeFunc}, exception::{Exception, throw}};
|
||||||
|
|
||||||
|
@ -11,12 +11,14 @@ pub mod exception;
|
||||||
pub mod function;
|
pub mod function;
|
||||||
pub mod ops;
|
pub mod ops;
|
||||||
pub mod range;
|
pub mod range;
|
||||||
|
pub mod index;
|
||||||
|
|
||||||
type RcList = Rc<RefCell<Vec<Value>>>;
|
type RcList = Rc<RefCell<Vec<Value>>>;
|
||||||
type RcTable = Rc<RefCell<HashMap<HashValue, Value>>>;
|
type RcTable = Rc<RefCell<HashMap<HashValue, Value>>>;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
|
#[default]
|
||||||
Nil,
|
Nil,
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
Symbol(Symbol),
|
Symbol(Symbol),
|
||||||
|
@ -27,6 +29,7 @@ pub enum Value {
|
||||||
Ratio(Rational64),
|
Ratio(Rational64),
|
||||||
Complex(Complex64),
|
Complex(Complex64),
|
||||||
|
|
||||||
|
Cell(Rc<RefCell<Value>>),
|
||||||
String(Rc<str>),
|
String(Rc<str>),
|
||||||
List(RcList),
|
List(RcList),
|
||||||
Table(RcTable),
|
Table(RcTable),
|
||||||
|
@ -37,62 +40,82 @@ pub enum Value {
|
||||||
impl Display for Value {
|
impl Display for Value {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Value::Nil => write!(f, "nil"),
|
Self::Nil => write!(f, "nil"),
|
||||||
Value::Bool(b) => write!(f, "{b}"),
|
Self::Bool(b) => write!(f, "{b}"),
|
||||||
Value::Symbol(s) => write!(f, ":{}", s.name()),
|
Self::Symbol(s) => write!(f, ":{}", s.name()),
|
||||||
Value::Range(r) => match r.ty {
|
Self::Range(r) => match r.ty {
|
||||||
RangeType::Open => write!(f, "{}..{}", r.start, r.stop),
|
RangeType::Open => write!(f, "{}..{}", r.start, r.stop),
|
||||||
RangeType::Closed => write!(f, "{}..={}", r.start, r.stop),
|
RangeType::Closed => write!(f, "{}..={}", r.start, r.stop),
|
||||||
RangeType::Endless => write!(f, "{}..*", r.start),
|
RangeType::Endless => write!(f, "{}..*", r.start),
|
||||||
},
|
},
|
||||||
Value::Int(n) => write!(f, "{n}"),
|
Self::Int(n) => write!(f, "{n}"),
|
||||||
Value::Float(x) => write!(f, "{x:?}"),
|
Self::Float(x) => write!(f, "{x:?}"),
|
||||||
Value::Ratio(r) => write!(f, "{}/{}", r.numer(), r.denom()),
|
Self::Ratio(r) => write!(f, "{}/{}", r.numer(), r.denom()),
|
||||||
Value::Complex(z) => write!(f, "{z}"),
|
Self::Complex(z) => write!(f, "{z}"),
|
||||||
|
|
||||||
Value::String(s) => {
|
Self::Cell(v) if f.alternate() => write!(f, "cell({:#})", v.borrow()),
|
||||||
if f.alternate() {
|
Self::Cell(v) => write!(f, "{})", v.borrow()),
|
||||||
write!(f, "{s:?}")
|
|
||||||
} else {
|
Self::String(s) if f.alternate() => write!(f, "{s:?}"),
|
||||||
write!(f, "{s}")
|
Self::String(s) => write!(f, "{s}"),
|
||||||
}
|
|
||||||
},
|
Self::List(l) => {
|
||||||
Value::List(l) => {
|
|
||||||
write!(f, "[")?;
|
write!(f, "[")?;
|
||||||
for (i, item) in l.borrow().iter().enumerate() {
|
for (i, item) in l.borrow().iter().enumerate() {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
write!(f, ", ")?;
|
write!(f, ", ")?;
|
||||||
}
|
}
|
||||||
if f.alternate() {
|
|
||||||
write!(f, "{item:#}")?;
|
write!(f, "{item:#}")?;
|
||||||
} else {
|
|
||||||
write!(f, "{item}")?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
write!(f, "]")
|
write!(f, "]")
|
||||||
},
|
},
|
||||||
Value::Table(t) => {
|
Self::Table(t) => {
|
||||||
write!(f, "{{ ")?;
|
write!(f, "{{ ")?;
|
||||||
for (i, (k, v)) in t.borrow().iter().enumerate() {
|
for (i, (k, v)) in t.borrow().iter().enumerate() {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
write!(f, ", ")?;
|
write!(f, ", ")?;
|
||||||
}
|
}
|
||||||
if f.alternate() {
|
|
||||||
write!(f, "({:#}) = {v:#}", k.0)?;
|
write!(f, "({:#}) = {v:#}", k.0)?;
|
||||||
} else {
|
|
||||||
write!(f, "({}) = {v}", k.0)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
write!(f, " }}")
|
write!(f, " }}")
|
||||||
},
|
},
|
||||||
Value::Function(g)
|
Self::Function(g)
|
||||||
=> write!(f, "<function {:?}>", Rc::as_ptr(g)),
|
=> write!(f, "<function {:?}>", Rc::as_ptr(g)),
|
||||||
Value::NativeFunc(g)
|
Self::NativeFunc(g)
|
||||||
=> write!(f, "<native function {:?}>", Rc::as_ptr(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)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct HashValue(Value);
|
pub struct HashValue(Value);
|
||||||
|
@ -102,15 +125,10 @@ impl Eq for HashValue {}
|
||||||
impl TryFrom<Value> for HashValue {
|
impl TryFrom<Value> for HashValue {
|
||||||
type Error = Exception;
|
type Error = Exception;
|
||||||
fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
|
fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
|
||||||
match value {
|
if !value.hashable() {
|
||||||
Value::Nil
|
throw!(*SYM_HASH_ERROR, "value {value:#} cannot be hashed")
|
||||||
| Value::Bool(_)
|
|
||||||
| Value::Symbol(_)
|
|
||||||
| Value::Int(_)
|
|
||||||
| Value::Ratio(_)
|
|
||||||
| Value::String(_) => Ok(Self(value)),
|
|
||||||
_ => throw!(*SYM_HASH_ERROR, "value {value:#} cannot be hashed"),
|
|
||||||
}
|
}
|
||||||
|
Ok(Self(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,6 +203,12 @@ impl_from!(Vec<Value>, List, rcref);
|
||||||
impl_from!(Function, Function, rc);
|
impl_from!(Function, Function, rc);
|
||||||
impl_from!(NativeFunc, NativeFunc, rc);
|
impl_from!(NativeFunc, NativeFunc, rc);
|
||||||
impl_from!(Rc<str>, String);
|
impl_from!(Rc<str>, String);
|
||||||
|
impl_from!(String, String, into);
|
||||||
impl_from!(&str, String, into);
|
impl_from!(&str, String, into);
|
||||||
impl_from!(Box<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()
|
||||||
|
}
|
||||||
|
|
|
@ -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_complex::Complex64;
|
||||||
use num_rational::Rational64;
|
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 {
|
impl Value {
|
||||||
pub fn truthy(&self) -> bool {
|
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::Complex(c) => !(c.re == 0.0 && c.im == 0.0 || c.is_nan()),
|
||||||
Value::String(s) => s.len() > 0,
|
Value::String(s) => s.len() > 0,
|
||||||
Value::List(l) => l.borrow().len() > 0,
|
Value::List(l) => l.borrow().len() > 0,
|
||||||
|
Value::Cell(v) => v.borrow().truthy(),
|
||||||
|
|
||||||
Value::Symbol(_) | Value::Table(_)
|
Value::Symbol(_) | Value::Table(_)
|
||||||
| Value::Function(_) | Value::NativeFunc(_) => true,
|
| Value::Function(_) | Value::NativeFunc(_) => true,
|
||||||
|
@ -32,7 +33,7 @@ fn ratio_to_f64(r: Rational64) -> f64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[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;
|
use Value as V;
|
||||||
match (&a, &b) {
|
match (&a, &b) {
|
||||||
(V::Int(x), V::Ratio(..)) => (V::Ratio((*x).into()), 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 {
|
fn div(self, rhs: Value) -> Self::Output {
|
||||||
use Value as V;
|
use Value as V;
|
||||||
match promote(self, rhs) {
|
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::Int(x), V::Int(y)) => Ok(V::Ratio(Rational64::new(x, y))),
|
||||||
(V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x / y)),
|
(V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x / y)),
|
||||||
(V::Float(x), V::Float(y)) => Ok(V::Float(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)]
|
#[allow(clippy::cast_sign_loss)]
|
||||||
const fn ipow(n: i64, p: u64) -> i64 {
|
fn ipow(n: i64, p: u64) -> Result<i64> {
|
||||||
match (n, p) {
|
match (n, p) {
|
||||||
(0, 0) => panic!("power 0^0"),
|
(0, 0) => throw!(*SYM_TYPE_ERROR, "integer 0 raised to power 0"),
|
||||||
(0, _) => 0,
|
(0, _) => Ok(0),
|
||||||
(_, 0) => 1,
|
(_, 0) => Ok(1),
|
||||||
(n, p) if p > u32::MAX as u64 => {
|
(n, p) if p > u32::MAX as u64 => {
|
||||||
let (lo, hi) = (p as u32, (p >> 32) as u32);
|
let (lo, hi) = (p as u32, (p >> 32) as u32);
|
||||||
let (a, b) = (n.pow(lo), n.pow(hi));
|
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)]
|
#[allow(clippy::cast_sign_loss)]
|
||||||
const fn rpow(n: i64, d: i64, p: i64) -> (i64, i64) {
|
fn rpow(n: i64, d: i64, p: i64) -> Result<(i64, i64)> {
|
||||||
match p {
|
Ok(match p {
|
||||||
0.. => (ipow(n, p as u64), ipow(d, p as u64)),
|
0.. => (ipow(n, p as u64)?, ipow(d, p as u64)?),
|
||||||
_ => (ipow(d, (-p) as u64), ipow(n, (-p) as u64)),
|
_ => (ipow(d, (-p) as u64)?, ipow(n, (-p) as u64)?),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
|
@ -171,11 +175,11 @@ impl Value {
|
||||||
pub fn pow(self, rhs: Value) -> Result<Self> {
|
pub fn pow(self, rhs: Value) -> Result<Self> {
|
||||||
use Value as V;
|
use Value as V;
|
||||||
if let (V::Ratio(x), V::Int(y)) = (&self, &rhs) {
|
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) {
|
match promote(self, rhs) {
|
||||||
(V::Int(x), V::Int(y))
|
(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))
|
(V::Float(x), V::Float(y))
|
||||||
=> Ok(V::Float(x.powf(y))),
|
=> Ok(V::Float(x.powf(y))),
|
||||||
(V::Ratio(x), V::Ratio(y))
|
(V::Ratio(x), V::Ratio(y))
|
||||||
|
@ -214,6 +218,7 @@ impl PartialEq for Value {
|
||||||
(V::String(a), V::String(b)) => *a == *b,
|
(V::String(a), V::String(b)) => *a == *b,
|
||||||
(V::List(a), V::List(b)) => *a.borrow() == *b.borrow(),
|
(V::List(a), V::List(b)) => *a.borrow() == *b.borrow(),
|
||||||
(V::Symbol(a), V::Symbol(b)) => a == b,
|
(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) {
|
(V::Range(a), V::Range(b)) => match (a.ty, b.ty) {
|
||||||
(Rty::Open, Rty::Open) => a.start == b.start && a.stop == b.stop,
|
(Rty::Open, Rty::Open) => a.start == b.start && a.stop == b.stop,
|
||||||
(Rty::Closed, Rty::Closed) => 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> {
|
pub fn concat(&self, other: &Self) -> Result<Self> {
|
||||||
use Value as V;
|
use Value as V;
|
||||||
match (self, other) {
|
match (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> {
|
pub fn to_iter_function(self) -> Result<Self> {
|
||||||
match self {
|
match self {
|
||||||
Self::Function(_) | Self::NativeFunc(_) => Ok(self),
|
Self::Function(_) | Self::NativeFunc(_) => Ok(self),
|
||||||
|
@ -370,15 +341,25 @@ impl Value {
|
||||||
let range_iter = RefCell::new(range.into_iter());
|
let range_iter = RefCell::new(range.into_iter());
|
||||||
let f = move |_: &mut Vm, _: Vec<Value>| -> Result<Value> {
|
let f = move |_: &mut Vm, _: Vec<Value>| -> Result<Value> {
|
||||||
if let Some(v) = range_iter.borrow_mut().next() {
|
if let Some(v) = range_iter.borrow_mut().next() {
|
||||||
Ok(v.into())
|
Ok(Value::from(v).to_cell())
|
||||||
} else {
|
} else {
|
||||||
throw!(*SYM_STOP_ITERATOR)
|
Ok(Value::Nil)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(NativeFunc {
|
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||||
arity: 0,
|
},
|
||||||
f: Box::new(f),
|
Self::String(s) => {
|
||||||
}.into())
|
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) => {
|
Self::List(list) => {
|
||||||
let idx = RefCell::new(0);
|
let idx = RefCell::new(0);
|
||||||
|
@ -386,32 +367,34 @@ impl Value {
|
||||||
let i = *idx.borrow();
|
let i = *idx.borrow();
|
||||||
if let Some(v) = list.borrow().get(i) {
|
if let Some(v) = list.borrow().get(i) {
|
||||||
*idx.borrow_mut() += 1;
|
*idx.borrow_mut() += 1;
|
||||||
Ok(v.clone())
|
Ok(v.clone().to_cell())
|
||||||
} else {
|
} else {
|
||||||
throw!(*SYM_STOP_ITERATOR)
|
Ok(Value::Nil)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(NativeFunc {
|
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||||
arity: 0,
|
|
||||||
f: Box::new(f),
|
|
||||||
}.into())
|
|
||||||
},
|
},
|
||||||
Self::Table(table) => {
|
Self::Table(table) => {
|
||||||
let keys: Vec<HashValue> = table.borrow().keys().cloned().collect();
|
let keys: Vec<HashValue> = table.borrow().keys().cloned().collect();
|
||||||
let keys = RefCell::new(keys.into_iter());
|
let keys = RefCell::new(keys.into_iter());
|
||||||
let f = move |_: &mut Vm, _: Vec<Value>| -> Result<Value> {
|
let f = move |_: &mut Vm, _: Vec<Value>| -> Result<Value> {
|
||||||
if let Some(v) = keys.borrow_mut().next() {
|
if let Some(v) = keys.borrow_mut().next() {
|
||||||
Ok(v.into_inner())
|
Ok(v.into_inner().to_cell())
|
||||||
} else {
|
} else {
|
||||||
throw!(*SYM_STOP_ITERATOR)
|
Ok(Value::Nil)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(NativeFunc {
|
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||||
arity: 0,
|
|
||||||
f: Box::new(f),
|
|
||||||
}.into())
|
|
||||||
},
|
},
|
||||||
_ => throw!(*SYM_TYPE_ERROR, "cannot iterate {self:#}"),
|
_ => 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,3 +41,4 @@ impl IntoIterator for Range {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{cmp::Ordering, collections::HashMap, rc::Rc, sync::{atomic::AtomicBool, Arc}};
|
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 }
|
struct TryFrame { idx: usize, stack_len: usize }
|
||||||
|
|
||||||
|
@ -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 {
|
impl Vm {
|
||||||
pub fn new(stack_max: usize) -> Self {
|
pub fn new(stack_max: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -88,6 +131,70 @@ impl Vm {
|
||||||
self.globals.get(&name)
|
self.globals.get(&name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn globals(&self) -> &HashMap<Symbol, Value> {
|
||||||
|
&self.globals
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<()> {
|
||||||
|
loop {
|
||||||
|
while let Some(try_frame) = frame.try_frames.pop() {
|
||||||
|
let table = &frame.func.chunk.try_tables[try_frame.idx];
|
||||||
|
for catch in &table.catches {
|
||||||
|
if catch.types.is_none() || catch.types.as_ref().unwrap().contains(&exc.ty) {
|
||||||
|
frame.ip = catch.addr;
|
||||||
|
frame.locals.truncate(table.local_count);
|
||||||
|
self.stack.truncate(try_frame.stack_len);
|
||||||
|
self.stack.push(Value::Table(exc.to_table()));
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if frame.root {
|
||||||
|
return Err(exc)
|
||||||
|
}
|
||||||
|
*frame = self.call_stack.pop().expect("no root frame");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn push(&mut self, v: Value) {
|
fn push(&mut self, v: Value) {
|
||||||
self.stack.push(v);
|
self.stack.push(v);
|
||||||
|
@ -233,7 +340,7 @@ impl Vm {
|
||||||
let v = self.pop();
|
let v = self.pop();
|
||||||
let idx = self.pop();
|
let idx = self.pop();
|
||||||
let ct = self.pop();
|
let ct = self.pop();
|
||||||
ct.store_index(idx, v.clone())?;
|
ct.store_index(self, idx, v.clone())?;
|
||||||
self.push(v);
|
self.push(v);
|
||||||
},
|
},
|
||||||
// ip = n
|
// ip = n
|
||||||
|
@ -256,20 +363,16 @@ impl Vm {
|
||||||
let iter = self.pop().to_iter_function()?;
|
let iter = self.pop().to_iter_function()?;
|
||||||
self.push(iter);
|
self.push(iter);
|
||||||
},
|
},
|
||||||
// [i] -> if i() succeeds then [i, i()], otherwise [] and ip = n
|
// [i,cell(v)] -> [i,v]
|
||||||
I::IterNext(n) => {
|
// [i,nil] -> [], ip = n
|
||||||
let v = &self.stack[self.stack.len() - 1];
|
I::IterTest(n) => {
|
||||||
self.call_stack.push(std::mem::take(frame));
|
match self.pop() {
|
||||||
let res = self.call_value(v.clone(), vec![v.clone()]);
|
Value::Cell(c) => self.push(cell_take(c)),
|
||||||
*frame = self.call_stack.pop().expect("no frame to pop");
|
Value::Nil => {
|
||||||
match res {
|
|
||||||
Ok(res) => self.push(res),
|
|
||||||
Err(e) => if e.ty == Symbol::get("stop_iterator") {
|
|
||||||
self.pop();
|
self.pop();
|
||||||
frame.ip = usize::from(n)
|
frame.ip = usize::from(n)
|
||||||
} else {
|
},
|
||||||
return Err(e)
|
v => throw!(*SYM_TYPE_ERROR, "iterator returned invalid value {v}")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// try_frames.push(t, stack.len())
|
// try_frames.push(t, stack.len())
|
||||||
|
@ -286,25 +389,27 @@ impl Vm {
|
||||||
},
|
},
|
||||||
// [f,a0,a1...an] -> [f(a0,a1...an)]
|
// [f,a0,a1...an] -> [f(a0,a1...an)]
|
||||||
I::Call(n) => {
|
I::Call(n) => {
|
||||||
self.check_interrupt()?;
|
|
||||||
let n = usize::from(n);
|
let n = usize::from(n);
|
||||||
|
|
||||||
let args = self.pop_n(n + 1);
|
let args = self.pop_n(n + 1);
|
||||||
let func = &args[0];
|
|
||||||
if let Value::NativeFunc(nf) = func {
|
let args = match get_call_outcome(args)? {
|
||||||
let nf = nf.clone();
|
CallOutcome::Call(args) => args,
|
||||||
if nf.arity != n {
|
CallOutcome::Partial(v) => {
|
||||||
throw!(*SYM_TYPE_ERROR, "function call with wrong argument count");
|
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));
|
self.call_stack.push(std::mem::take(frame));
|
||||||
let res = (nf.f)(self, args)?;
|
let res = (nf.func)(self, args)?;
|
||||||
*frame = self.call_stack.pop().expect("no frame to pop");
|
*frame = self.call_stack.pop().expect("no frame to pop");
|
||||||
|
|
||||||
self.stack.push(res);
|
self.stack.push(res);
|
||||||
} else if let Value::Function(func) = func {
|
} else if let Value::Function(func) = &args[0] {
|
||||||
if func.arity != n {
|
|
||||||
throw!(*SYM_TYPE_ERROR, "function call with wrong argument count");
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.call_stack.len() + 1 >= self.stack_max {
|
if self.call_stack.len() + 1 >= self.stack_max {
|
||||||
throw!(*SYM_CALL_STACK_OVERFLOW, "call stack overflow")
|
throw!(*SYM_CALL_STACK_OVERFLOW, "call stack overflow")
|
||||||
|
@ -314,7 +419,7 @@ impl Vm {
|
||||||
let func = func.clone();
|
let func = func.clone();
|
||||||
*frame = CallFrame::new(func, args);
|
*frame = CallFrame::new(func, args);
|
||||||
} else {
|
} else {
|
||||||
throw!(*SYM_TYPE_ERROR, "attempt to call non-function {func}");
|
unreachable!("already verified by calling get_call_type");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// [v] -> [], return v
|
// [v] -> [], return v
|
||||||
|
@ -323,6 +428,7 @@ impl Vm {
|
||||||
},
|
},
|
||||||
// [v] -> [], return v
|
// [v] -> [], return v
|
||||||
I::Return => {
|
I::Return => {
|
||||||
|
self.check_interrupt()?;
|
||||||
*frame = self.call_stack.pop().expect("no root frame");
|
*frame = self.call_stack.pop().expect("no root frame");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -330,70 +436,24 @@ impl Vm {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_exception(&mut self, frame: &mut CallFrame, exc: Exception) -> Result<()> {
|
|
||||||
loop {
|
|
||||||
while let Some(try_frame) = frame.try_frames.pop() {
|
|
||||||
let table = &frame.func.chunk.try_tables[try_frame.idx];
|
|
||||||
for catch in &table.catches {
|
|
||||||
if catch.types.is_none() || catch.types.as_ref().unwrap().contains(&exc.ty) {
|
|
||||||
frame.ip = catch.addr;
|
|
||||||
frame.locals.truncate(table.local_count);
|
|
||||||
self.stack.truncate(try_frame.stack_len);
|
|
||||||
self.stack.push(Value::Table(exc.to_table()));
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if frame.root {
|
|
||||||
return Err(exc)
|
|
||||||
}
|
|
||||||
*frame = self.call_stack.pop().expect("no root frame");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,17 +1,34 @@
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use syn::{LitInt, ItemFn};
|
use syn::{parse::Parse, parse_macro_input, ItemFn, LitInt, Token};
|
||||||
use quote::quote;
|
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]
|
#[proc_macro_attribute]
|
||||||
pub fn native_func(input: TokenStream, annotated_item: TokenStream) -> TokenStream {
|
pub fn native_func(input: TokenStream, annotated_item: TokenStream) -> TokenStream {
|
||||||
let itemfn: ItemFn = syn::parse(annotated_item).unwrap();
|
let Ok(itemfn) = syn::parse::<ItemFn>(annotated_item.clone()) else {
|
||||||
let arity: LitInt = syn::parse(input).unwrap();
|
return annotated_item
|
||||||
|
};
|
||||||
|
let args: NativeFuncArgs = parse_macro_input!(input as NativeFuncArgs);
|
||||||
|
|
||||||
let visibility = itemfn.vis;
|
let visibility = itemfn.vis;
|
||||||
let block = itemfn.block;
|
let block = itemfn.block;
|
||||||
let name = itemfn.sig.ident;
|
let name = itemfn.sig.ident;
|
||||||
let inputs = itemfn.sig.inputs;
|
let inputs = itemfn.sig.inputs;
|
||||||
let output = itemfn.sig.output;
|
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.constness.is_none(), "item must not be const");
|
||||||
assert!(itemfn.sig.asyncness.is_none(), "item must not be async");
|
assert!(itemfn.sig.asyncness.is_none(), "item must not be async");
|
||||||
|
@ -22,8 +39,11 @@ pub fn native_func(input: TokenStream, annotated_item: TokenStream) -> TokenStre
|
||||||
let expanded = quote! {
|
let expanded = quote! {
|
||||||
#visibility fn #name() -> ::talc_lang::value::function::NativeFunc {
|
#visibility fn #name() -> ::talc_lang::value::function::NativeFunc {
|
||||||
::talc_lang::value::function::NativeFunc {
|
::talc_lang::value::function::NativeFunc {
|
||||||
|
attrs: ::talc_lang::value::function::FuncAttrs{
|
||||||
arity: #arity,
|
arity: #arity,
|
||||||
f: Box::new(|#inputs| #output #block)
|
variadic: #variadic,
|
||||||
|
},
|
||||||
|
func: Box::new(|#inputs| #output #block)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,3 +7,8 @@ edition = "2021"
|
||||||
talc-lang = { path = "../talc-lang" }
|
talc-lang = { path = "../talc-lang" }
|
||||||
talc-macros = { path = "../talc-macros" }
|
talc-macros = { path = "../talc-macros" }
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
rand = { version = "0.8", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["random"]
|
||||||
|
random = ["dep:rand"]
|
||||||
|
|
208
talc-std/src/collection.rs
Normal file
208
talc-std/src/collection.rs
Normal 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())
|
||||||
|
}
|
|
@ -1,22 +1,31 @@
|
||||||
use talc_lang::{symbol::SYM_TYPE_ERROR, value::{exception::{throw, Exception, Result}, Value}, Vm};
|
use talc_lang::{symbol::SYM_TYPE_ERROR, value::{exception::{throw, Exception, Result}, Value}, Vm};
|
||||||
use talc_macros::native_func;
|
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> {
|
pub fn throw(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||||
let [_, ty, msg] = args.try_into().expect("bypassed arity check");
|
let ([_, ty], varargs) = unpack_varargs!(args);
|
||||||
let Value::Symbol(ty) = ty else {
|
let Value::Symbol(ty) = ty else {
|
||||||
throw!(*SYM_TYPE_ERROR, "exception type must be a symbol")
|
throw!(*SYM_TYPE_ERROR, "exception type must be a symbol")
|
||||||
};
|
};
|
||||||
match msg {
|
Err(match &*varargs {
|
||||||
Value::String(msg) => Err(Exception::new_with_msg(ty, msg)),
|
[] | [Value::Nil]
|
||||||
Value::Nil => Err(Exception::new(ty)),
|
=> Exception::new(ty),
|
||||||
_ => throw!(*SYM_TYPE_ERROR, "exception message must be a string")
|
[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)]
|
#[native_func(1)]
|
||||||
pub fn rethrow(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
pub fn rethrow(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||||
let [_, table] = args.try_into().expect("bypassed arity check");
|
let [_, table] = unpack_args!(args);
|
||||||
if let Value::Table(table) = table {
|
if let Value::Table(table) = table {
|
||||||
if let Some(e) = Exception::from_table(&table) {
|
if let Some(e) = Exception::from_table(&table) {
|
||||||
return Err(e)
|
return Err(e)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
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 talc_macros::native_func;
|
||||||
|
|
||||||
use crate::SYM_IO_ERROR;
|
use crate::{unpack_args, SYM_IO_ERROR};
|
||||||
|
|
||||||
#[native_func(1)]
|
#[native_func(1)]
|
||||||
pub fn print(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
pub fn print(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||||
|
@ -30,8 +30,41 @@ pub fn readln(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
|
||||||
Ok(buf.into())
|
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) {
|
pub fn load(vm: &mut Vm) {
|
||||||
vm.set_global_name("print", print().into());
|
vm.set_global_name("print", print().into());
|
||||||
vm.set_global_name("println", println().into());
|
vm.set_global_name("println", println().into());
|
||||||
vm.set_global_name("readln", readln().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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 talc_macros::native_func;
|
||||||
|
|
||||||
#[native_func(1)]
|
use crate::{unpack_args, unpack_varargs};
|
||||||
pub fn iter(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|
||||||
let [_, v] = args.try_into().expect("bypassed arity check");
|
|
||||||
v.to_iter_function()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 load(vm: &mut Vm) {
|
pub fn load(vm: &mut Vm) {
|
||||||
vm.set_global_name("iter", iter().into());
|
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("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("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] = unpack_args!(args);
|
||||||
|
|
||||||
|
v.to_iter_function()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[native_func(1)]
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,47 @@
|
||||||
use talc_lang::{symbol::{symbol, Symbol}, Vm};
|
use talc_lang::{symbol::{symbol, Symbol}, Vm};
|
||||||
|
|
||||||
pub mod num;
|
pub mod value;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
pub mod iter;
|
pub mod iter;
|
||||||
pub mod exception;
|
pub mod exception;
|
||||||
|
pub mod num;
|
||||||
|
pub mod collection;
|
||||||
|
#[cfg(feature="random")]
|
||||||
|
pub mod random;
|
||||||
|
|
||||||
pub fn load_all(vm: &mut Vm) {
|
pub fn load_all(vm: &mut Vm) {
|
||||||
|
value::load(vm);
|
||||||
|
exception::load(vm);
|
||||||
|
iter::load(vm);
|
||||||
|
collection::load(vm);
|
||||||
num::load(vm);
|
num::load(vm);
|
||||||
io::load(vm);
|
io::load(vm);
|
||||||
iter::load(vm);
|
#[cfg(feature="random")]
|
||||||
exception::load(vm);
|
random::load(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
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;
|
||||||
|
|
|
@ -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) {
|
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
50
talc-std/src/random.rs
Normal 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
170
talc-std/src/value.rs
Normal 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))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue