Compare commits
8 commits
4e61ca90f0
...
151c6abf45
Author | SHA1 | Date | |
---|---|---|---|
151c6abf45 | |||
ba7db1e91b | |||
801aaf7b78 | |||
59f3e320e3 | |||
0560847753 | |||
d04f98dedc | |||
754fbf6c2c | |||
e4b4c981d2 |
48 changed files with 5660 additions and 2673 deletions
719
Cargo.lock
generated
719
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
23
examples/closures.talc
Executable file
23
examples/closures.talc
Executable file
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env talc
|
||||
|
||||
-- adapted from Crafting Interpreters 10.6
|
||||
|
||||
make_counter = \-> do
|
||||
var i = 0
|
||||
\-> do
|
||||
i += 1
|
||||
println(i)
|
||||
end
|
||||
end
|
||||
|
||||
var counter1 = make_counter()
|
||||
counter1() -- 1
|
||||
counter1() -- 2
|
||||
counter1() -- 3
|
||||
counter1() -- 4
|
||||
var counter2 = make_counter()
|
||||
counter2() -- 1
|
||||
counter2() -- 2
|
||||
counter1() -- 5
|
||||
counter1() -- 6
|
||||
counter2() -- 3
|
23
examples/closures2.talc
Executable file
23
examples/closures2.talc
Executable file
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env talc
|
||||
|
||||
var i = 0;
|
||||
|
||||
var outer = \n -> do
|
||||
var inner = \-> do
|
||||
i = i + n
|
||||
end
|
||||
end
|
||||
|
||||
var by3 = outer(3)
|
||||
var by5 = outer(5)
|
||||
|
||||
by3()
|
||||
println(i) -- 3
|
||||
by3()
|
||||
println(i) -- 6
|
||||
by5()
|
||||
println(i) -- 11
|
||||
by5()
|
||||
println(i) -- 16
|
||||
by3()
|
||||
println(i) -- 19
|
9
examples/totient.talc
Executable file
9
examples/totient.talc
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env talc
|
||||
|
||||
totient = \n -> do
|
||||
count(factors(n)) | pairs | map(\v -> do
|
||||
v[0]^v[1] - v[0]^(v[1] - 1)
|
||||
end) | prod
|
||||
end
|
||||
|
||||
println(totient(6615)) -- 3024
|
3
format.sh
Executable file
3
format.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
cargo +nightly fmt
|
11
rustfmt.toml
Normal file
11
rustfmt.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
unstable_features = true
|
||||
|
||||
hard_tabs = true
|
||||
tab_spaces = 4
|
||||
max_width = 92
|
||||
|
||||
trailing_semicolon = false
|
||||
trailing_comma = "Vertical"
|
||||
|
||||
use_field_init_shorthand = true
|
||||
|
|
@ -11,5 +11,5 @@ path = "src/main.rs"
|
|||
talc-lang = { path = "../talc-lang" }
|
||||
talc-std = { path = "../talc-std" }
|
||||
rustyline = "14.0"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
clap = { version = "4.5", features = ["std", "help", "usage", "derive", "error-context"], default-features = false }
|
||||
ctrlc = "3.4"
|
||||
|
|
|
@ -1,42 +1,25 @@
|
|||
use std::{borrow::Cow, cell::RefCell, collections::HashMap, rc::Rc};
|
||||
use std::{borrow::Cow, cell::RefCell, rc::Rc};
|
||||
|
||||
use rustyline::{completion::Completer, highlight::Highlighter, hint::Hinter, validate::{ValidationContext, ValidationResult, Validator}, Helper, Result};
|
||||
use talc_lang::{lstring::LStr, Lexer, Vm};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum TokenType {
|
||||
String, Symbol, Number, Literal
|
||||
}
|
||||
use rustyline::{
|
||||
completion::Completer,
|
||||
highlight::Highlighter,
|
||||
hint::Hinter,
|
||||
validate::{ValidationContext, ValidationResult, Validator},
|
||||
Helper, Result,
|
||||
};
|
||||
use talc_lang::{
|
||||
lstring::LStr,
|
||||
parser::{Lexer, Pos, Span, TokenKind},
|
||||
Vm,
|
||||
};
|
||||
|
||||
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,
|
||||
}
|
||||
Self { vm }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +43,11 @@ impl Completer for TalcHelper {
|
|||
}
|
||||
}
|
||||
let res: String = res.chars().rev().collect();
|
||||
let mut keys = self.vm.borrow().globals().keys()
|
||||
let mut keys = self
|
||||
.vm
|
||||
.borrow()
|
||||
.globals()
|
||||
.keys()
|
||||
.map(|sym| sym.name())
|
||||
.filter(|name| name.starts_with(LStr::from_str(&res)))
|
||||
.map(LStr::to_string)
|
||||
|
@ -76,24 +63,33 @@ impl Hinter for TalcHelper {
|
|||
|
||||
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 lexer = Lexer::new(line);
|
||||
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" }
|
||||
let mut last = Pos::new();
|
||||
while let Some(Ok(token)) = lexer.next() {
|
||||
if token.kind == TokenKind::Eof {
|
||||
break
|
||||
}
|
||||
buf += &line[last..];
|
||||
buf += Span::new(last, token.span.start).of(line);
|
||||
last = token.span.end;
|
||||
let format = match token.kind {
|
||||
TokenKind::Nil
|
||||
| TokenKind::True
|
||||
| TokenKind::False
|
||||
| TokenKind::Integer
|
||||
| TokenKind::Float
|
||||
| TokenKind::Imaginary => "\x1b[93m",
|
||||
TokenKind::String => "\x1b[92m",
|
||||
TokenKind::Symbol => "\x1b[96m",
|
||||
_ => "",
|
||||
};
|
||||
buf += format;
|
||||
buf += token.content;
|
||||
if !format.is_empty() {
|
||||
buf += "\x1b[0m"
|
||||
}
|
||||
}
|
||||
buf += &line[(last.idx as usize)..];
|
||||
Cow::Owned(buf)
|
||||
}
|
||||
|
||||
|
@ -116,62 +112,92 @@ impl Highlighter for TalcHelper {
|
|||
|
||||
impl Validator for TalcHelper {
|
||||
fn validate(&self, ctx: &mut ValidationContext) -> Result<ValidationResult> {
|
||||
let tokens = self.lex.lex(ctx.input());
|
||||
use TokenKind as K;
|
||||
let lexer = Lexer::new(ctx.input());
|
||||
let mut delims = Vec::new();
|
||||
let mut mismatch = None;
|
||||
for token in tokens {
|
||||
for token in lexer {
|
||||
let token = match token {
|
||||
Ok(t) => t,
|
||||
Err(e) => return Ok(ValidationResult::Invalid(
|
||||
Some(e.to_string()))),
|
||||
Err(e) => return Ok(ValidationResult::Invalid(Some(format!(" {e}")))),
|
||||
};
|
||||
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 }
|
||||
let k = token.kind;
|
||||
let s = token.span;
|
||||
match k {
|
||||
K::Eof => break,
|
||||
K::LParen | K::LBrack | K::LBrace | K::If | K::While | K::For | K::Try => {
|
||||
delims.push(token.kind)
|
||||
}
|
||||
"catch" => match delims.pop() {
|
||||
Some("try") => delims.push(t),
|
||||
v => { mismatch = Some((v, t)); break }
|
||||
K::RParen => match delims.pop() {
|
||||
Some(K::LParen) => (),
|
||||
v => {
|
||||
mismatch = Some((v, k, s));
|
||||
break
|
||||
}
|
||||
"do" => match delims.last().copied() {
|
||||
Some("while" | "for" | "catch") => {
|
||||
},
|
||||
K::RBrack => match delims.pop() {
|
||||
Some(K::LBrack) => (),
|
||||
v => {
|
||||
mismatch = Some((v, k, s));
|
||||
break
|
||||
}
|
||||
},
|
||||
K::RBrace => match delims.pop() {
|
||||
Some(K::LBrace) => (),
|
||||
v => {
|
||||
mismatch = Some((v, k, s));
|
||||
break
|
||||
}
|
||||
},
|
||||
K::Then => match delims.pop() {
|
||||
Some(K::If | K::Elif) => delims.push(k),
|
||||
v => {
|
||||
mismatch = Some((v, k, s));
|
||||
break
|
||||
}
|
||||
},
|
||||
K::Catch => match delims.pop() {
|
||||
Some(K::Try) => delims.push(k),
|
||||
v => {
|
||||
mismatch = Some((v, k, s));
|
||||
break
|
||||
}
|
||||
},
|
||||
K::Do => match delims.last().copied() {
|
||||
Some(K::While | K::For | K::Catch) => {
|
||||
delims.pop();
|
||||
delims.push(t);
|
||||
delims.push(k);
|
||||
}
|
||||
_ => delims.push(k),
|
||||
},
|
||||
_ => delims.push(t)
|
||||
K::Elif | K::Else => match delims.pop() {
|
||||
Some(K::Then) => delims.push(k),
|
||||
v => {
|
||||
mismatch = Some((v, k, s));
|
||||
break
|
||||
}
|
||||
},
|
||||
"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 }
|
||||
K::End => match delims.pop() {
|
||||
Some(K::Then | K::Else | K::Do | K::Try | K::Catch) => (),
|
||||
v => {
|
||||
mismatch = Some((v, k, s));
|
||||
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}")))),
|
||||
Some((None, b, s)) => {
|
||||
return Ok(ValidationResult::Invalid(Some(format!(
|
||||
" found unmatched {b} at {s}"
|
||||
))))
|
||||
}
|
||||
Some((Some(a), b, s)) => {
|
||||
return Ok(ValidationResult::Invalid(Some(format!(
|
||||
" found {a} matched with {b} at {s}"
|
||||
))))
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
|
@ -180,6 +206,5 @@ impl Validator for TalcHelper {
|
|||
} else {
|
||||
Ok(ValidationResult::Incomplete)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use clap::{ColorChoice, Parser};
|
||||
use talc_lang::{compiler::compile, value::function::disasm_recursive, Vm};
|
||||
use std::{path::PathBuf, process::ExitCode, rc::Rc};
|
||||
use talc_lang::{
|
||||
compiler::compile, parser, symbol::Symbol, value::function::disasm_recursive, Vm,
|
||||
};
|
||||
|
||||
mod repl;
|
||||
mod helper;
|
||||
mod repl;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
|
@ -20,28 +22,27 @@ struct Args {
|
|||
disasm: bool,
|
||||
|
||||
/// show disassembled bytecode
|
||||
#[arg(short='H', long)]
|
||||
#[arg(short = 'H', long)]
|
||||
histfile: Option<PathBuf>,
|
||||
|
||||
/// enable or disable color
|
||||
#[arg(short, long, default_value="auto")]
|
||||
#[arg(short, long, default_value = "auto")]
|
||||
color: ColorChoice,
|
||||
}
|
||||
|
||||
fn exec(src: &str, args: &Args) -> ExitCode {
|
||||
let parser = talc_lang::Parser::new();
|
||||
fn exec(name: Symbol, src: &str, args: &Args) -> ExitCode {
|
||||
let mut vm = Vm::new(256);
|
||||
talc_std::load_all(&mut vm);
|
||||
|
||||
let ex = match parser.parse(src) {
|
||||
let ex = match parser::parse(src) {
|
||||
Ok(ex) => ex,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {e}");
|
||||
return ExitCode::FAILURE
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let func = Rc::new(compile(&ex));
|
||||
let func = Rc::new(compile(&ex, Some(name)));
|
||||
|
||||
if args.disasm {
|
||||
if let Err(e) = disasm_recursive(&func, &mut std::io::stderr()) {
|
||||
|
@ -65,8 +66,10 @@ fn main() -> ExitCode {
|
|||
return repl::repl(&args)
|
||||
}
|
||||
|
||||
match std::fs::read_to_string(args.file.as_ref().unwrap()) {
|
||||
Ok(s) => exec(&s, &args),
|
||||
let file = args.file.as_ref().unwrap();
|
||||
|
||||
match std::fs::read_to_string(file) {
|
||||
Ok(s) => exec(Symbol::get(file.as_os_str()), &s, &args),
|
||||
Err(e) => {
|
||||
eprintln!("Error: {e}");
|
||||
ExitCode::FAILURE
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
use std::{cell::RefCell, io::IsTerminal, process::ExitCode, rc::Rc};
|
||||
|
||||
use clap::ColorChoice;
|
||||
use rustyline::{error::ReadlineError, history::{FileHistory, History}, ColorMode, Config, Editor};
|
||||
use talc_lang::{compiler::compile_repl, symbol::Symbol, value::{function::disasm_recursive, Value}, Vm};
|
||||
use rustyline::{
|
||||
error::ReadlineError,
|
||||
history::{FileHistory, History},
|
||||
ColorMode, Config, Editor,
|
||||
};
|
||||
use talc_lang::{
|
||||
compiler::compile_repl,
|
||||
parser,
|
||||
symbol::Symbol,
|
||||
value::{function::disasm_recursive, Value},
|
||||
Vm,
|
||||
};
|
||||
|
||||
use crate::{helper::TalcHelper, Args};
|
||||
|
||||
|
@ -30,7 +40,6 @@ impl ReplColors {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
fn get_colmode(args: &Args) -> ColorMode {
|
||||
match args.color {
|
||||
ColorChoice::Auto => ColorMode::Enabled,
|
||||
|
@ -45,7 +54,8 @@ pub fn init_rustyline(args: &Args) -> Result<Editor<TalcHelper, FileHistory>, Ex
|
|||
.color_mode(get_colmode(args))
|
||||
.check_cursor_position(true)
|
||||
.completion_type(rustyline::CompletionType::List)
|
||||
.max_history_size(4096).unwrap()
|
||||
.max_history_size(4096)
|
||||
.unwrap()
|
||||
.build();
|
||||
let mut hist = FileHistory::default();
|
||||
if let Some(f) = &args.histfile {
|
||||
|
@ -60,12 +70,12 @@ pub fn init_rustyline(args: &Args) -> Result<Editor<TalcHelper, FileHistory>, Ex
|
|||
Err(ReadlineError::Io(e)) => {
|
||||
eprintln!("Error creating repl: {e}");
|
||||
Err(ExitCode::FAILURE)
|
||||
},
|
||||
}
|
||||
Err(ReadlineError::Errno(e)) => {
|
||||
eprintln!("Error creating repl: {e}");
|
||||
Err(ExitCode::FAILURE)
|
||||
},
|
||||
Err(_) => Err(ExitCode::SUCCESS)
|
||||
}
|
||||
Err(_) => Err(ExitCode::SUCCESS),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,7 +84,6 @@ pub fn repl(args: &Args) -> ExitCode {
|
|||
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);
|
||||
|
@ -118,15 +127,15 @@ pub fn repl(args: &Args) -> ExitCode {
|
|||
Err(e) => {
|
||||
eprintln!("{}Error:{} {e}", c.error, c.reset);
|
||||
continue
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let ex = match parser.parse(&line) {
|
||||
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);
|
||||
|
@ -155,4 +164,3 @@ pub fn repl(args: &Args) -> ExitCode {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,13 +4,11 @@ version = "0.2.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lalrpop-util = { version = "0.20", features = ["lexer", "unicode"] }
|
||||
num-complex = "0.4"
|
||||
num-rational = { version = "0.4", default-features = false, features = [] }
|
||||
num-traits = "0.2"
|
||||
thiserror = "1.0"
|
||||
lazy_static = "1.4"
|
||||
lazy_static = "1.5"
|
||||
unicode-ident = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
lalrpop = { version = "0.20", default_features = false, features = ["lexer", "unicode"]}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
lalrpop::process_root()
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
use crate::{lstring::LStr, symbol::Symbol, value::Value};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum BinaryOp {
|
||||
Add, Sub, Mul, Div, Mod, Pow, IntDiv,
|
||||
Shr, Shl, BitAnd, BitXor, BitOr,
|
||||
Eq, Ne, Gt, Lt, Ge, Le,
|
||||
Concat, Append,
|
||||
Range, RangeIncl,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum UnaryOp {
|
||||
Neg, Not, RangeEndless,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Expr<'s> {
|
||||
Literal(Value),
|
||||
Ident(&'s LStr),
|
||||
|
||||
UnaryOp(UnaryOp, Box<Expr<'s>>),
|
||||
BinaryOp(BinaryOp, Box<Expr<'s>>, Box<Expr<'s>>),
|
||||
|
||||
Assign(Option<BinaryOp>, Box<LValue<'s>>, Box<Expr<'s>>),
|
||||
AssignVar(&'s LStr, Box<Expr<'s>>),
|
||||
AssignGlobal(&'s LStr, Box<Expr<'s>>),
|
||||
|
||||
Index(Box<Expr<'s>>, Box<Expr<'s>>),
|
||||
FnCall(Box<Expr<'s>>, Vec<Expr<'s>>),
|
||||
AssocFnCall(Box<Expr<'s>>, Symbol, Vec<Expr<'s>>),
|
||||
Pipe(Box<Expr<'s>>, Box<Expr<'s>>),
|
||||
|
||||
Block(Vec<Expr<'s>>),
|
||||
List(Vec<Expr<'s>>),
|
||||
Table(Vec<(Expr<'s>, Expr<'s>)>),
|
||||
|
||||
Return(Box<Expr<'s>>),
|
||||
And(Box<Expr<'s>>, Box<Expr<'s>>),
|
||||
Or(Box<Expr<'s>>, Box<Expr<'s>>),
|
||||
If(Box<Expr<'s>>, Box<Expr<'s>>, Option<Box<Expr<'s>>>),
|
||||
While(Box<Expr<'s>>, Box<Expr<'s>>),
|
||||
For(&'s LStr, Box<Expr<'s>>, Box<Expr<'s>>),
|
||||
Lambda(Vec<&'s LStr>, Box<Expr<'s>>),
|
||||
Try(Box<Expr<'s>>, Vec<CatchBlock<'s>>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CatchBlock<'s> {
|
||||
pub name: Option<&'s LStr>,
|
||||
pub types: Option<Vec<Symbol>>,
|
||||
pub body: Expr<'s>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LValue<'s> {
|
||||
Ident(&'s LStr),
|
||||
Index(Box<Expr<'s>>, Box<Expr<'s>>),
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
use crate::{value::Value, ast::{UnaryOp, BinaryOp}, symbol::Symbol};
|
||||
use crate::{
|
||||
parser::ast::{BinaryOp, UnaryOp},
|
||||
symbol::Symbol,
|
||||
value::Value,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Arg24([u8; 3]);
|
||||
|
@ -13,14 +17,20 @@ impl Arg24 {
|
|||
|
||||
#[inline]
|
||||
pub fn from_i32(n: i32) -> Self {
|
||||
assert!((-0x80_0000..=0x7f_ffff).contains(&n), "value out of range for argument");
|
||||
assert!(
|
||||
(-0x80_0000..=0x7f_ffff).contains(&n),
|
||||
"value out of range for argument"
|
||||
);
|
||||
// can't panic: size of slice guaranteed
|
||||
Self(n.to_le_bytes()[0..3].try_into().unwrap())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_i64(n: i64) -> Self {
|
||||
assert!((-0x80_0000..=0x7f_ffff).contains(&n), "value out of range for argument");
|
||||
assert!(
|
||||
(-0x80_0000..=0x7f_ffff).contains(&n),
|
||||
"value out of range for argument"
|
||||
);
|
||||
// can't panic: size of slice guaranteed
|
||||
Self(n.to_le_bytes()[0..3].try_into().unwrap())
|
||||
}
|
||||
|
@ -64,7 +74,9 @@ impl From<Arg24> for i32 {
|
|||
fn from(v: Arg24) -> Self {
|
||||
let mut n = u32::from(v);
|
||||
// sign-extend
|
||||
if n & 0x00_800000 != 0 { n |= 0xff_000000; }
|
||||
if n & 0x00_800000 != 0 {
|
||||
n |= 0xff_000000;
|
||||
}
|
||||
n as i32
|
||||
}
|
||||
}
|
||||
|
@ -88,38 +100,55 @@ impl TryFrom<Arg24> for Symbol {
|
|||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub enum Instruction {
|
||||
#[default]
|
||||
Nop,
|
||||
Nop, // do nothing
|
||||
|
||||
LoadLocal(Arg24),
|
||||
StoreLocal(Arg24),
|
||||
NewLocal,
|
||||
DropLocal(Arg24),
|
||||
LoadLocal(Arg24), // push nth local onto stack
|
||||
StoreLocal(Arg24), // pop stack into nth local
|
||||
NewLocal, // pop stack into a new local
|
||||
DropLocal(Arg24), // remove last n locals
|
||||
|
||||
LoadGlobal(Arg24), StoreGlobal(Arg24),
|
||||
LoadGlobal(Arg24), // load global by id
|
||||
StoreGlobal(Arg24), // store global by id
|
||||
|
||||
Const(Arg24),
|
||||
Int(Arg24),
|
||||
Symbol(Arg24),
|
||||
Bool(bool),
|
||||
Nil,
|
||||
CloseOver(Arg24), // load nth local and convert to cell, write back a copy
|
||||
Closure(Arg24), // load constant function and fill state from stack
|
||||
LoadUpvalue(Arg24), // load
|
||||
StoreUpvalue(Arg24), // store a cell from closure state to new local
|
||||
ContinueUpvalue(Arg24), //
|
||||
LoadClosedLocal(Arg24), // load through cell in nth local
|
||||
StoreClosedLocal(Arg24), // store through cell in nth local
|
||||
|
||||
Dup, DupTwo, Drop(Arg24), Swap,
|
||||
Const(Arg24), // push nth constant
|
||||
Int(Arg24), // push integer
|
||||
Symbol(Arg24), // push symbol
|
||||
Bool(bool), // push boolean
|
||||
Nil, // push nil
|
||||
|
||||
Dup,
|
||||
DupTwo,
|
||||
Drop(Arg24),
|
||||
Swap,
|
||||
|
||||
UnaryOp(UnaryOp),
|
||||
BinaryOp(BinaryOp),
|
||||
|
||||
NewList(u8), GrowList(u8),
|
||||
NewTable(u8), GrowTable(u8),
|
||||
NewList(u8),
|
||||
GrowList(u8),
|
||||
NewTable(u8),
|
||||
GrowTable(u8),
|
||||
|
||||
Index, StoreIndex,
|
||||
Index,
|
||||
StoreIndex,
|
||||
|
||||
Jump(Arg24),
|
||||
JumpTrue(Arg24),
|
||||
JumpFalse(Arg24),
|
||||
|
||||
IterBegin, IterTest(Arg24),
|
||||
IterBegin,
|
||||
IterTest(Arg24),
|
||||
|
||||
BeginTry(Arg24), EndTry,
|
||||
BeginTry(Arg24),
|
||||
EndTry,
|
||||
|
||||
Call(u8),
|
||||
Return,
|
||||
|
@ -133,14 +162,30 @@ impl std::fmt::Display for Instruction {
|
|||
Self::StoreLocal(a) => write!(f, "store {}", usize::from(a)),
|
||||
Self::NewLocal => write!(f, "newlocal"),
|
||||
Self::DropLocal(n) => write!(f, "discardlocal {}", usize::from(n)),
|
||||
Self::LoadGlobal(s) => write!(f, "loadglobal {}",
|
||||
Symbol::try_from(s).expect("symbol does not exist").name()),
|
||||
Self::StoreGlobal(s) => write!(f, "storeglobal {}",
|
||||
Symbol::try_from(s).expect("symbol does not exist").name()),
|
||||
Self::LoadGlobal(s) => write!(
|
||||
f,
|
||||
"loadglobal {}",
|
||||
Symbol::try_from(s).expect("symbol does not exist").name()
|
||||
),
|
||||
Self::StoreGlobal(s) => write!(
|
||||
f,
|
||||
"storeglobal {}",
|
||||
Symbol::try_from(s).expect("symbol does not exist").name()
|
||||
),
|
||||
Self::CloseOver(n) => write!(f, "closeover {}", usize::from(n)),
|
||||
Self::Closure(c) => write!(f, "closure {}", usize::from(c)),
|
||||
Self::LoadUpvalue(n) => write!(f, "load_upval {}", usize::from(n)),
|
||||
Self::StoreUpvalue(n) => write!(f, "store_upval {}", usize::from(n)),
|
||||
Self::ContinueUpvalue(n) => write!(f, "cont_upval {}", usize::from(n)),
|
||||
Self::LoadClosedLocal(n) => write!(f, "load_closed {}", usize::from(n)),
|
||||
Self::StoreClosedLocal(n) => write!(f, "store_closed {}", usize::from(n)),
|
||||
Self::Const(c) => write!(f, "const {}", usize::from(c)),
|
||||
Self::Int(i) => write!(f, "int {}", i64::from(i)),
|
||||
Self::Symbol(s) => write!(f, "symbol {}",
|
||||
Symbol::try_from(s).expect("symbol does not exist").name()),
|
||||
Self::Symbol(s) => write!(
|
||||
f,
|
||||
"symbol {}",
|
||||
Symbol::try_from(s).expect("symbol does not exist").name()
|
||||
),
|
||||
Self::Bool(b) => write!(f, "bool {b}"),
|
||||
Self::Nil => write!(f, "nil"),
|
||||
Self::Dup => write!(f, "dup"),
|
||||
|
@ -193,19 +238,28 @@ impl Chunk {
|
|||
}
|
||||
|
||||
pub fn add_const(&mut self, v: Value) -> usize {
|
||||
assert!(self.consts.len() < 0xff_ffff, "too many constants in a chunk");
|
||||
assert!(
|
||||
self.consts.len() < 0xff_ffff,
|
||||
"too many constants in a chunk"
|
||||
);
|
||||
self.consts.push(v);
|
||||
self.consts.len() - 1
|
||||
}
|
||||
|
||||
pub fn add_instr(&mut self, i: Instruction) -> usize {
|
||||
assert!(self.instrs.len() < 0xff_ffff, "too many instructions in a chunk");
|
||||
assert!(
|
||||
self.instrs.len() < 0xff_ffff,
|
||||
"too many instructions in a chunk"
|
||||
);
|
||||
self.instrs.push(i);
|
||||
self.instrs.len() - 1
|
||||
}
|
||||
|
||||
pub fn begin_try_table(&mut self, local_count: usize) -> (usize, TryTable) {
|
||||
assert!(self.try_tables.len() < 0xff_ffff, "too many catch tables in a chunk");
|
||||
assert!(
|
||||
self.try_tables.len() < 0xff_ffff,
|
||||
"too many catch tables in a chunk"
|
||||
);
|
||||
let table = TryTable {
|
||||
catches: Vec::new(),
|
||||
local_count,
|
||||
|
@ -218,5 +272,3 @@ impl Chunk {
|
|||
self.try_tables[idx] = table;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,22 +1,36 @@
|
|||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::ast::{BinaryOp, Expr, LValue, CatchBlock};
|
||||
use crate::chunk::{Instruction as I, Chunk, Arg24, Catch};
|
||||
use crate::lstr;
|
||||
use crate::lstring::LStr;
|
||||
use crate::symbol::Symbol;
|
||||
use crate::chunk::{Arg24, Catch, Chunk, Instruction as I};
|
||||
use crate::parser::ast::{BinaryOp, CatchBlock, Expr, ExprKind, LValue, LValueKind};
|
||||
use crate::symbol::{Symbol, SYM_SELF};
|
||||
use crate::value::function::{FuncAttrs, Function};
|
||||
use crate::value::Value;
|
||||
|
||||
enum ResolveOutcome {
|
||||
Var(VarKind),
|
||||
InParent,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum VarKind {
|
||||
Local(usize),
|
||||
Closed(usize),
|
||||
Global,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Local {
|
||||
name: Rc<LStr>,
|
||||
scope: usize,
|
||||
pub struct Var {
|
||||
name: Symbol,
|
||||
kind: VarKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum CompilerMode {
|
||||
Function, Repl, Module,
|
||||
Function,
|
||||
Repl,
|
||||
Module,
|
||||
}
|
||||
|
||||
struct Compiler<'a> {
|
||||
|
@ -24,18 +38,19 @@ struct Compiler<'a> {
|
|||
parent: Option<&'a Compiler<'a>>,
|
||||
chunk: Chunk,
|
||||
attrs: FuncAttrs,
|
||||
scope: usize,
|
||||
locals: Vec<Local>,
|
||||
globals: Vec<Local>,
|
||||
scope: HashMap<Symbol, Var>,
|
||||
shadowed: Vec<(Symbol, Option<Var>)>,
|
||||
closes: BTreeMap<Symbol, usize>,
|
||||
local_count: usize,
|
||||
}
|
||||
|
||||
pub fn compile(expr: &Expr) -> Function {
|
||||
let mut comp = Compiler::new_module(None);
|
||||
pub fn compile(expr: &Expr, name: Option<Symbol>) -> Function {
|
||||
let mut comp = Compiler::new_module(name, None);
|
||||
comp.expr(expr);
|
||||
comp.finish()
|
||||
}
|
||||
|
||||
pub fn compile_repl(expr: &Expr, globals: &[Local]) -> (Function, Vec<Local>) {
|
||||
pub fn compile_repl(expr: &Expr, globals: &[Symbol]) -> (Function, Vec<Symbol>) {
|
||||
let mut comp = Compiler::new_repl(globals);
|
||||
comp.expr(expr);
|
||||
comp.finish_repl()
|
||||
|
@ -43,52 +58,66 @@ pub fn compile_repl(expr: &Expr, globals: &[Local]) -> (Function, Vec<Local>) {
|
|||
|
||||
impl<'a> Default for Compiler<'a> {
|
||||
fn default() -> Self {
|
||||
let locals = vec![Local {
|
||||
name: lstr!("self").into(),
|
||||
scope: 0,
|
||||
}];
|
||||
let mut scope = HashMap::new();
|
||||
scope.insert(
|
||||
*SYM_SELF,
|
||||
Var {
|
||||
name: *SYM_SELF,
|
||||
kind: VarKind::Local(0),
|
||||
},
|
||||
);
|
||||
Self {
|
||||
mode: CompilerMode::Function,
|
||||
parent: None,
|
||||
chunk: Chunk::new(),
|
||||
attrs: FuncAttrs { arity: 0, variadic: false },
|
||||
scope: 0,
|
||||
locals,
|
||||
globals: Vec::new(),
|
||||
attrs: FuncAttrs::default(),
|
||||
scope,
|
||||
shadowed: Vec::new(),
|
||||
local_count: 1,
|
||||
closes: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Compiler<'a> {
|
||||
fn new_repl(globals: &[Local]) -> Self {
|
||||
Self {
|
||||
fn new_repl(globals: &[Symbol]) -> Self {
|
||||
let mut new = Self {
|
||||
mode: CompilerMode::Repl,
|
||||
globals: globals.to_vec(),
|
||||
attrs: FuncAttrs {
|
||||
arity: 0,
|
||||
name: Some(Symbol::get("<repl>")),
|
||||
},
|
||||
..Self::default()
|
||||
};
|
||||
|
||||
for g in globals {
|
||||
new.declare_global(*g);
|
||||
}
|
||||
new
|
||||
}
|
||||
|
||||
fn new_module(parent: Option<&'a Self>) -> Self {
|
||||
fn new_module(name: Option<Symbol>, parent: Option<&'a Self>) -> Self {
|
||||
Self {
|
||||
mode: CompilerMode::Module,
|
||||
attrs: FuncAttrs { arity: 0, name },
|
||||
parent,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn new_function(&'a self, args: &[&LStr]) -> Self {
|
||||
fn new_function(&'a self, name: Option<Symbol>, args: &[Symbol]) -> Self {
|
||||
let mut new = Self {
|
||||
mode: CompilerMode::Function,
|
||||
attrs: FuncAttrs {
|
||||
arity: args.len(),
|
||||
name,
|
||||
},
|
||||
parent: Some(self),
|
||||
..Self::default()
|
||||
};
|
||||
new.attrs.arity = args.len();
|
||||
|
||||
for arg in args {
|
||||
new.locals.push(Local {
|
||||
name: (*arg).into(),
|
||||
scope: 0,
|
||||
});
|
||||
new.declare_local(*arg);
|
||||
}
|
||||
|
||||
new
|
||||
|
@ -96,14 +125,27 @@ impl<'a> Compiler<'a> {
|
|||
|
||||
pub fn finish(mut self) -> Function {
|
||||
self.emit(I::Return);
|
||||
Function { chunk: Rc::new(self.chunk), attrs: self.attrs }
|
||||
Function::new(Rc::new(self.chunk), self.attrs, self.closes.len())
|
||||
}
|
||||
|
||||
pub fn finish_repl(mut self) -> (Function, Vec<Local>) {
|
||||
pub fn finish_inner(mut self) -> (Function, BTreeMap<Symbol, usize>) {
|
||||
self.emit(I::Return);
|
||||
// TODO closure
|
||||
(
|
||||
Function::new(Rc::new(self.chunk), self.attrs, self.closes.len()),
|
||||
self.closes,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn finish_repl(mut self) -> (Function, Vec<Symbol>) {
|
||||
self.emit(I::Return);
|
||||
(
|
||||
Function { chunk: Rc::new(self.chunk), attrs: self.attrs },
|
||||
self.globals
|
||||
// TODO closure
|
||||
Function::new(Rc::new(self.chunk), self.attrs, self.closes.len()),
|
||||
self.scope
|
||||
.into_iter()
|
||||
.filter_map(|(_, v)| (v.kind == VarKind::Global).then_some(v.name))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -126,36 +168,44 @@ impl<'a> Compiler<'a> {
|
|||
// dup followed by store: remove the dup
|
||||
if instrs.len() >= 2
|
||||
&& matches!(instrs.get(instrs.len() - 2), Some(I::Dup))
|
||||
&& matches!(instrs.last(), Some(
|
||||
I::NewLocal | I::StoreLocal(_) | I::StoreGlobal(_)
|
||||
))
|
||||
{
|
||||
&& matches!(
|
||||
instrs.last(),
|
||||
Some(
|
||||
I::NewLocal
|
||||
| I::StoreLocal(_) | I::StoreGlobal(_)
|
||||
| I::StoreClosedLocal(_)
|
||||
| I::StoreUpvalue(_)
|
||||
)
|
||||
) {
|
||||
// can't panic: checked that instrs.len() >= 2
|
||||
let i = self.chunk.instrs.pop().unwrap();
|
||||
self.chunk.instrs.pop().unwrap();
|
||||
self.chunk.instrs.push(i);
|
||||
n -= 1;
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
// final side-effectless instruction
|
||||
let poppable = matches!(
|
||||
instrs.last(),
|
||||
Some(
|
||||
I::Dup | I::Const(_) | I::Int(_)
|
||||
| I::Nil | I::Bool(_) | I::Symbol(_)
|
||||
| I::LoadLocal(_)
|
||||
I::Dup
|
||||
| I::Const(_) | I::Int(_)
|
||||
| I::Nil | I::Bool(_)
|
||||
| I::Symbol(_) | I::LoadLocal(_)
|
||||
| I::LoadClosedLocal(_)
|
||||
| I::LoadUpvalue(_)
|
||||
)
|
||||
);
|
||||
if poppable {
|
||||
// can't panic: checked that instrs.last() was Some
|
||||
instrs.pop().unwrap();
|
||||
n -= 1;
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
|
||||
// no more optimizations possible
|
||||
break;
|
||||
break
|
||||
}
|
||||
if n > 0 {
|
||||
self.emit(I::Drop(Arg24::from_usize(n)));
|
||||
|
@ -170,164 +220,181 @@ impl<'a> Compiler<'a> {
|
|||
self.chunk.instrs[n] = new;
|
||||
}
|
||||
|
||||
fn begin_scope(&mut self) {
|
||||
self.scope += 1;
|
||||
#[must_use]
|
||||
fn begin_scope(&mut self) -> usize {
|
||||
self.shadowed.len()
|
||||
}
|
||||
|
||||
fn end_scope(&mut self) {
|
||||
self.scope -= 1;
|
||||
fn end_scope(&mut self, scope: usize) {
|
||||
let mut locals = 0;
|
||||
while self.shadowed.len() > scope {
|
||||
let (name, var) = self.shadowed.pop().expect("scope bad");
|
||||
|
||||
// no need to clean up at bottom scope
|
||||
if self.scope == 0 { return; }
|
||||
|
||||
for i in (0..self.globals.len()).rev() {
|
||||
if self.globals[i].scope <= self.scope {
|
||||
break;
|
||||
if let Some(var) = var {
|
||||
if var.kind != VarKind::Global {
|
||||
locals += 1;
|
||||
}
|
||||
self.globals.pop();
|
||||
}
|
||||
|
||||
let mut count = 0;
|
||||
for i in (0..self.locals.len()).rev() {
|
||||
if self.locals[i].scope <= self.scope {
|
||||
break;
|
||||
}
|
||||
self.locals.pop();
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if count > 0 && self.scope > 0 {
|
||||
self.emit(I::DropLocal(Arg24::from_usize(count)));
|
||||
self.scope.insert(name, var);
|
||||
} else {
|
||||
self.scope.remove(&name);
|
||||
}
|
||||
}
|
||||
|
||||
if locals > 0 {
|
||||
self.emit(I::DropLocal(Arg24::from_usize(locals)));
|
||||
self.local_count -= locals;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// variables
|
||||
//
|
||||
|
||||
fn resolve_local(&mut self, name: &LStr) -> Option<usize> {
|
||||
self.locals.iter().rev()
|
||||
.position(|v| v.name.as_ref() == name)
|
||||
.map(|x| self.locals.len() - x - 1)
|
||||
fn resolve_name(&self, name: Symbol) -> ResolveOutcome {
|
||||
if let Some(v) = self.scope.get(&name) {
|
||||
return ResolveOutcome::Var(v.kind)
|
||||
}
|
||||
let Some(parent) = self.parent else {
|
||||
return ResolveOutcome::None
|
||||
};
|
||||
if let ResolveOutcome::None = parent.resolve_name(name) {
|
||||
return ResolveOutcome::None
|
||||
}
|
||||
ResolveOutcome::InParent
|
||||
}
|
||||
|
||||
fn resolve_global(&mut self, name: &LStr) -> Option<usize> {
|
||||
self.globals.iter().rev()
|
||||
.position(|v| v.name.as_ref() == name)
|
||||
.map(|x| self.globals.len() - x - 1)
|
||||
}
|
||||
|
||||
|
||||
fn load_var(&mut self, name: &LStr) {
|
||||
match (self.resolve_local(name), self.resolve_global(name)) {
|
||||
(Some(n), None) => {
|
||||
fn load_var(&mut self, name: Symbol) {
|
||||
match self.resolve_name(name) {
|
||||
ResolveOutcome::Var(VarKind::Local(n)) => {
|
||||
self.emit(I::LoadLocal(Arg24::from_usize(n)));
|
||||
},
|
||||
(Some(n), Some(m)) if n >= m => {
|
||||
self.emit(I::LoadLocal(Arg24::from_usize(n)));
|
||||
},
|
||||
_ => {
|
||||
let sym = Symbol::get(name);
|
||||
self.emit(I::LoadGlobal(Arg24::from_symbol(sym)));
|
||||
}
|
||||
ResolveOutcome::Var(VarKind::Closed(n)) => {
|
||||
self.emit(I::LoadClosedLocal(Arg24::from_usize(n)));
|
||||
}
|
||||
ResolveOutcome::InParent => {
|
||||
let n = match self.closes.get(&name) {
|
||||
Some(n) => *n,
|
||||
None => {
|
||||
let n = self.closes.len();
|
||||
self.closes.insert(name, n);
|
||||
n
|
||||
}
|
||||
};
|
||||
self.emit(I::LoadUpvalue(Arg24::from_usize(n)));
|
||||
}
|
||||
ResolveOutcome::None | ResolveOutcome::Var(VarKind::Global) => {
|
||||
self.emit(I::LoadGlobal(Arg24::from_symbol(name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn declare_local(&mut self, name: &LStr) -> usize {
|
||||
if let Some(i) = self.resolve_local(name) {
|
||||
if self.locals[i].scope == self.scope {
|
||||
self.emit(I::StoreLocal(Arg24::from_usize(i)));
|
||||
return i;
|
||||
}
|
||||
fn declare_local(&mut self, name: Symbol) -> usize {
|
||||
let local = Var {
|
||||
name,
|
||||
kind: VarKind::Local(self.local_count),
|
||||
};
|
||||
self.local_count += 1;
|
||||
let shadowed = self.scope.insert(name, local);
|
||||
self.shadowed.push((name, shadowed));
|
||||
self.local_count - 1
|
||||
}
|
||||
|
||||
self.locals.push(Local {
|
||||
name: name.into(),
|
||||
scope: self.scope,
|
||||
});
|
||||
|
||||
let i = self.locals.len() - 1;
|
||||
fn assign_local(&mut self, name: Symbol) -> usize {
|
||||
let n = self.declare_local(name);
|
||||
self.emit(I::NewLocal);
|
||||
i
|
||||
n
|
||||
}
|
||||
|
||||
fn store_local(&mut self, i: usize) {
|
||||
self.emit(I::StoreLocal(Arg24::from_usize(i)));
|
||||
fn assign_global(&mut self, name: Symbol) {
|
||||
self.declare_global(name);
|
||||
self.store_var(name);
|
||||
}
|
||||
|
||||
fn store_global(&mut self, name: &LStr) {
|
||||
let sym = Symbol::get(name);
|
||||
self.emit(I::StoreGlobal(Arg24::from_symbol(sym)));
|
||||
if let Some(i) = self.resolve_global(name) {
|
||||
if self.globals[i].scope == self.scope {
|
||||
return
|
||||
}
|
||||
}
|
||||
self.globals.push(Local {
|
||||
name: name.into(),
|
||||
scope: self.scope,
|
||||
});
|
||||
fn declare_global(&mut self, name: Symbol) {
|
||||
let global = Var {
|
||||
name,
|
||||
kind: VarKind::Global,
|
||||
};
|
||||
let shadowed = self.scope.insert(name, global);
|
||||
self.shadowed.push((name, shadowed));
|
||||
}
|
||||
|
||||
fn store_default(&mut self, name: &LStr) {
|
||||
match (self.resolve_local(name), self.resolve_global(name)) {
|
||||
(Some(n), None) => self.store_local(n),
|
||||
(Some(n), Some(m)) if n >= m => self.store_local(n),
|
||||
(_, Some(_)) => self.store_global(name),
|
||||
(None, None) => {
|
||||
if self.mode == CompilerMode::Repl && self.scope == 1 {
|
||||
self.store_global(name);
|
||||
} else {
|
||||
self.declare_local(name);
|
||||
fn store_var(&mut self, name: Symbol) {
|
||||
match self.resolve_name(name) {
|
||||
ResolveOutcome::Var(VarKind::Local(n)) => {
|
||||
self.emit(I::StoreLocal(Arg24::from_usize(n)));
|
||||
}
|
||||
ResolveOutcome::Var(VarKind::Closed(n)) => {
|
||||
self.emit(I::StoreClosedLocal(Arg24::from_usize(n)));
|
||||
}
|
||||
ResolveOutcome::InParent => {
|
||||
let n = match self.closes.get(&name) {
|
||||
Some(n) => *n,
|
||||
None => {
|
||||
let n = self.closes.len();
|
||||
self.closes.insert(name, n);
|
||||
n
|
||||
}
|
||||
};
|
||||
self.emit(I::StoreUpvalue(Arg24::from_usize(n)));
|
||||
}
|
||||
ResolveOutcome::Var(VarKind::Global) => {
|
||||
self.emit(I::StoreGlobal(Arg24::from_symbol(name)));
|
||||
}
|
||||
ResolveOutcome::None if self.mode == CompilerMode::Repl => {
|
||||
self.emit(I::StoreGlobal(Arg24::from_symbol(name)));
|
||||
}
|
||||
ResolveOutcome::None => {
|
||||
self.assign_local(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Expressions
|
||||
//
|
||||
|
||||
fn expr(&mut self, e: &Expr) {
|
||||
match e {
|
||||
Expr::Block(xs) if xs.is_empty() => { self.emit(I::Nil); },
|
||||
Expr::Block(xs) => {
|
||||
self.begin_scope();
|
||||
for x in &xs[0..xs.len()-1] {
|
||||
let Expr { kind, .. } = e;
|
||||
match kind {
|
||||
ExprKind::Block(xs) if xs.is_empty() => {
|
||||
self.emit(I::Nil);
|
||||
}
|
||||
ExprKind::Block(xs) => {
|
||||
let scope = self.begin_scope();
|
||||
for x in &xs[0..xs.len() - 1] {
|
||||
self.expr(x);
|
||||
self.emit_discard(1);
|
||||
}
|
||||
self.expr(&xs[xs.len()-1]);
|
||||
self.end_scope();
|
||||
},
|
||||
Expr::Literal(v) => self.expr_literal(v),
|
||||
Expr::Ident(ident) => self.load_var(ident),
|
||||
Expr::UnaryOp(o, a) => {
|
||||
self.expr(&xs[xs.len() - 1]);
|
||||
self.end_scope(scope);
|
||||
}
|
||||
ExprKind::Literal(v) => self.expr_literal(v),
|
||||
ExprKind::Ident(ident) => self.load_var(*ident),
|
||||
ExprKind::UnaryOp(o, a) => {
|
||||
self.expr(a);
|
||||
self.emit(I::UnaryOp(*o));
|
||||
},
|
||||
Expr::BinaryOp(o, a, b) => {
|
||||
}
|
||||
ExprKind::BinaryOp(o, a, b) => {
|
||||
self.expr(a);
|
||||
self.expr(b);
|
||||
self.emit(I::BinaryOp(*o));
|
||||
},
|
||||
Expr::Assign(o, lv, a) => self.expr_assign(*o, lv, a),
|
||||
Expr::AssignVar(name, a) => {
|
||||
}
|
||||
ExprKind::Assign(o, lv, a) => self.expr_assign(*o, lv, a),
|
||||
ExprKind::AssignVar(name, a) => {
|
||||
self.expr(a);
|
||||
self.emit(I::Dup);
|
||||
self.declare_local(name);
|
||||
},
|
||||
Expr::AssignGlobal(name, a) => {
|
||||
self.assign_local(*name);
|
||||
}
|
||||
ExprKind::AssignGlobal(name, a) => {
|
||||
self.expr(a);
|
||||
self.emit(I::Dup);
|
||||
self.store_global(name);
|
||||
},
|
||||
Expr::List(xs) if xs.is_empty() => {
|
||||
self.assign_global(*name);
|
||||
}
|
||||
ExprKind::List(xs) if xs.is_empty() => {
|
||||
self.emit(I::NewList(0));
|
||||
},
|
||||
Expr::List(xs) => {
|
||||
}
|
||||
ExprKind::List(xs) => {
|
||||
let mut first = true;
|
||||
for chunk in xs.chunks(16) {
|
||||
for e in chunk {
|
||||
|
@ -340,11 +407,11 @@ impl<'a> Compiler<'a> {
|
|||
self.emit(I::GrowList(chunk.len() as u8));
|
||||
}
|
||||
}
|
||||
},
|
||||
Expr::Table(xs) if xs.is_empty() => {
|
||||
}
|
||||
ExprKind::Table(xs) if xs.is_empty() => {
|
||||
self.emit(I::NewTable(0));
|
||||
},
|
||||
Expr::Table(xs) => {
|
||||
}
|
||||
ExprKind::Table(xs) => {
|
||||
let mut first = true;
|
||||
for chunk in xs.chunks(8) {
|
||||
for (k, v) in chunk {
|
||||
|
@ -358,20 +425,20 @@ impl<'a> Compiler<'a> {
|
|||
self.emit(I::GrowTable(chunk.len() as u8));
|
||||
}
|
||||
}
|
||||
},
|
||||
Expr::Index(ct, idx) => {
|
||||
}
|
||||
ExprKind::Index(ct, idx) => {
|
||||
self.expr(ct);
|
||||
self.expr(idx);
|
||||
self.emit(I::Index);
|
||||
},
|
||||
Expr::FnCall(f, args) => {
|
||||
}
|
||||
ExprKind::FnCall(f, args) => {
|
||||
self.expr(f);
|
||||
for a in args {
|
||||
self.expr(a);
|
||||
}
|
||||
self.emit(I::Call(args.len() as u8));
|
||||
},
|
||||
Expr::AssocFnCall(o, f, args) => {
|
||||
}
|
||||
ExprKind::AssocFnCall(o, f, args) => {
|
||||
self.expr(o);
|
||||
self.emit(I::Dup);
|
||||
self.emit(I::Symbol(Arg24::from_symbol(*f)));
|
||||
|
@ -381,35 +448,36 @@ impl<'a> Compiler<'a> {
|
|||
self.expr(a);
|
||||
}
|
||||
self.emit(I::Call((args.len() + 1) as u8));
|
||||
},
|
||||
Expr::Return(e) => {
|
||||
}
|
||||
ExprKind::Return(e) => {
|
||||
self.expr(e);
|
||||
self.emit(I::Return);
|
||||
},
|
||||
Expr::Pipe(a, f) => {
|
||||
}
|
||||
ExprKind::Pipe(a, f) => {
|
||||
self.expr(a);
|
||||
self.expr(f);
|
||||
self.emit(I::Swap);
|
||||
self.emit(I::Call(1));
|
||||
},
|
||||
Expr::Lambda(args, body) => self.expr_lambda(args, body),
|
||||
Expr::And(a, b) => {
|
||||
}
|
||||
ExprKind::Lambda(args, body) => self.expr_fndef(None, args, body),
|
||||
ExprKind::FnDef(name, args, body) => self.expr_fndef(*name, args, body),
|
||||
ExprKind::And(a, b) => {
|
||||
self.expr(a);
|
||||
self.emit(I::Dup);
|
||||
let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0)));
|
||||
self.emit_discard(1);
|
||||
self.expr(b);
|
||||
self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip())));
|
||||
},
|
||||
Expr::Or(a, b) => {
|
||||
}
|
||||
ExprKind::Or(a, b) => {
|
||||
self.expr(a);
|
||||
self.emit(I::Dup);
|
||||
let j1 = self.emit(I::JumpTrue(Arg24::from_usize(0)));
|
||||
self.emit_discard(1);
|
||||
self.expr(b);
|
||||
self.update_instr(j1, I::JumpTrue(Arg24::from_usize(self.ip())));
|
||||
},
|
||||
Expr::If(cond, b1, b2) => {
|
||||
}
|
||||
ExprKind::If(cond, b1, b2) => {
|
||||
self.expr(cond);
|
||||
let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0)));
|
||||
self.expr(b1);
|
||||
|
@ -420,11 +488,9 @@ impl<'a> Compiler<'a> {
|
|||
} else {
|
||||
self.emit(I::Nil);
|
||||
}
|
||||
self.update_instr(j2,
|
||||
I::Jump(Arg24::from_usize(self.ip()))
|
||||
);
|
||||
},
|
||||
Expr::While(cond, body) => {
|
||||
self.update_instr(j2, I::Jump(Arg24::from_usize(self.ip())));
|
||||
}
|
||||
ExprKind::While(cond, body) => {
|
||||
let start = self.ip();
|
||||
self.expr(cond);
|
||||
|
||||
|
@ -435,14 +501,14 @@ impl<'a> Compiler<'a> {
|
|||
self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip())));
|
||||
|
||||
self.emit(I::Nil);
|
||||
},
|
||||
Expr::For(name, iter, body) => self.expr_for(name, iter, body),
|
||||
Expr::Try(body, catches) => self.expr_try(body, catches),
|
||||
}
|
||||
ExprKind::For(name, iter, body) => self.expr_for(*name, iter, body),
|
||||
ExprKind::Try(body, catches) => self.expr_try(body, catches),
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_try(&mut self, body: &Expr, catch_blocks: &[CatchBlock]) {
|
||||
let (idx, mut table) = self.chunk.begin_try_table(self.locals.len());
|
||||
let (idx, mut table) = self.chunk.begin_try_table(self.local_count);
|
||||
|
||||
self.emit(I::BeginTry(Arg24::from_usize(idx)));
|
||||
self.expr(body);
|
||||
|
@ -456,16 +522,16 @@ impl<'a> Compiler<'a> {
|
|||
types: catch_block.types.clone(),
|
||||
});
|
||||
|
||||
self.begin_scope();
|
||||
let scope = self.begin_scope();
|
||||
|
||||
if let Some(name) = catch_block.name {
|
||||
self.declare_local(name);
|
||||
self.assign_local(name);
|
||||
} else {
|
||||
self.emit_discard(1);
|
||||
}
|
||||
|
||||
self.expr(&catch_block.body);
|
||||
self.end_scope();
|
||||
self.end_scope(scope);
|
||||
|
||||
let end_addr = self.emit(I::Jump(Arg24::from_usize(0)));
|
||||
catch_end_addrs.push(end_addr);
|
||||
|
@ -480,15 +546,15 @@ impl<'a> Compiler<'a> {
|
|||
self.chunk.finish_catch_table(idx, table);
|
||||
}
|
||||
|
||||
fn expr_for(&mut self, name: &LStr, iter: &Expr, body: &Expr) {
|
||||
fn expr_for(&mut self, name: Symbol, iter: &Expr, body: &Expr) {
|
||||
// load iterable and convert to iterator
|
||||
self.expr(iter);
|
||||
self.emit(I::IterBegin);
|
||||
|
||||
// declare loop variable
|
||||
self.begin_scope();
|
||||
let scope = self.begin_scope();
|
||||
self.emit(I::Nil);
|
||||
let local = self.declare_local(name);
|
||||
self.assign_local(name);
|
||||
|
||||
// begin loop
|
||||
let start = self.ip();
|
||||
|
@ -497,7 +563,7 @@ impl<'a> Compiler<'a> {
|
|||
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_var(name);
|
||||
|
||||
// body
|
||||
self.expr(body);
|
||||
|
@ -507,57 +573,96 @@ impl<'a> Compiler<'a> {
|
|||
self.emit(I::Jump(Arg24::from_usize(start)));
|
||||
|
||||
self.update_instr(j1, I::IterTest(Arg24::from_usize(self.ip())));
|
||||
self.end_scope();
|
||||
self.end_scope(scope);
|
||||
self.emit(I::Nil);
|
||||
}
|
||||
|
||||
fn expr_lambda(&mut self, args: &[&LStr], body: &Expr) {
|
||||
let mut inner = self.new_function(args);
|
||||
fn expr_fndef(&mut self, name: Option<Symbol>, args: &[Symbol], body: &Expr) {
|
||||
let mut inner = self.new_function(name, args);
|
||||
inner.parent = Some(self);
|
||||
inner.expr(body);
|
||||
let func = inner.finish();
|
||||
let n = self.add_const(func.into());
|
||||
self.emit(I::Const(Arg24::from_usize(n)));
|
||||
|
||||
let (func, closes) = inner.finish_inner();
|
||||
let func_const = self.add_const(func.into());
|
||||
|
||||
let num_closed = closes.len();
|
||||
|
||||
for (name, _) in closes {
|
||||
match self.resolve_name(name) {
|
||||
ResolveOutcome::Var(VarKind::Local(n)) => {
|
||||
self.emit(I::CloseOver(Arg24::from_usize(n)));
|
||||
self.scope.entry(name).and_modify(|v| {
|
||||
v.kind = VarKind::Closed(n);
|
||||
});
|
||||
}
|
||||
ResolveOutcome::Var(VarKind::Closed(n)) => {
|
||||
self.emit(I::LoadLocal(Arg24::from_usize(n)));
|
||||
}
|
||||
ResolveOutcome::InParent => {
|
||||
let n = self.closes.len();
|
||||
self.closes.insert(name, n);
|
||||
self.emit(I::ContinueUpvalue(Arg24::from_usize(n)));
|
||||
}
|
||||
ResolveOutcome::None | ResolveOutcome::Var(VarKind::Global) => {
|
||||
panic!("upvalue resolved to none or global")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if num_closed == 0 {
|
||||
self.emit(I::Const(Arg24::from_usize(func_const)));
|
||||
} else {
|
||||
self.emit(I::Closure(Arg24::from_usize(func_const)));
|
||||
}
|
||||
|
||||
if let Some(name) = name {
|
||||
self.emit(I::Dup);
|
||||
self.store_var(name);
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_literal(&mut self, val: &Value) {
|
||||
match val {
|
||||
Value::Nil
|
||||
=> { self.emit(I::Nil); },
|
||||
Value::Bool(b)
|
||||
=> { self.emit(I::Bool(*b)); },
|
||||
Value::Int(i) if (-0x80_0000..=0x7f_ffff).contains(i)
|
||||
=> { self.emit(I::Int(Arg24::from_i64(*i))); },
|
||||
Value::Symbol(s)
|
||||
=> { self.emit(I::Symbol(Arg24::from_symbol(*s))); },
|
||||
Value::Nil => {
|
||||
self.emit(I::Nil);
|
||||
}
|
||||
Value::Bool(b) => {
|
||||
self.emit(I::Bool(*b));
|
||||
}
|
||||
Value::Int(i) if (-0x80_0000..=0x7f_ffff).contains(i) => {
|
||||
self.emit(I::Int(Arg24::from_i64(*i)));
|
||||
}
|
||||
Value::Symbol(s) => {
|
||||
self.emit(I::Symbol(Arg24::from_symbol(*s)));
|
||||
}
|
||||
_ => {
|
||||
let n = self.add_const(val.clone());
|
||||
self.emit(I::Const(Arg24::from_usize(n)));
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_assign(&mut self, o: Option<BinaryOp>, lv: &LValue, a: &Expr) {
|
||||
match (lv, o) {
|
||||
(LValue::Ident(i), None) => {
|
||||
match (&lv.kind, o) {
|
||||
(LValueKind::Ident(i), None) => {
|
||||
self.expr(a);
|
||||
self.emit(I::Dup);
|
||||
self.store_default(i);
|
||||
},
|
||||
(LValue::Ident(i), Some(o)) => {
|
||||
self.load_var(i);
|
||||
self.store_var(*i);
|
||||
}
|
||||
(LValueKind::Ident(i), Some(o)) => {
|
||||
self.load_var(*i);
|
||||
self.expr(a);
|
||||
self.emit(I::BinaryOp(o));
|
||||
self.emit(I::Dup);
|
||||
self.store_default(i);
|
||||
},
|
||||
(LValue::Index(ct, i), None) => {
|
||||
self.store_var(*i);
|
||||
}
|
||||
(LValueKind::Index(ct, i), None) => {
|
||||
self.expr(ct);
|
||||
self.expr(i);
|
||||
self.expr(a);
|
||||
self.emit(I::StoreIndex);
|
||||
},
|
||||
(LValue::Index(ct, i), Some(o)) => {
|
||||
}
|
||||
(LValueKind::Index(ct, i), Some(o)) => {
|
||||
self.expr(ct);
|
||||
self.expr(i);
|
||||
self.emit(I::DupTwo);
|
||||
|
@ -565,8 +670,7 @@ impl<'a> Compiler<'a> {
|
|||
self.expr(a);
|
||||
self.emit(I::BinaryOp(o));
|
||||
self.emit(I::StoreIndex);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
use std::{rc::Rc, collections::HashMap, cell::RefCell, fmt::Display};
|
||||
use crate::{lstring::LStr, symbol::{Symbol, SYM_DATA, SYM_MSG, SYM_TYPE}, value::{HashValue, Value}};
|
||||
use crate::{
|
||||
lstring::LStr,
|
||||
symbol::{Symbol, SYM_DATA, SYM_MSG, SYM_TYPE},
|
||||
value::{HashValue, Value},
|
||||
};
|
||||
use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Exception>;
|
||||
|
||||
|
@ -12,19 +16,35 @@ pub struct Exception {
|
|||
|
||||
impl Exception {
|
||||
pub fn new(ty: Symbol) -> Self {
|
||||
Self { ty, msg: None, data: None }
|
||||
Self {
|
||||
ty,
|
||||
msg: None,
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_msg(ty: Symbol, msg: Rc<LStr>) -> Self {
|
||||
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) }
|
||||
Self {
|
||||
ty,
|
||||
msg: None,
|
||||
data: Some(data),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_msg_data(ty: Symbol, msg: Rc<LStr>, data: Value) -> Self {
|
||||
Self { ty, msg: Some(msg), data: Some(data) }
|
||||
Self {
|
||||
ty,
|
||||
msg: Some(msg),
|
||||
data: Some(data),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_table(table: &Rc<RefCell<HashMap<HashValue, Value>>>) -> Option<Self> {
|
||||
|
@ -76,11 +96,11 @@ impl Display for Exception {
|
|||
|
||||
#[macro_export]
|
||||
macro_rules! exception {
|
||||
($exc_ty:expr, $fstr:literal, $($arg:expr),*) => {
|
||||
($exc_ty:expr, $($t:tt)*) => {
|
||||
$crate::exception::Exception::new_with_msg(
|
||||
$exc_ty,
|
||||
$crate::lstring::LString::from(
|
||||
format!($fstr, $($arg),*)
|
||||
format!($($t)*)
|
||||
).into()
|
||||
)
|
||||
};
|
||||
|
@ -102,6 +122,3 @@ macro_rules! throw {
|
|||
}
|
||||
|
||||
pub use throw;
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,50 +1,14 @@
|
|||
#![allow(clippy::mutable_key_type)]
|
||||
#![warn(clippy::semicolon_if_nothing_returned)]
|
||||
#![warn(clippy::allow_attributes)]
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[allow(clippy::extra_unused_lifetimes)]
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
#[allow(clippy::let_unit_value)]
|
||||
#[allow(clippy::just_underscores_and_digits)]
|
||||
#[allow(clippy::pedantic)]
|
||||
mod parser {
|
||||
pub use __intern_token::new_builder as new_builder;
|
||||
include!(concat!(env!("OUT_DIR"),"/parser.rs"));
|
||||
}
|
||||
mod parser_util;
|
||||
mod vm;
|
||||
pub mod symbol;
|
||||
|
||||
pub mod ast;
|
||||
pub mod value;
|
||||
pub mod exception;
|
||||
pub mod chunk;
|
||||
pub mod compiler;
|
||||
pub mod exception;
|
||||
pub mod lstring;
|
||||
pub mod parser;
|
||||
pub mod symbol;
|
||||
pub mod value;
|
||||
|
||||
pub use parser::BlockParser as Parser;
|
||||
mod vm;
|
||||
pub use vm::Vm;
|
||||
|
||||
pub use parser_util::{parse_int, parse_float, parse_str_escapes};
|
||||
|
||||
type LexResult<'input> = Result<
|
||||
(usize, Token<'input>, usize),
|
||||
ParseError<usize, Token<'input>, parser_util::ParseError>
|
||||
>;
|
||||
|
||||
use lalrpop_util::{ParseError, lexer::{Token, MatcherBuilder}};
|
||||
|
||||
pub struct Lexer {
|
||||
builder: MatcherBuilder,
|
||||
}
|
||||
|
||||
impl Default for Lexer {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
|
||||
impl Lexer {
|
||||
pub fn new() -> Self {
|
||||
Self { builder: crate::parser::new_builder() }
|
||||
}
|
||||
pub fn lex<'s>(&'s self, input: &'s str) -> impl Iterator<Item=LexResult<'s>> {
|
||||
self.builder.matcher::<parser_util::ParseError>(input)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,15 @@
|
|||
use std::{borrow::{Borrow, BorrowMut, Cow}, ffi::OsStr, fmt::{self, Write}, io, iter::{Copied, FusedIterator}, ops::{Add, AddAssign, Deref, DerefMut, Index, IndexMut}, rc::Rc, slice, str::Utf8Error, string::FromUtf8Error};
|
||||
use std::{
|
||||
borrow::{Borrow, BorrowMut, Cow},
|
||||
ffi::{OsStr, OsString},
|
||||
fmt::{self, Write},
|
||||
io,
|
||||
iter::{Copied, FusedIterator},
|
||||
ops::{Add, AddAssign, Deref, DerefMut, Index, IndexMut},
|
||||
rc::Rc,
|
||||
slice,
|
||||
str::Utf8Error,
|
||||
string::FromUtf8Error,
|
||||
};
|
||||
|
||||
use unicode_ident::{is_xid_continue, is_xid_start};
|
||||
|
||||
|
@ -28,7 +39,9 @@ fn is_continue(b: u8) -> bool {
|
|||
#[inline]
|
||||
fn calc_continue(mut ch: u32, bytes: &[u8]) -> Option<char> {
|
||||
for b in bytes {
|
||||
if !is_continue(*b) { return None }
|
||||
if !is_continue(*b) {
|
||||
return None
|
||||
}
|
||||
ch = (ch << 6) | (b & 0x3f) as u32;
|
||||
}
|
||||
char::from_u32(ch)
|
||||
|
@ -40,23 +53,29 @@ fn next_codepoint(bytes: &[u8]) -> Option<(&[u8], Result<char, u8>)> {
|
|||
match init {
|
||||
0..=0x7f => return Some((&bytes[1..], Ok(init as char))),
|
||||
0xc0..=0xdf => 'case: {
|
||||
if bytes.len() < 2 { break 'case }
|
||||
if bytes.len() < 2 {
|
||||
break 'case
|
||||
}
|
||||
let Some(ch) = calc_continue(init as u32 & 0x1f, &bytes[1..2]) else {
|
||||
break 'case;
|
||||
break 'case
|
||||
};
|
||||
return Some((&bytes[2..], Ok(ch)))
|
||||
},
|
||||
}
|
||||
0xe0..=0xef => 'case: {
|
||||
if bytes.len() < 3 { break 'case }
|
||||
if bytes.len() < 3 {
|
||||
break 'case
|
||||
}
|
||||
let Some(ch) = calc_continue(init as u32 & 0x0f, &bytes[1..3]) else {
|
||||
break 'case;
|
||||
break 'case
|
||||
};
|
||||
return Some((&bytes[3..], Ok(ch)))
|
||||
},
|
||||
}
|
||||
0xf0..=0xf7 => 'case: {
|
||||
if bytes.len() < 4 { break 'case }
|
||||
if bytes.len() < 4 {
|
||||
break 'case
|
||||
}
|
||||
let Some(ch) = calc_continue(init as u32 & 0x07, &bytes[1..4]) else {
|
||||
break 'case;
|
||||
break 'case
|
||||
};
|
||||
return Some((&bytes[4..], Ok(ch)))
|
||||
}
|
||||
|
@ -69,45 +88,55 @@ fn next_codepoint(bytes: &[u8]) -> Option<(&[u8], Result<char, u8>)> {
|
|||
#[inline]
|
||||
fn next_codepoint_back(bytes: &[u8]) -> Option<(&[u8], Result<char, u8>)> {
|
||||
let len = bytes.len();
|
||||
if len < 1 { return None }
|
||||
if len < 1 {
|
||||
return None
|
||||
}
|
||||
|
||||
let last = bytes[len-1];
|
||||
let last = bytes[len - 1];
|
||||
if (0..=0x7f).contains(&last) {
|
||||
return Some((&bytes[..len-1], Ok(last as char)))
|
||||
return Some((&bytes[..len - 1], Ok(last as char)))
|
||||
}
|
||||
|
||||
'case: {
|
||||
if !is_continue(last) { break 'case }
|
||||
if !is_continue(last) {
|
||||
break 'case
|
||||
}
|
||||
|
||||
if len < 2 { break 'case }
|
||||
let b1 = bytes[len-2];
|
||||
if len < 2 {
|
||||
break 'case
|
||||
}
|
||||
let b1 = bytes[len - 2];
|
||||
if 0xe0 & b1 == 0xc0 {
|
||||
if let Some(ch) = calc_continue(b1 as u32 & 0x1f, &[last]) {
|
||||
return Some((&bytes[..len-2], Ok(ch)))
|
||||
return Some((&bytes[..len - 2], Ok(ch)))
|
||||
};
|
||||
} else if !is_continue(b1) {
|
||||
break 'case
|
||||
}
|
||||
|
||||
if len < 3 { break 'case }
|
||||
let b2 = bytes[len-3];
|
||||
if len < 3 {
|
||||
break 'case
|
||||
}
|
||||
let b2 = bytes[len - 3];
|
||||
if 0xf0 & b2 == 0xe0 {
|
||||
if let Some(ch) = calc_continue(b2 as u32 & 0x0f, &[b1, last]) {
|
||||
return Some((&bytes[..len-3], Ok(ch)))
|
||||
return Some((&bytes[..len - 3], Ok(ch)))
|
||||
};
|
||||
} else if !is_continue(b2) {
|
||||
break 'case
|
||||
}
|
||||
|
||||
if len < 4 { break 'case }
|
||||
let b3 = bytes[len-4];
|
||||
if len < 4 {
|
||||
break 'case
|
||||
}
|
||||
let b3 = bytes[len - 4];
|
||||
if 0xf8 & b3 == 0xf0 {
|
||||
if let Some(ch) = calc_continue(b3 as u32 & 0x07, &[b2, b1, last]) {
|
||||
return Some((&bytes[..len-4], Ok(ch)))
|
||||
return Some((&bytes[..len - 4], Ok(ch)))
|
||||
};
|
||||
}
|
||||
}
|
||||
Some((&bytes[..len-1], Err(last)))
|
||||
Some((&bytes[..len - 1], Err(last)))
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
@ -230,32 +259,69 @@ impl BorrowMut<LStr> for LString {
|
|||
|
||||
impl From<LString> for Vec<u8> {
|
||||
#[inline]
|
||||
fn from(value: LString) -> Self { value.inner }
|
||||
fn from(value: LString) -> Self {
|
||||
value.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for LString {
|
||||
#[inline]
|
||||
fn from(value: Vec<u8>) -> Self { Self { inner: value } }
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
Self { inner: value }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for LString {
|
||||
#[inline]
|
||||
fn from(value: String) -> Self { Self { inner: value.into_bytes() } }
|
||||
fn from(value: String) -> Self {
|
||||
Self {
|
||||
inner: value.into_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OsString> for LString {
|
||||
#[inline]
|
||||
fn from(value: OsString) -> Self {
|
||||
Self {
|
||||
inner: value.into_encoded_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&LStr> for LString {
|
||||
#[inline]
|
||||
fn from(value: &LStr) -> Self { value.to_owned() }
|
||||
fn from(value: &LStr) -> Self {
|
||||
value.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for LString {
|
||||
#[inline]
|
||||
fn from(value: &str) -> Self { value.to_owned().into() }
|
||||
fn from(value: &str) -> Self {
|
||||
value.to_owned().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for LString {
|
||||
#[inline]
|
||||
fn from(value: &[u8]) -> Self { value.to_owned().into() }
|
||||
fn from(value: &[u8]) -> Self {
|
||||
value.to_owned().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> From<&[u8; N]> for LString {
|
||||
#[inline]
|
||||
fn from(value: &[u8; N]) -> Self {
|
||||
value.as_slice().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> From<[u8; N]> for LString {
|
||||
#[inline]
|
||||
fn from(value: [u8; N]) -> Self {
|
||||
value.as_slice().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'_, LStr>> for LString {
|
||||
|
@ -263,7 +329,7 @@ impl From<Cow<'_, LStr>> for LString {
|
|||
fn from(value: Cow<'_, LStr>) -> Self {
|
||||
match value {
|
||||
Cow::Borrowed(b) => b.to_owned(),
|
||||
Cow::Owned(o) => o
|
||||
Cow::Owned(o) => o,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -282,9 +348,18 @@ impl From<Cow<'_, [u8]>> for LString {
|
|||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> From<Cow<'_, [u8; N]>> for LString {
|
||||
#[inline]
|
||||
fn from(value: Cow<'_, [u8; N]>) -> Self {
|
||||
value.into_owned().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a LStr> for &'a [u8] {
|
||||
#[inline]
|
||||
fn from(value: &'a LStr) -> Self { &value.inner }
|
||||
fn from(value: &'a LStr) -> Self {
|
||||
&value.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [u8]> for &'a LStr {
|
||||
|
@ -294,6 +369,13 @@ impl<'a> From<&'a [u8]> for &'a LStr {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, const N: usize> From<&'a [u8; N]> for &'a LStr {
|
||||
#[inline]
|
||||
fn from(value: &'a [u8; N]) -> Self {
|
||||
LStr::from_bytes(value.as_slice())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for &'a LStr {
|
||||
#[inline]
|
||||
fn from(value: &'a str) -> Self {
|
||||
|
@ -301,9 +383,18 @@ impl<'a> From<&'a str> for &'a LStr {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a OsStr> for &'a LStr {
|
||||
#[inline]
|
||||
fn from(value: &'a OsStr) -> Self {
|
||||
LStr::from_os_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a mut LStr> for &'a mut [u8] {
|
||||
#[inline]
|
||||
fn from(value: &'a mut LStr) -> Self { &mut value.inner }
|
||||
fn from(value: &'a mut LStr) -> Self {
|
||||
&mut value.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a mut [u8]> for &'a mut LStr {
|
||||
|
@ -315,7 +406,9 @@ impl<'a> From<&'a mut [u8]> for &'a mut LStr {
|
|||
|
||||
impl<'a> From<&'a LString> for &'a LStr {
|
||||
#[inline]
|
||||
fn from(value: &'a LString) -> Self { value }
|
||||
fn from(value: &'a LString) -> Self {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&LStr> for Rc<LStr> {
|
||||
|
@ -376,35 +469,35 @@ impl From<u8> for LString {
|
|||
|
||||
impl FromIterator<char> for LString {
|
||||
#[inline]
|
||||
fn from_iter<I: IntoIterator<Item=char>>(iter: I) -> Self {
|
||||
fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
|
||||
String::from_iter(iter).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<u8> for LString {
|
||||
#[inline]
|
||||
fn from_iter<T: IntoIterator<Item=u8>>(iter: T) -> Self {
|
||||
fn from_iter<T: IntoIterator<Item = u8>>(iter: T) -> Self {
|
||||
Vec::from_iter(iter).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<u8> for LString {
|
||||
#[inline]
|
||||
fn extend<I: IntoIterator<Item=u8>>(&mut self, iter: I) {
|
||||
fn extend<I: IntoIterator<Item = u8>>(&mut self, iter: I) {
|
||||
self.inner.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Extend<&'a u8> for LString {
|
||||
#[inline]
|
||||
fn extend<I: IntoIterator<Item=&'a u8>>(&mut self, iter: I) {
|
||||
fn extend<I: IntoIterator<Item = &'a u8>>(&mut self, iter: I) {
|
||||
self.inner.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<char> for LString {
|
||||
#[inline]
|
||||
fn extend<I: IntoIterator<Item=char>>(&mut self, iter: I) {
|
||||
fn extend<I: IntoIterator<Item = char>>(&mut self, iter: I) {
|
||||
let iter = iter.into_iter();
|
||||
let (lo, _) = iter.size_hint();
|
||||
self.reserve(lo);
|
||||
|
@ -414,7 +507,7 @@ impl Extend<char> for LString {
|
|||
|
||||
impl<'a> Extend<&'a char> for LString {
|
||||
#[inline]
|
||||
fn extend<I: IntoIterator<Item=&'a char>>(&mut self, iter: I) {
|
||||
fn extend<I: IntoIterator<Item = &'a char>>(&mut self, iter: I) {
|
||||
let iter = iter.into_iter();
|
||||
let (lo, _) = iter.size_hint();
|
||||
self.reserve(lo);
|
||||
|
@ -432,7 +525,9 @@ impl io::Write for LString {
|
|||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> { Ok(()) }
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
//impl fmt::Write for LString {
|
||||
|
@ -511,15 +606,25 @@ impl<'a> Iterator for Bytes<'a> {
|
|||
type Item = u8;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> { self.0.next() }
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.0.next()
|
||||
}
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) { self.0.size_hint() }
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.0.size_hint()
|
||||
}
|
||||
#[inline]
|
||||
fn count(self) -> usize { self.0.count() }
|
||||
fn count(self) -> usize {
|
||||
self.0.count()
|
||||
}
|
||||
#[inline]
|
||||
fn last(self) -> Option<Self::Item> { self.0.last() }
|
||||
fn last(self) -> Option<Self::Item> {
|
||||
self.0.last()
|
||||
}
|
||||
#[inline]
|
||||
fn nth(&mut self, n: usize) -> Option<Self::Item> { self.0.nth(n) }
|
||||
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
||||
self.0.nth(n)
|
||||
}
|
||||
#[inline]
|
||||
fn all<F: FnMut(Self::Item) -> bool>(&mut self, f: F) -> bool {
|
||||
self.0.all(f)
|
||||
|
@ -540,7 +645,9 @@ impl<'a> Iterator for Bytes<'a> {
|
|||
|
||||
impl<'a> ExactSizeIterator for Bytes<'a> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize { self.0.len() }
|
||||
fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FusedIterator for Bytes<'a> {}
|
||||
|
@ -561,7 +668,7 @@ impl<'a> Iterator for LosslessChars<'a> {
|
|||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let len = self.0.len();
|
||||
((len + 3)/4, Some(len))
|
||||
((len + 3) / 4, Some(len))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -573,6 +680,42 @@ impl<'a> DoubleEndedIterator for LosslessChars<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LosslessCharsIndices<'a>(&'a [u8], usize);
|
||||
|
||||
impl<'a> Iterator for LosslessCharsIndices<'a> {
|
||||
type Item = (usize, Result<char, u8>);
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (new_bytes, res) = next_codepoint(self.0)?;
|
||||
let index = self.1;
|
||||
self.0 = new_bytes;
|
||||
match res {
|
||||
Ok(c) => self.1 += c.len_utf8(),
|
||||
Err(_) => self.1 += 1,
|
||||
}
|
||||
Some((index, res))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let len = self.0.len();
|
||||
((len + 3) / 4, Some(len))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DoubleEndedIterator for LosslessCharsIndices<'a> {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
let (new_bytes, res) = next_codepoint_back(self.0)?;
|
||||
self.0 = new_bytes;
|
||||
match res {
|
||||
Ok(c) => self.1 -= c.len_utf8(),
|
||||
Err(_) => self.1 -= 1,
|
||||
}
|
||||
Some((self.1, res))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Chars<'a>(LosslessChars<'a>);
|
||||
|
@ -605,6 +748,36 @@ impl<'a> DoubleEndedIterator for Chars<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CharsIndices<'a>(LosslessCharsIndices<'a>);
|
||||
|
||||
impl<'a> Iterator for CharsIndices<'a> {
|
||||
type Item = (usize, char);
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if let (index, Ok(c)) = self.0.next()? {
|
||||
return Some((index, c))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.0.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DoubleEndedIterator for CharsIndices<'a> {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if let (index, Ok(c)) = self.0.next_back()? {
|
||||
return Some((index, c))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LStr {
|
||||
#[inline]
|
||||
|
@ -612,6 +785,11 @@ impl LStr {
|
|||
Self::from_bytes(string.as_bytes())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_os_str(os_string: &OsStr) -> &Self {
|
||||
Self::from_bytes(os_string.as_encoded_bytes())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn from_bytes(bytes: &[u8]) -> &Self {
|
||||
unsafe { &*(bytes as *const [u8] as *const LStr) }
|
||||
|
@ -623,19 +801,35 @@ impl LStr {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn as_bytes(&self) -> &[u8] { &self.inner }
|
||||
pub const fn as_bytes(&self) -> &[u8] {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_bytes_mut(&mut self) -> &mut [u8] { &mut self.inner }
|
||||
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.inner
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn byte_at(&self, n: usize) -> u8 {
|
||||
self.inner[n]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn byte_get(&self, n: usize) -> Option<u8> {
|
||||
self.inner.get(n).copied()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bytes(&self) -> Bytes {
|
||||
Bytes(self.as_bytes().iter().copied())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn chars(&self) -> Chars {
|
||||
Chars(self.chars_lossless())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn chars_lossless(&self) -> LosslessChars {
|
||||
LosslessChars(self.as_bytes())
|
||||
|
@ -671,11 +865,13 @@ impl LStr {
|
|||
|
||||
#[inline]
|
||||
pub fn to_os_str(&self) -> Cow<OsStr> {
|
||||
#[cfg(unix)] {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
Cow::Borrowed(OsStr::from_bytes(self.as_bytes()))
|
||||
}
|
||||
#[cfg(not(unix))] {
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
Cow::Owned(self.to_string().into())
|
||||
}
|
||||
}
|
||||
|
@ -746,10 +942,21 @@ impl LStr {
|
|||
self.as_bytes().ends_with(s.as_bytes())
|
||||
}
|
||||
|
||||
pub fn strip_prefix(&self, prefix: &LStr) -> Option<&LStr> {
|
||||
self.as_bytes()
|
||||
.strip_prefix(prefix.as_bytes())
|
||||
.map(LStr::from_bytes)
|
||||
}
|
||||
|
||||
pub fn strip_suffix(&self, suffix: &LStr) -> Option<&LStr> {
|
||||
self.as_bytes()
|
||||
.strip_suffix(suffix.as_bytes())
|
||||
.map(LStr::from_bytes)
|
||||
}
|
||||
|
||||
pub fn is_identifier(&self) -> bool {
|
||||
let mut chars = self.chars_lossless();
|
||||
let first = chars.next()
|
||||
.is_some_and(|ch| ch.is_ok_and(is_xid_start));
|
||||
let first = chars.next().is_some_and(|ch| ch.is_ok_and(is_xid_start));
|
||||
if !first {
|
||||
return false
|
||||
}
|
||||
|
@ -775,11 +982,15 @@ impl AddAssign<&LStr> for LString {
|
|||
}
|
||||
|
||||
impl Default for &LStr {
|
||||
fn default() -> Self { [].as_ref().into() }
|
||||
fn default() -> Self {
|
||||
[].as_ref().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for &mut LStr {
|
||||
fn default() -> Self { [].as_mut().into() }
|
||||
fn default() -> Self {
|
||||
[].as_mut().into()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_index {
|
||||
|
@ -821,4 +1032,3 @@ impl_index!(std::ops::RangeInclusive<usize>);
|
|||
impl_index!(std::ops::RangeTo<usize>);
|
||||
impl_index!(std::ops::RangeToInclusive<usize>);
|
||||
impl_index!((std::ops::Bound<usize>, std::ops::Bound<usize>));
|
||||
|
||||
|
|
|
@ -1,442 +0,0 @@
|
|||
use std::rc::Rc;
|
||||
use crate::ast::*;
|
||||
use crate::value::Value;
|
||||
use crate::symbol::Symbol;
|
||||
use crate::parser_util::*;
|
||||
use crate::lstring::LStr;
|
||||
use crate::lstr;
|
||||
use num_complex::Complex64;
|
||||
|
||||
grammar;
|
||||
|
||||
extern {
|
||||
type Error = ParseError;
|
||||
|
||||
}
|
||||
|
||||
match {
|
||||
// line separator (use \ to escape newlines)
|
||||
r";|\n" => LineSeparator,
|
||||
|
||||
// whitespace
|
||||
r"[ \t\r]*" => {},
|
||||
r"\\\r?\n" => {},
|
||||
r"--[^\n]*" => {},
|
||||
|
||||
// kw literals
|
||||
"true",
|
||||
"false",
|
||||
"nil",
|
||||
// kw variables
|
||||
"global",
|
||||
"var",
|
||||
// kw logic
|
||||
"and",
|
||||
"or",
|
||||
"not",
|
||||
// kw control flow
|
||||
"begin",
|
||||
"end",
|
||||
"if",
|
||||
"then",
|
||||
"elif",
|
||||
"else",
|
||||
"while",
|
||||
"for",
|
||||
"do",
|
||||
"in",
|
||||
"continue",
|
||||
"break",
|
||||
"try",
|
||||
"catch",
|
||||
"return",
|
||||
} else {
|
||||
// identifiers
|
||||
r"[a-zA-Z_][a-zA-Z0-9_]*" => TokIdentifier,
|
||||
|
||||
// literals
|
||||
r"0x[0-9A-Fa-f][0-9A-Fa-f_]*" => TokHexInteger,
|
||||
r"[0-9][0-9_]*" => TokDecInteger,
|
||||
r"0o[0-7][0-7]*" => TokOctInteger,
|
||||
r"0s[0-5][0-5]*" => TokSexInteger,
|
||||
r"0b[01][01_]*" => TokBinInteger,
|
||||
|
||||
r"[0-9][0-9_]*([eE][-+]?[0-9_]*[0-9][0-9_]*i?|i)|([0-9][0-9_]*)?\.[0-9_]+([eE]_*[-+]?[0-9_]*[0-9][0-9_]*)?i?" => TokFloat,
|
||||
|
||||
r#""([^\\"]|\\.)*""# => TokStringDouble,
|
||||
r#"'[^']*'"# => TokStringSingle,
|
||||
|
||||
r#":[a-zA-Z_][a-zA-Z0-9_]*"# => TokSymbolNone,
|
||||
r#":'[^']*'"# => TokSymbolSingle,
|
||||
r#":"([^\\"]|\\.)*""# => TokSymbolDouble,
|
||||
} else {
|
||||
// everything else
|
||||
_
|
||||
}
|
||||
|
||||
pub Block: Box<Expr<'input>> = {
|
||||
LineSeparator* <xs:(<Expr> LineSeparator+)*> <x:Expr> LineSeparator* => {
|
||||
let v = xs.into_iter().chain(std::iter::once(x)).map(|x| *x).collect();
|
||||
return Box::new(Expr::Block(v));
|
||||
},
|
||||
LineSeparator* => Box::new(Expr::Block(Vec::new())),
|
||||
}
|
||||
|
||||
|
||||
Expr: Box<Expr<'input>> = {
|
||||
"return" <e:Expr> => Box::new(Expr::Return(e)),
|
||||
Assign,
|
||||
}
|
||||
|
||||
//
|
||||
// assignment
|
||||
//
|
||||
|
||||
|
||||
Assign: Box<Expr<'input>> = {
|
||||
<l:LValue> <o:AssignOp> <r:Assign> => Box::new(Expr::Assign(o, l, r)),
|
||||
"var" <i:Identifier> "=" <r:Assign> => Box::new(Expr::AssignVar(i, r)),
|
||||
"global" <i:Identifier> "=" <r:Assign> => Box::new(Expr::AssignGlobal(i, r)),
|
||||
Or,
|
||||
}
|
||||
|
||||
LValue: Box<LValue<'input>> = {
|
||||
<Identifier> => Box::new(LValue::Ident(<>)),
|
||||
<l:BinaryIndex> "!" <r:CallOrAccess> => Box::new(LValue::Index(l, r)),
|
||||
<l:CallOrAccess> "." <r:Identifier> => Box::new(LValue::Index(l, Box::new(Expr::Literal(Value::Symbol(Symbol::get(r)))))),
|
||||
}
|
||||
|
||||
AssignOp: Option<BinaryOp> = {
|
||||
"=" => None,
|
||||
"+=" => Some(BinaryOp::Add),
|
||||
"-=" => Some(BinaryOp::Sub),
|
||||
"*=" => Some(BinaryOp::Mul),
|
||||
"/=" => Some(BinaryOp::Div),
|
||||
"%=" => Some(BinaryOp::Mod),
|
||||
"^=" => Some(BinaryOp::Pow),
|
||||
"++=" => Some(BinaryOp::Concat),
|
||||
"&=" => Some(BinaryOp::Append),
|
||||
}
|
||||
|
||||
//
|
||||
// logical ops
|
||||
//
|
||||
|
||||
// or
|
||||
Or: Box<Expr<'input>> = {
|
||||
<l:Or> "or" <r:And> => Box::new(Expr::Or(l, r)),
|
||||
And,
|
||||
}
|
||||
|
||||
// and
|
||||
And: Box<Expr<'input>> = {
|
||||
<l:And> "and" <r:UnaryNot> => Box::new(Expr::And(l, r)),
|
||||
UnaryNot,
|
||||
}
|
||||
|
||||
// not
|
||||
UnaryNot: Box<Expr<'input>> = {
|
||||
"not" <r:Pipe> => Box::new(Expr::UnaryOp(UnaryOp::Not, r)),
|
||||
Pipe,
|
||||
}
|
||||
|
||||
//
|
||||
// pipe
|
||||
//
|
||||
|
||||
Pipe: Box<Expr<'input>> = {
|
||||
<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![lstr!("$")], e)),
|
||||
"::" <e:BinaryCompare> => Box::new(Expr::Lambda(vec![lstr!("$"), lstr!("$$")], e)),
|
||||
BinaryCompare,
|
||||
}
|
||||
|
||||
//
|
||||
// operations
|
||||
//
|
||||
|
||||
// == != > < >= <=
|
||||
BinaryCompare: Box<Expr<'input>> = {
|
||||
<l:BinaryConcat> <o:CompareOp> <r:BinaryConcat> => Box::new(Expr::BinaryOp(o, l, r)),
|
||||
BinaryConcat,
|
||||
}
|
||||
|
||||
CompareOp: BinaryOp = {
|
||||
"==" => BinaryOp::Eq,
|
||||
"!=" => BinaryOp::Ne,
|
||||
">" => BinaryOp::Gt,
|
||||
"<" => BinaryOp::Lt,
|
||||
">=" => BinaryOp::Ge,
|
||||
"<=" => BinaryOp::Le,
|
||||
}
|
||||
|
||||
|
||||
// concat (++)
|
||||
BinaryConcat: Box<Expr<'input>> = {
|
||||
<l:BinaryConcat> "++" <r:BinaryAppend> => Box::new(Expr::BinaryOp(BinaryOp::Concat, l, r)),
|
||||
BinaryAppend,
|
||||
}
|
||||
|
||||
// append ( & )
|
||||
BinaryAppend: Box<Expr<'input>> = {
|
||||
<l:BinaryAppend> "&" <r:BinaryRange> => Box::new(Expr::BinaryOp(BinaryOp::Append, l, r)),
|
||||
BinaryRange,
|
||||
}
|
||||
|
||||
|
||||
|
||||
// .. ..= ..*
|
||||
BinaryRange: Box<Expr<'input>> = {
|
||||
<l:BinaryBitOr> <o:RangeOp> <r:BinaryBitOr> => Box::new(Expr::BinaryOp(o, l, r)),
|
||||
<l:BinaryBitOr> "..*" => Box::new(Expr::UnaryOp(UnaryOp::RangeEndless, l)),
|
||||
BinaryBitOr,
|
||||
}
|
||||
|
||||
RangeOp: BinaryOp = {
|
||||
".." => BinaryOp::Range,
|
||||
"..=" => BinaryOp::RangeIncl,
|
||||
}
|
||||
// #|
|
||||
BinaryBitOr: Box<Expr<'input>> = {
|
||||
<l:BinaryBitOr> "#|" <r:BinaryBitXor> => Box::new(Expr::BinaryOp(BinaryOp::BitOr, l, r)),
|
||||
BinaryBitXor,
|
||||
}
|
||||
|
||||
// #^
|
||||
BinaryBitXor: Box<Expr<'input>> = {
|
||||
<l:BinaryBitXor> "#^" <r:BinaryBitAnd> => Box::new(Expr::BinaryOp(BinaryOp::BitXor, l, r)),
|
||||
BinaryBitAnd,
|
||||
}
|
||||
|
||||
// #&
|
||||
BinaryBitAnd: Box<Expr<'input>> = {
|
||||
<l:BinaryBitAnd> "#&" <r:BinaryShift> => Box::new(Expr::BinaryOp(BinaryOp::BitAnd, l, r)),
|
||||
BinaryShift,
|
||||
}
|
||||
|
||||
// >> <<
|
||||
BinaryShift: Box<Expr<'input>> = {
|
||||
<l:BinaryShift> <o:ShiftOp> <r:BinaryAdd> => Box::new(Expr::BinaryOp(o, l, r)),
|
||||
BinaryAdd,
|
||||
}
|
||||
|
||||
ShiftOp: BinaryOp = {
|
||||
">>" => BinaryOp::Shr,
|
||||
"<<" => BinaryOp::Shl,
|
||||
}
|
||||
|
||||
// + -
|
||||
BinaryAdd: Box<Expr<'input>> = {
|
||||
<l:BinaryAdd> <o:AddOp> <r:BinaryMul> => Box::new(Expr::BinaryOp(o, l, r)),
|
||||
BinaryMul,
|
||||
}
|
||||
|
||||
AddOp: BinaryOp = {
|
||||
"+" => BinaryOp::Add,
|
||||
"-" => BinaryOp::Sub,
|
||||
}
|
||||
|
||||
|
||||
// * / %
|
||||
BinaryMul: Box<Expr<'input>> = {
|
||||
<l:BinaryMul> <o:MulOp> <r:UnaryMinus> => Box::new(Expr::BinaryOp(o, l, r)),
|
||||
UnaryMinus,
|
||||
}
|
||||
|
||||
MulOp: BinaryOp = {
|
||||
"*" => BinaryOp::Mul,
|
||||
"/" => BinaryOp::Div,
|
||||
"//" => BinaryOp::IntDiv,
|
||||
"%" => BinaryOp::Mod,
|
||||
}
|
||||
|
||||
|
||||
// unary-
|
||||
UnaryMinus: Box<Expr<'input>> = {
|
||||
"-" <r:BinaryPow> => Box::new(Expr::UnaryOp(UnaryOp::Neg, r)),
|
||||
BinaryPow,
|
||||
}
|
||||
|
||||
|
||||
// power ( ^ )
|
||||
BinaryPow: Box<Expr<'input>> = {
|
||||
<l:BinaryIndex> "^" <r:UnaryMinus> => Box::new(Expr::BinaryOp(BinaryOp::Pow, l, r)),
|
||||
BinaryIndex,
|
||||
}
|
||||
|
||||
|
||||
// index ( ! )
|
||||
BinaryIndex: Box<Expr<'input>> = {
|
||||
<l:BinaryIndex> "!" <r:UnaryMinus2> => Box::new(Expr::Index(l, r)),
|
||||
CallOrAccess,
|
||||
}
|
||||
|
||||
// unary-
|
||||
UnaryMinus2: Box<Expr<'input>> = {
|
||||
"-" <r:UnaryMinus2> => Box::new(Expr::UnaryOp(UnaryOp::Neg, r)),
|
||||
CallOrAccess,
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// things
|
||||
//
|
||||
|
||||
// function call
|
||||
CallOrAccess: Box<Expr<'input>> = {
|
||||
<l:CallOrAccess> "(" <args:ExprList> ")" => Box::new(Expr::FnCall(l, args)),
|
||||
<l:CallOrAccess> "->" <r:Identifier> "(" <args:ExprList> ")" => Box::new(Expr::AssocFnCall(l, Symbol::get(r), args)),
|
||||
<l:CallOrAccess> "." <r:Identifier> => Box::new(Expr::Index(l, Box::new(Expr::Literal(Value::Symbol(Symbol::get(r)))))),
|
||||
Term,
|
||||
}
|
||||
|
||||
//
|
||||
// base
|
||||
//
|
||||
|
||||
|
||||
Term: Box<Expr<'input>> = {
|
||||
Identifier => Box::new(Expr::Ident(<>)),
|
||||
TermNotIdent,
|
||||
}
|
||||
|
||||
TermNotIdent: Box<Expr<'input>> = {
|
||||
"(" <Expr> ")" => <>,
|
||||
"[" <ExprList> "]" => Box::new(Expr::List(<>)),
|
||||
"{" <TableItems> "}" => Box::new(Expr::Table(<>)),
|
||||
"$" => Box::new(Expr::Ident(lstr!("$"))),
|
||||
"$$" => Box::new(Expr::Ident(lstr!("$$"))),
|
||||
|
||||
"do" <Block> "end" => <>,
|
||||
"if" <IfStmtChain> => <>,
|
||||
"while" <a:Expr> "do" <b:Block> "end"
|
||||
=> Box::new(Expr::While(a, b)),
|
||||
"for" <v:Identifier> "in" <a:Expr> "do" <b:Block> "end"
|
||||
=> Box::new(Expr::For(v, a, b)),
|
||||
"try" <b:Block> <mut ch:CatchChain> => {
|
||||
ch.reverse();
|
||||
Box::new(Expr::Try(b, ch))
|
||||
},
|
||||
|
||||
Literal => Box::new(Expr::Literal(<>)),
|
||||
}
|
||||
|
||||
IfStmtChain: Box<Expr<'input>> = {
|
||||
<a:Expr> "then" <b:Block> "end" => Box::new(Expr::If(a, b, None)),
|
||||
<a:Expr> "then" <b:Block> "else" <c:Block> "end" => Box::new(Expr::If(a, b, Some(c))),
|
||||
<a:Expr> "then" <b:Block> "elif" <c:IfStmtChain> => Box::new(Expr::If(a, b, Some(c))),
|
||||
}
|
||||
|
||||
CatchChain: Vec<CatchBlock<'input>> = {
|
||||
"catch" <types:SymbolList> <name:("in" <Identifier>)?> "do" <b:Block> <mut ch:CatchChain> => {
|
||||
|
||||
ch.push(CatchBlock { name, types: Some(types), body: *b });
|
||||
ch
|
||||
},
|
||||
"catch" "*" <name:("in" <Identifier>)?> "do" <b:Block> <mut ch:CatchChain> => {
|
||||
ch.push(CatchBlock { name, types: None, body: *b });
|
||||
ch
|
||||
},
|
||||
"end" => Vec::new(),
|
||||
}
|
||||
|
||||
SymbolList: Vec<Symbol> = {
|
||||
<mut xs:(<SymbolLiteral> ",")*> <x:SymbolLiteral?> => {
|
||||
if let Some(x) = x { xs.push(x) };
|
||||
xs
|
||||
},
|
||||
}
|
||||
|
||||
ExprList: Vec<Expr<'input>> = {
|
||||
<xs:(<Expr> ",")*> <x:Expr?> => {
|
||||
let mut xs: Vec<_> = xs.into_iter().map(|x| *x).collect();
|
||||
if let Some(x) = x { xs.push(*x) };
|
||||
xs
|
||||
}
|
||||
}
|
||||
|
||||
TableItems: Vec<(Expr<'input>, Expr<'input>)> = {
|
||||
<mut xs:(<TableItem> ",")*> <x:TableItem?> => {
|
||||
if let Some(x) = x { xs.push(x) };
|
||||
xs
|
||||
}
|
||||
}
|
||||
|
||||
TableItem: (Expr<'input>, Expr<'input>) = {
|
||||
<k:TableKey> "=" <v:Expr> => (k, *v),
|
||||
}
|
||||
|
||||
TableKey: Expr<'input> = {
|
||||
Identifier => Expr::Literal(Value::Symbol(Symbol::get(<>))),
|
||||
TermNotIdent => *<>,
|
||||
}
|
||||
|
||||
IdentList: Vec<&'input LStr> = {
|
||||
<mut xs:(<Identifier> ",")*> <x:Identifier?>
|
||||
=> { if let Some(x) = x { xs.push(x) }; xs }
|
||||
}
|
||||
|
||||
Identifier: &'input LStr = TokIdentifier => <>.into();
|
||||
|
||||
|
||||
//
|
||||
// literals
|
||||
//
|
||||
|
||||
Literal: Value = {
|
||||
TokDecInteger =>? parse_int(<>, 10)
|
||||
.map(Value::Int)
|
||||
.map_err(|e| ParseError::from(e).into()),
|
||||
TokHexInteger =>? parse_int(&<>[2..], 16)
|
||||
.map(Value::Int)
|
||||
.map_err(|e| ParseError::from(e).into()),
|
||||
TokOctInteger =>? parse_int(&<>[2..], 8)
|
||||
.map(Value::Int)
|
||||
.map_err(|e| ParseError::from(e).into()),
|
||||
TokSexInteger =>? parse_int(&<>[2..], 6)
|
||||
.map(Value::Int)
|
||||
.map_err(|e| ParseError::from(e).into()),
|
||||
TokBinInteger =>? parse_int(&<>[2..], 2)
|
||||
.map(Value::Int)
|
||||
.map_err(|e| ParseError::from(e).into()),
|
||||
<f:TokFloat> =>? {
|
||||
if let Some(f) = f.strip_suffix('i') {
|
||||
parse_float(f)
|
||||
.map(|im| Value::Complex(Complex64::new(0.0, im)))
|
||||
.map_err(|e| ParseError::from(e).into())
|
||||
} else {
|
||||
parse_float(f)
|
||||
.map(Value::Float)
|
||||
.map_err(|e| ParseError::from(e).into())
|
||||
}
|
||||
},
|
||||
StringLiteral => Value::String(<>),
|
||||
SymbolLiteral => Value::Symbol(<>),
|
||||
"true" => Value::Bool(true),
|
||||
"false" => Value::Bool(false),
|
||||
"nil" => Value::Nil,
|
||||
}
|
||||
|
||||
StringLiteral: Rc<LStr> = {
|
||||
TokStringSingle => LStr::from_str(&<>[1..<>.len()-1]).into(),
|
||||
TokStringDouble =>? parse_str_escapes(&<>[1..<>.len()-1])
|
||||
.map(|s| s.into())
|
||||
.map_err(|e| ParseError::from(e).into()),
|
||||
}
|
||||
|
||||
SymbolLiteral: Symbol = {
|
||||
TokSymbolNone => Symbol::get(&<>[1..]),
|
||||
TokSymbolSingle => Symbol::get(&<>[2..<>.len()-1]),
|
||||
TokSymbolDouble =>? parse_str_escapes(&<>[2..<>.len()-1])
|
||||
.map(|s| Symbol::get(&s))
|
||||
.map_err(|e| ParseError::from(e).into()),
|
||||
}
|
314
talc-lang/src/parser/ast.rs
Normal file
314
talc-lang/src/parser/ast.rs
Normal file
|
@ -0,0 +1,314 @@
|
|||
use core::fmt;
|
||||
|
||||
use crate::{symbol::Symbol, value::Value};
|
||||
|
||||
use super::Span;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum BinaryOp {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Mod,
|
||||
Pow,
|
||||
IntDiv,
|
||||
Shr,
|
||||
Shl,
|
||||
BitAnd,
|
||||
BitXor,
|
||||
BitOr,
|
||||
Eq,
|
||||
Ne,
|
||||
Gt,
|
||||
Lt,
|
||||
Ge,
|
||||
Le,
|
||||
Concat,
|
||||
Append,
|
||||
Range,
|
||||
RangeIncl,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum UnaryOp {
|
||||
Neg,
|
||||
Not,
|
||||
RangeEndless,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Expr {
|
||||
pub span: Span,
|
||||
pub kind: ExprKind,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ExprKind {
|
||||
Literal(Value),
|
||||
Ident(Symbol),
|
||||
|
||||
UnaryOp(UnaryOp, Box<Expr>),
|
||||
BinaryOp(BinaryOp, Box<Expr>, Box<Expr>),
|
||||
|
||||
Assign(Option<BinaryOp>, Box<LValue>, Box<Expr>),
|
||||
AssignVar(Symbol, Box<Expr>),
|
||||
AssignGlobal(Symbol, Box<Expr>),
|
||||
FnDef(Option<Symbol>, Vec<Symbol>, Box<Expr>),
|
||||
|
||||
Index(Box<Expr>, Box<Expr>),
|
||||
FnCall(Box<Expr>, Vec<Expr>),
|
||||
AssocFnCall(Box<Expr>, Symbol, Vec<Expr>),
|
||||
Pipe(Box<Expr>, Box<Expr>),
|
||||
|
||||
Block(Vec<Expr>),
|
||||
List(Vec<Expr>),
|
||||
Table(Vec<(Expr, Expr)>),
|
||||
|
||||
Return(Box<Expr>),
|
||||
And(Box<Expr>, Box<Expr>),
|
||||
Or(Box<Expr>, Box<Expr>),
|
||||
If(Box<Expr>, Box<Expr>, Option<Box<Expr>>),
|
||||
While(Box<Expr>, Box<Expr>),
|
||||
For(Symbol, Box<Expr>, Box<Expr>),
|
||||
Lambda(Vec<Symbol>, Box<Expr>),
|
||||
Try(Box<Expr>, Vec<CatchBlock>),
|
||||
}
|
||||
|
||||
impl ExprKind {
|
||||
pub fn span(self, span: Span) -> Expr {
|
||||
Expr { kind: self, span }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CatchBlock {
|
||||
pub span: Span,
|
||||
pub name: Option<Symbol>,
|
||||
pub types: Option<Vec<Symbol>>,
|
||||
pub body: Expr,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LValue {
|
||||
pub span: Span,
|
||||
pub kind: LValueKind,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LValueKind {
|
||||
Ident(Symbol),
|
||||
Index(Box<Expr>, Box<Expr>),
|
||||
}
|
||||
|
||||
impl LValueKind {
|
||||
pub fn span(self, span: Span) -> LValue {
|
||||
LValue { kind: self, span }
|
||||
}
|
||||
}
|
||||
|
||||
impl LValue {
|
||||
pub fn from_expr(e: Expr) -> Option<LValue> {
|
||||
let Expr { span, kind } = e;
|
||||
match kind {
|
||||
ExprKind::Ident(i) => Some(LValueKind::Ident(i).span(span)),
|
||||
ExprKind::Index(l, r) => Some(LValueKind::Index(l, r).span(span)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CatchBlock {
|
||||
pub fn write_to(&self, w: &mut impl fmt::Write, depth: usize) -> fmt::Result {
|
||||
write!(w, "{0: >1$}catch", "", depth * 2)?;
|
||||
if let Some(name) = self.name {
|
||||
write!(w, " ${}", name.name())?;
|
||||
}
|
||||
if let Some(types) = &self.types {
|
||||
write!(w, ":")?;
|
||||
for ty in types {
|
||||
write!(w, " {}", ty.name())?;
|
||||
}
|
||||
}
|
||||
writeln!(w)?;
|
||||
self.body.write_to(w, depth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CatchBlock {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.write_to(f, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl LValue {
|
||||
pub fn write_to(&self, w: &mut impl fmt::Write, depth: usize) -> fmt::Result {
|
||||
write!(w, "{0: >1$}", "", depth * 2)?;
|
||||
let depth = depth + 1;
|
||||
match &self.kind {
|
||||
LValueKind::Ident(n) => writeln!(w, "${}", n.name()),
|
||||
LValueKind::Index(l, r) => {
|
||||
writeln!(w, "index")?;
|
||||
l.write_to(w, depth)?;
|
||||
r.write_to(w, depth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.write_to(f, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
pub fn write_to(&self, w: &mut impl fmt::Write, depth: usize) -> fmt::Result {
|
||||
write!(w, "{0: >1$}", "", depth * 2)?;
|
||||
let depth = depth + 1;
|
||||
match &self.kind {
|
||||
ExprKind::Literal(val) => writeln!(w, "{val}"),
|
||||
ExprKind::Ident(n) => writeln!(w, "${}", n.name()),
|
||||
ExprKind::UnaryOp(op, e) => {
|
||||
writeln!(w, "uop {op:?}")?;
|
||||
e.write_to(w, depth)
|
||||
}
|
||||
ExprKind::BinaryOp(op, l, r) => {
|
||||
writeln!(w, "bop {op:?}")?;
|
||||
l.write_to(w, depth)?;
|
||||
r.write_to(w, depth)
|
||||
}
|
||||
ExprKind::Assign(op, l, r) => {
|
||||
if let Some(op) = op {
|
||||
writeln!(w, "asgn {op:?}")?;
|
||||
} else {
|
||||
writeln!(w, "asgn =")?;
|
||||
}
|
||||
l.write_to(w, depth)?;
|
||||
r.write_to(w, depth)
|
||||
}
|
||||
ExprKind::AssignVar(l, r) => {
|
||||
writeln!(w, "var {}", l.name())?;
|
||||
r.write_to(w, depth)
|
||||
}
|
||||
ExprKind::AssignGlobal(l, r) => {
|
||||
writeln!(w, "global {}", l.name())?;
|
||||
r.write_to(w, depth)
|
||||
}
|
||||
ExprKind::FnDef(n, p, b) => {
|
||||
if let Some(n) = n {
|
||||
writeln!(w, "fndef ${}", n.name())?;
|
||||
} else {
|
||||
writeln!(w, "fndef anon")?;
|
||||
}
|
||||
for arg in p {
|
||||
writeln!(w, " ${}", arg.name())?;
|
||||
}
|
||||
b.write_to(w, depth)
|
||||
}
|
||||
ExprKind::Index(l, r) => {
|
||||
writeln!(w, "index")?;
|
||||
l.write_to(w, depth)?;
|
||||
r.write_to(w, depth)
|
||||
}
|
||||
ExprKind::FnCall(f, a) => {
|
||||
writeln!(w, "call")?;
|
||||
f.write_to(w, depth)?;
|
||||
for arg in a {
|
||||
arg.write_to(w, depth)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ExprKind::AssocFnCall(d, f, a) => {
|
||||
writeln!(w, "assoc call {}", f.name())?;
|
||||
d.write_to(w, depth)?;
|
||||
for arg in a {
|
||||
arg.write_to(w, depth)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ExprKind::Pipe(l, r) => {
|
||||
writeln!(w, "pipe")?;
|
||||
l.write_to(w, depth)?;
|
||||
r.write_to(w, depth)
|
||||
}
|
||||
ExprKind::Block(b) => {
|
||||
writeln!(w, "block")?;
|
||||
for e in b {
|
||||
e.write_to(w, depth)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ExprKind::List(l) => {
|
||||
writeln!(w, "list")?;
|
||||
for e in l {
|
||||
e.write_to(w, depth)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ExprKind::Table(t) => {
|
||||
writeln!(w, "list")?;
|
||||
for (k, v) in t {
|
||||
k.write_to(w, depth)?;
|
||||
v.write_to(w, depth)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ExprKind::Return(e) => {
|
||||
writeln!(w, "return")?;
|
||||
e.write_to(w, depth)
|
||||
}
|
||||
ExprKind::And(l, r) => {
|
||||
writeln!(w, "and")?;
|
||||
l.write_to(w, depth)?;
|
||||
r.write_to(w, depth)
|
||||
}
|
||||
ExprKind::Or(l, r) => {
|
||||
writeln!(w, "or")?;
|
||||
l.write_to(w, depth)?;
|
||||
r.write_to(w, depth)
|
||||
}
|
||||
ExprKind::If(c, b, e) => {
|
||||
writeln!(w, "if")?;
|
||||
c.write_to(w, depth)?;
|
||||
b.write_to(w, depth)?;
|
||||
if let Some(e) = e {
|
||||
e.write_to(w, depth)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ExprKind::While(c, b) => {
|
||||
writeln!(w, "while")?;
|
||||
c.write_to(w, depth)?;
|
||||
b.write_to(w, depth)
|
||||
}
|
||||
ExprKind::For(v, i, b) => {
|
||||
writeln!(w, "for {}", v.name())?;
|
||||
i.write_to(w, depth)?;
|
||||
b.write_to(w, depth)
|
||||
}
|
||||
ExprKind::Lambda(a, b) => {
|
||||
write!(w, "lambda")?;
|
||||
for arg in a {
|
||||
write!(w, " {}", arg.name())?;
|
||||
}
|
||||
writeln!(w)?;
|
||||
b.write_to(w, depth)
|
||||
}
|
||||
ExprKind::Try(t, c) => {
|
||||
write!(w, "try")?;
|
||||
t.write_to(w, depth)?;
|
||||
for catch in c {
|
||||
catch.write_to(w, depth)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Expr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.write_to(f, 0)
|
||||
}
|
||||
}
|
602
talc-lang/src/parser/lexer.rs
Normal file
602
talc-lang/src/parser/lexer.rs
Normal file
|
@ -0,0 +1,602 @@
|
|||
use std::{fmt, iter::Peekable, str::Chars};
|
||||
|
||||
use unicode_ident::{is_xid_continue, is_xid_start};
|
||||
|
||||
use super::{ParserError, Pos, Span};
|
||||
|
||||
type Result<T> = std::result::Result<T, ParserError>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum TokenKind {
|
||||
Eof,
|
||||
LineSeparator,
|
||||
|
||||
Bang,
|
||||
BangEqual,
|
||||
HashAmper,
|
||||
HashAmperEqual,
|
||||
HashCaret,
|
||||
HashCaretEqual,
|
||||
HashPipe,
|
||||
HashPipeEqual,
|
||||
Dollar,
|
||||
Percent,
|
||||
PercentEqual,
|
||||
Amper,
|
||||
AmperEqual,
|
||||
Star,
|
||||
StarEqual,
|
||||
Plus,
|
||||
PlusPlus,
|
||||
PlusPlusEqual,
|
||||
PlusEqual,
|
||||
Comma,
|
||||
Minus,
|
||||
MinusEqual,
|
||||
Arrow,
|
||||
Dot,
|
||||
DotDot,
|
||||
DotDotStar,
|
||||
DotDotEqual,
|
||||
Slash,
|
||||
SlashSlash,
|
||||
SlashSlashEqual,
|
||||
SlashEqual,
|
||||
Colon,
|
||||
Less,
|
||||
LessLess,
|
||||
LessLessEqual,
|
||||
LessEqual,
|
||||
Equal,
|
||||
EqualEqual,
|
||||
Greater,
|
||||
GreaterEqual,
|
||||
GreaterGreater,
|
||||
GreaterGreaterEqual,
|
||||
LParen,
|
||||
RParen,
|
||||
LBrack,
|
||||
RBrack,
|
||||
LBrace,
|
||||
RBrace,
|
||||
Backslash,
|
||||
Caret,
|
||||
CaretEqual,
|
||||
Pipe,
|
||||
|
||||
Identifier,
|
||||
Integer,
|
||||
Float,
|
||||
Imaginary,
|
||||
String,
|
||||
Symbol,
|
||||
And,
|
||||
Break,
|
||||
Catch,
|
||||
Continue,
|
||||
Do,
|
||||
Elif,
|
||||
Else,
|
||||
End,
|
||||
False,
|
||||
Fn,
|
||||
For,
|
||||
Global,
|
||||
If,
|
||||
In,
|
||||
Nil,
|
||||
Not,
|
||||
Or,
|
||||
Return,
|
||||
Then,
|
||||
True,
|
||||
Try,
|
||||
Var,
|
||||
While,
|
||||
}
|
||||
use TokenKind as K;
|
||||
|
||||
impl TokenKind {
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
K::Eof => "end of file",
|
||||
K::LineSeparator => "line separator",
|
||||
K::Bang => "'!'",
|
||||
K::BangEqual => "'!='",
|
||||
K::HashAmper => "'#&'",
|
||||
K::HashAmperEqual => "'#&='",
|
||||
K::HashCaret => "'#^'",
|
||||
K::HashCaretEqual => "'#^='",
|
||||
K::HashPipe => "'#|'",
|
||||
K::HashPipeEqual => "'#|='",
|
||||
K::Dollar => "'$'",
|
||||
K::Percent => "'%'",
|
||||
K::PercentEqual => "'%='",
|
||||
K::Amper => "'&'",
|
||||
K::AmperEqual => "'&='",
|
||||
K::Star => "'*'",
|
||||
K::StarEqual => "'*='",
|
||||
K::Plus => "'+'",
|
||||
K::PlusPlus => "'++'",
|
||||
K::PlusPlusEqual => "'++='",
|
||||
K::PlusEqual => "'+='",
|
||||
K::Comma => "','",
|
||||
K::Minus => "'-'",
|
||||
K::MinusEqual => "'-='",
|
||||
K::Arrow => "'=>'",
|
||||
K::Dot => "'.'",
|
||||
K::DotDot => "'..'",
|
||||
K::DotDotStar => "'..*'",
|
||||
K::DotDotEqual => "'..='",
|
||||
K::Slash => "'/'",
|
||||
K::SlashSlash => "'//'",
|
||||
K::SlashSlashEqual => "'//='",
|
||||
K::SlashEqual => "'/='",
|
||||
K::Colon => "':'",
|
||||
K::Less => "'<'",
|
||||
K::LessLess => "'<<'",
|
||||
K::LessLessEqual => "'<<='",
|
||||
K::LessEqual => "'<='",
|
||||
K::Equal => "'='",
|
||||
K::EqualEqual => "'=='",
|
||||
K::Greater => "'>'",
|
||||
K::GreaterEqual => "'>='",
|
||||
K::GreaterGreater => "'>>'",
|
||||
K::GreaterGreaterEqual => "'>>='",
|
||||
K::LParen => "'('",
|
||||
K::RParen => "')'",
|
||||
K::LBrack => "'['",
|
||||
K::RBrack => "']'",
|
||||
K::LBrace => "'{'",
|
||||
K::RBrace => "'}'",
|
||||
K::Backslash => "'\\'",
|
||||
K::Caret => "'^'",
|
||||
K::CaretEqual => "'^='",
|
||||
K::Pipe => "'|'",
|
||||
K::Identifier => "identifier",
|
||||
K::Integer => "integer",
|
||||
K::Float => "float",
|
||||
K::Imaginary => "imaginary",
|
||||
K::String => "string",
|
||||
K::Symbol => "symbol",
|
||||
K::And => "'and'",
|
||||
K::Break => "'break'",
|
||||
K::Catch => "'catch'",
|
||||
K::Continue => "'continue'",
|
||||
K::Do => "'do'",
|
||||
K::Elif => "'elif'",
|
||||
K::Else => "'else'",
|
||||
K::End => "'end'",
|
||||
K::False => "'false'",
|
||||
K::Fn => "'fn'",
|
||||
K::For => "'for'",
|
||||
K::Global => "'global'",
|
||||
K::If => "'if'",
|
||||
K::In => "'in'",
|
||||
K::Nil => "'nil'",
|
||||
K::Not => "'not'",
|
||||
K::Or => "'or'",
|
||||
K::Return => "'return'",
|
||||
K::Then => "'then'",
|
||||
K::True => "'true'",
|
||||
K::Try => "'try'",
|
||||
K::Var => "'var'",
|
||||
K::While => "'while'",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TokenKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(self.name())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Token<'s> {
|
||||
pub span: Span,
|
||||
pub content: &'s str,
|
||||
pub kind: TokenKind,
|
||||
}
|
||||
|
||||
pub struct Lexer<'s> {
|
||||
src: &'s str,
|
||||
chars: Peekable<Chars<'s>>,
|
||||
start_pos: Pos,
|
||||
pos: Pos,
|
||||
}
|
||||
|
||||
impl<'s> Lexer<'s> {
|
||||
pub fn new(src: &'s str) -> Self {
|
||||
Self {
|
||||
src,
|
||||
chars: src.chars().peekable(),
|
||||
start_pos: Pos::new(),
|
||||
pos: Pos::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid_char(&self, c: char) -> ParserError {
|
||||
let span = Span::new(self.pos, self.pos.advance(c));
|
||||
let msg = match c as u32 {
|
||||
c @ 0x00..=0x7f => format!("invalid character (codepoint 0x{:2x})", c),
|
||||
c => format!("invalid character (codepoint U+{:04x})", c),
|
||||
};
|
||||
ParserError { span, msg }
|
||||
}
|
||||
|
||||
fn filter_char(&mut self, c: Option<char>, advance: bool) -> Result<char> {
|
||||
match c {
|
||||
Some(c) if c.is_control() && !matches!(c, '\n' | '\r' | '\t') => {
|
||||
Err(self.invalid_char(c))
|
||||
}
|
||||
Some(c) => {
|
||||
if advance {
|
||||
self.pos = self.pos.advance(c);
|
||||
}
|
||||
Ok(c)
|
||||
}
|
||||
None => Ok('\0'),
|
||||
}
|
||||
}
|
||||
|
||||
fn peek(&mut self) -> Result<char> {
|
||||
let c = self.chars.peek().copied();
|
||||
self.filter_char(c, false)
|
||||
}
|
||||
|
||||
fn peek_n(&mut self, n: usize) -> Option<char> {
|
||||
let chars = &self.src[(self.pos.idx as usize)..];
|
||||
chars.chars().nth(n)
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Result<char> {
|
||||
let c = self.chars.next();
|
||||
self.filter_char(c, true)
|
||||
}
|
||||
|
||||
fn and_peek(&mut self) -> Result<char> {
|
||||
self.next()?;
|
||||
self.peek()
|
||||
}
|
||||
|
||||
fn emit(&self, kind: TokenKind) -> Result<Token<'s>> {
|
||||
let span = Span::new(self.start_pos, self.pos);
|
||||
Ok(Token {
|
||||
span,
|
||||
content: span.of(self.src),
|
||||
kind,
|
||||
})
|
||||
}
|
||||
|
||||
fn and_emit(&mut self, kind: TokenKind) -> Result<Token<'s>> {
|
||||
self.next()?;
|
||||
self.emit(kind)
|
||||
}
|
||||
|
||||
fn unexpected(&mut self) -> Result<Token<'s>> {
|
||||
let c = self.peek()?;
|
||||
let span = Span::new(self.pos, self.pos.advance(c));
|
||||
let msg = match c {
|
||||
'\0' => "unexpected end of file".to_owned(),
|
||||
'\n' => "unexpected newline character".to_owned(),
|
||||
'\t' => "unexpected tab character".to_owned(),
|
||||
'\r' => "unexpected return character".to_owned(),
|
||||
c => format!("unexpected character {c}"),
|
||||
};
|
||||
Err(ParserError { span, msg })
|
||||
}
|
||||
|
||||
fn next_ident(&mut self) -> Result<Token<'s>> {
|
||||
self.next()?;
|
||||
while is_xid_continue(self.peek()?) {
|
||||
self.next()?;
|
||||
}
|
||||
let kind = match Span::new(self.start_pos, self.pos).of(self.src) {
|
||||
"and" => K::And,
|
||||
"break" => K::Break,
|
||||
"catch" => K::Catch,
|
||||
"continue" => K::Continue,
|
||||
"do" => K::Do,
|
||||
"elif" => K::Elif,
|
||||
"else" => K::Else,
|
||||
"end" => K::End,
|
||||
"false" => K::False,
|
||||
"fn" => K::Fn,
|
||||
"for" => K::For,
|
||||
"global" => K::Global,
|
||||
"if" => K::If,
|
||||
"in" => K::In,
|
||||
"nil" => K::Nil,
|
||||
"not" => K::Not,
|
||||
"or" => K::Or,
|
||||
"return" => K::Return,
|
||||
"then" => K::Then,
|
||||
"true" => K::True,
|
||||
"try" => K::Try,
|
||||
"var" => K::Var,
|
||||
"while" => K::While,
|
||||
_ => K::Identifier,
|
||||
};
|
||||
self.emit(kind)
|
||||
}
|
||||
|
||||
fn next_int_base(&mut self, radix: u32) -> Result<Token<'s>> {
|
||||
loop {
|
||||
let c = self.peek()?;
|
||||
if c == '_' || c.is_digit(radix) {
|
||||
self.next()?;
|
||||
} else if is_xid_start(c) {
|
||||
return self.unexpected()
|
||||
} else {
|
||||
return self.emit(K::Integer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_imag(&mut self) -> Result<Token<'s>> {
|
||||
self.next()?;
|
||||
if is_xid_start(self.peek()?) {
|
||||
self.unexpected()
|
||||
} else {
|
||||
self.emit(K::Imaginary)
|
||||
}
|
||||
}
|
||||
|
||||
fn next_float(&mut self, mut has_e: bool) -> Result<Token<'s>> {
|
||||
while !has_e {
|
||||
while matches!(self.peek()?, '_' | '0'..='9') {
|
||||
self.next()?;
|
||||
}
|
||||
match self.peek()? {
|
||||
'e' => {
|
||||
self.next()?;
|
||||
has_e = true;
|
||||
}
|
||||
'i' => return self.next_imag(),
|
||||
c if is_xid_start(c) => return self.unexpected(),
|
||||
_ => return self.emit(K::Float),
|
||||
}
|
||||
}
|
||||
if matches!(self.peek()?, '+' | '-') {
|
||||
self.next()?;
|
||||
}
|
||||
while matches!(self.peek()?, '_' | '0'..='9') {
|
||||
self.next()?;
|
||||
}
|
||||
match self.peek()? {
|
||||
'i' => self.next_imag(),
|
||||
c if is_xid_start(c) => self.unexpected(),
|
||||
_ => self.emit(K::Float),
|
||||
}
|
||||
}
|
||||
|
||||
fn next_number(&mut self) -> Result<Token<'s>> {
|
||||
if self.next()? == '0' {
|
||||
while self.peek()? == '_' {
|
||||
self.next()?;
|
||||
}
|
||||
match self.peek()? {
|
||||
'x' => {
|
||||
self.next()?;
|
||||
return self.next_int_base(16)
|
||||
}
|
||||
'o' => {
|
||||
self.next()?;
|
||||
return self.next_int_base(8)
|
||||
}
|
||||
's' => {
|
||||
self.next()?;
|
||||
return self.next_int_base(6)
|
||||
}
|
||||
'b' => {
|
||||
self.next()?;
|
||||
return self.next_int_base(2)
|
||||
}
|
||||
'0'..='9' | '.' | 'e' | 'i' => (),
|
||||
c if is_xid_start(c) => return self.unexpected(),
|
||||
_ => return self.emit(K::Integer),
|
||||
}
|
||||
}
|
||||
while matches!(self.peek()?, '_' | '0'..='9') {
|
||||
self.next()?;
|
||||
}
|
||||
match self.peek()? {
|
||||
'r' => todo!("arbitrary radix integer literals"),
|
||||
'e' => {
|
||||
self.next()?;
|
||||
self.next_float(true)
|
||||
}
|
||||
'i' => self.next_imag(),
|
||||
'.' => {
|
||||
if self.peek_n(1) == Some('.') {
|
||||
self.emit(K::Integer)
|
||||
} else {
|
||||
self.next()?;
|
||||
self.next_float(false)
|
||||
}
|
||||
}
|
||||
c if is_xid_start(c) => self.unexpected(),
|
||||
_ => self.emit(K::Integer),
|
||||
}
|
||||
}
|
||||
|
||||
fn next_string(&mut self) -> Result<Token<'s>> {
|
||||
let double_quote = self.next()? == '"';
|
||||
loop {
|
||||
match self.next()? {
|
||||
'\0' => return self.unexpected(),
|
||||
'"' if double_quote => break,
|
||||
'\'' if !double_quote => break,
|
||||
'\\' if double_quote => {
|
||||
self.next()?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
self.emit(K::String)
|
||||
}
|
||||
|
||||
fn next_symbol(&mut self) -> Result<Token<'s>> {
|
||||
if matches!(self.peek()?, '\'' | '"') {
|
||||
self.next_string()?;
|
||||
} else {
|
||||
self.next_ident()?;
|
||||
}
|
||||
self.emit(K::Symbol)
|
||||
}
|
||||
|
||||
fn line_comment(&mut self) -> Result<Token<'s>> {
|
||||
while !matches!(self.peek()?, '\0' | '\n') {
|
||||
self.next()?;
|
||||
}
|
||||
self.next_token()
|
||||
}
|
||||
|
||||
fn next_token(&mut self) -> Result<Token<'s>> {
|
||||
while matches!(self.peek()?, ' ' | '\t' | '\r') {
|
||||
self.next()?;
|
||||
}
|
||||
self.start_pos = self.pos;
|
||||
match self.peek()? {
|
||||
// misc
|
||||
'\0' => self.emit(K::Eof),
|
||||
';' | '\n' => self.and_emit(K::LineSeparator),
|
||||
'(' => self.and_emit(K::LParen),
|
||||
')' => self.and_emit(K::RParen),
|
||||
'{' => self.and_emit(K::LBrace),
|
||||
'}' => self.and_emit(K::RBrace),
|
||||
'[' => self.and_emit(K::LBrack),
|
||||
']' => self.and_emit(K::RBrack),
|
||||
',' => self.and_emit(K::Comma),
|
||||
'$' => self.and_emit(K::Dollar),
|
||||
'|' => self.and_emit(K::Pipe),
|
||||
'\\' => match self.and_peek()? {
|
||||
'\n' => {
|
||||
self.next()?;
|
||||
self.next_token()
|
||||
}
|
||||
_ => self.emit(K::Backslash),
|
||||
},
|
||||
// arithmetic
|
||||
'+' => match self.and_peek()? {
|
||||
'+' => match self.and_peek()? {
|
||||
'=' => self.and_emit(K::PlusPlusEqual),
|
||||
_ => self.emit(K::PlusPlus),
|
||||
},
|
||||
'=' => self.and_emit(K::PlusEqual),
|
||||
_ => self.emit(K::Plus),
|
||||
},
|
||||
'-' => match self.and_peek()? {
|
||||
'-' => self.line_comment(),
|
||||
'=' => self.and_emit(K::MinusEqual),
|
||||
'>' => self.and_emit(K::Arrow),
|
||||
_ => self.emit(K::Minus),
|
||||
},
|
||||
'*' => match self.and_peek()? {
|
||||
'=' => self.and_emit(K::StarEqual),
|
||||
_ => self.emit(K::Star),
|
||||
},
|
||||
'/' => match self.and_peek()? {
|
||||
'/' => match self.and_peek()? {
|
||||
'=' => self.and_emit(K::SlashSlashEqual),
|
||||
_ => self.emit(K::SlashSlash),
|
||||
},
|
||||
'=' => self.and_emit(K::SlashEqual),
|
||||
_ => self.emit(K::Slash),
|
||||
},
|
||||
'%' => match self.and_peek()? {
|
||||
'=' => self.and_emit(K::PercentEqual),
|
||||
_ => self.emit(K::Percent),
|
||||
},
|
||||
'^' => match self.and_peek()? {
|
||||
'=' => self.and_emit(K::CaretEqual),
|
||||
_ => self.emit(K::Caret),
|
||||
},
|
||||
// logical
|
||||
'#' => match self.and_peek()? {
|
||||
'&' => match self.and_peek()? {
|
||||
'=' => self.and_emit(K::HashAmperEqual),
|
||||
_ => self.emit(K::HashAmper),
|
||||
},
|
||||
'^' => match self.and_peek()? {
|
||||
'=' => self.and_emit(K::HashCaretEqual),
|
||||
_ => self.emit(K::HashCaret),
|
||||
},
|
||||
'|' => match self.and_peek()? {
|
||||
'=' => self.and_emit(K::HashPipeEqual),
|
||||
_ => self.emit(K::HashPipe),
|
||||
},
|
||||
'!' => self.line_comment(),
|
||||
_ => self.unexpected(),
|
||||
},
|
||||
// lists
|
||||
'!' => match self.and_peek()? {
|
||||
'=' => self.and_emit(K::BangEqual),
|
||||
_ => self.emit(K::Bang),
|
||||
},
|
||||
'&' => match self.and_peek()? {
|
||||
'=' => self.and_emit(K::AmperEqual),
|
||||
_ => self.emit(K::Amper),
|
||||
},
|
||||
// comparison
|
||||
'<' => match self.and_peek()? {
|
||||
'<' => match self.and_peek()? {
|
||||
'=' => self.and_emit(K::LessLessEqual),
|
||||
_ => self.emit(K::LessLess),
|
||||
},
|
||||
'=' => self.and_emit(K::LessEqual),
|
||||
_ => self.emit(K::Less),
|
||||
},
|
||||
'>' => match self.and_peek()? {
|
||||
'>' => match self.and_peek()? {
|
||||
'=' => self.and_emit(K::GreaterGreaterEqual),
|
||||
_ => self.emit(K::GreaterGreater),
|
||||
},
|
||||
'=' => self.and_emit(K::GreaterEqual),
|
||||
_ => self.emit(K::Greater),
|
||||
},
|
||||
'=' => match self.and_peek()? {
|
||||
'=' => self.and_emit(K::EqualEqual),
|
||||
_ => self.emit(K::Equal),
|
||||
},
|
||||
// range
|
||||
'.' => match self.and_peek()? {
|
||||
'.' => match self.and_peek()? {
|
||||
'*' => self.and_emit(K::DotDotStar),
|
||||
'=' => self.and_emit(K::DotDotEqual),
|
||||
_ => self.emit(K::DotDot),
|
||||
},
|
||||
_ => self.emit(K::Dot),
|
||||
},
|
||||
':' => match self.and_peek()? {
|
||||
c if is_xid_start(c) || c == '"' || c == '\'' => self.next_symbol(),
|
||||
_ => self.emit(K::Colon),
|
||||
},
|
||||
'0'..='9' => self.next_number(),
|
||||
c if is_xid_start(c) => self.next_ident(),
|
||||
'"' | '\'' => self.next_string(),
|
||||
_ => self.unexpected(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tokens(mut self) -> Result<Vec<Token<'s>>> {
|
||||
let mut res = Vec::new();
|
||||
loop {
|
||||
let t = self.next_token()?;
|
||||
let k = t.kind;
|
||||
res.push(t);
|
||||
if k == TokenKind::Eof {
|
||||
return Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> Iterator for Lexer<'s> {
|
||||
type Item = Result<Token<'s>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
Some(self.next_token())
|
||||
}
|
||||
}
|
14
talc-lang/src/parser/mod.rs
Normal file
14
talc-lang/src/parser/mod.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
pub mod ast;
|
||||
|
||||
mod lexer;
|
||||
pub use lexer::*;
|
||||
|
||||
#[expect(clippy::module_inception)]
|
||||
mod parser;
|
||||
pub use parser::*;
|
||||
|
||||
mod pos;
|
||||
pub use pos::*;
|
||||
|
||||
mod util;
|
||||
pub use util::*;
|
663
talc-lang/src/parser/parser.rs
Normal file
663
talc-lang/src/parser/parser.rs
Normal file
|
@ -0,0 +1,663 @@
|
|||
use std::iter::Peekable;
|
||||
|
||||
use crate::{
|
||||
symbol::{Symbol, SYM_DOLLAR_SIGN},
|
||||
value::Value,
|
||||
};
|
||||
|
||||
use super::{
|
||||
ast::{BinaryOp, CatchBlock, Expr, ExprKind, LValue, UnaryOp},
|
||||
parse_float, parse_int_literal, parse_str_escapes, Lexer, ParserError, Span,
|
||||
SpanParserError, Token, TokenKind,
|
||||
};
|
||||
use num_complex::Complex64;
|
||||
use ExprKind as E;
|
||||
use TokenKind as T;
|
||||
|
||||
type Result<T> = std::result::Result<T, ParserError>;
|
||||
|
||||
macro_rules! expect {
|
||||
($self:expr, $($t:tt)*) => {{
|
||||
let t = $self.next()?;
|
||||
match t.kind {
|
||||
$($t)* => t,
|
||||
e => return Err(ParserError { span: t.span, msg: expect_inner!(e, $($t)*) })
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! expect_inner {
|
||||
($e:expr, $($tok:path)|*) => {
|
||||
{
|
||||
let mut s = format!("unexpected token {}, expected ", $e.name())
|
||||
+ $($tok.name() + ", " +)* "";
|
||||
s.truncate(s.len() - 2);
|
||||
s
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! try_next {
|
||||
($self:expr, $pat:pat) => {{
|
||||
let t = $self.peek()?;
|
||||
match t.kind {
|
||||
$pat => Some($self.next()?),
|
||||
_ => None,
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! throw {
|
||||
($span:expr, $($t:tt)*) => {
|
||||
return Err(ParserError { span: $span, msg: format!($($t)*) })
|
||||
};
|
||||
}
|
||||
|
||||
impl TokenKind {
|
||||
pub fn assign_op(self) -> Option<Option<BinaryOp>> {
|
||||
Some(match self {
|
||||
T::PlusPlusEqual => Some(BinaryOp::Concat),
|
||||
T::AmperEqual => Some(BinaryOp::Append),
|
||||
T::HashPipeEqual => Some(BinaryOp::BitOr),
|
||||
T::HashCaretEqual => Some(BinaryOp::BitXor),
|
||||
T::HashAmperEqual => Some(BinaryOp::BitAnd),
|
||||
T::LessLessEqual => Some(BinaryOp::Shl),
|
||||
T::GreaterGreaterEqual => Some(BinaryOp::Shr),
|
||||
T::PlusEqual => Some(BinaryOp::Add),
|
||||
T::MinusEqual => Some(BinaryOp::Sub),
|
||||
T::StarEqual => Some(BinaryOp::Mul),
|
||||
T::SlashEqual => Some(BinaryOp::Div),
|
||||
T::SlashSlashEqual => Some(BinaryOp::IntDiv),
|
||||
T::PercentEqual => Some(BinaryOp::Mod),
|
||||
T::CaretEqual => Some(BinaryOp::Pow),
|
||||
T::Equal => None,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn binary_op(self) -> Option<BinaryOp> {
|
||||
Some(match self {
|
||||
T::EqualEqual => BinaryOp::Eq,
|
||||
T::BangEqual => BinaryOp::Ne,
|
||||
T::Greater => BinaryOp::Gt,
|
||||
T::GreaterEqual => BinaryOp::Ge,
|
||||
T::Less => BinaryOp::Lt,
|
||||
T::LessEqual => BinaryOp::Le,
|
||||
T::PlusPlus => BinaryOp::Concat,
|
||||
T::Amper => BinaryOp::Append,
|
||||
T::DotDot => BinaryOp::Range,
|
||||
T::DotDotEqual => BinaryOp::RangeIncl,
|
||||
T::HashPipe => BinaryOp::BitOr,
|
||||
T::HashCaret => BinaryOp::BitXor,
|
||||
T::HashAmper => BinaryOp::BitAnd,
|
||||
T::LessLess => BinaryOp::Shl,
|
||||
T::GreaterGreater => BinaryOp::Shr,
|
||||
T::Plus => BinaryOp::Add,
|
||||
T::Minus => BinaryOp::Sub,
|
||||
T::Star => BinaryOp::Mul,
|
||||
T::Slash => BinaryOp::Div,
|
||||
T::SlashSlash => BinaryOp::IntDiv,
|
||||
T::Percent => BinaryOp::Mod,
|
||||
T::Caret => BinaryOp::Pow,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unary_op(self) -> Option<UnaryOp> {
|
||||
match self {
|
||||
T::Minus => Some(UnaryOp::Neg),
|
||||
T::Not => Some(UnaryOp::Not),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn postfix_unary_op(self) -> Option<UnaryOp> {
|
||||
match self {
|
||||
T::DotDotStar => Some(UnaryOp::RangeEndless),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UnaryOp {
|
||||
pub fn precedence(self) -> u8 {
|
||||
match self {
|
||||
UnaryOp::Not => 0,
|
||||
UnaryOp::RangeEndless => 40,
|
||||
UnaryOp::Neg => 110,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryOp {
|
||||
pub fn precedence(self) -> (u8, u8) {
|
||||
match self {
|
||||
BinaryOp::Eq
|
||||
| BinaryOp::Ne
|
||||
| BinaryOp::Gt
|
||||
| BinaryOp::Ge
|
||||
| BinaryOp::Lt
|
||||
| BinaryOp::Le => (10, 10),
|
||||
BinaryOp::Concat => (20, 25),
|
||||
BinaryOp::Append => (30, 35),
|
||||
BinaryOp::Range | BinaryOp::RangeIncl => (40, 40),
|
||||
BinaryOp::BitOr => (50, 55),
|
||||
BinaryOp::BitXor => (60, 65),
|
||||
BinaryOp::BitAnd => (70, 75),
|
||||
BinaryOp::Shl | BinaryOp::Shr => (80, 85),
|
||||
BinaryOp::Add | BinaryOp::Sub => (90, 95),
|
||||
BinaryOp::Mul | BinaryOp::Div | BinaryOp::IntDiv | BinaryOp::Mod => (100, 105),
|
||||
BinaryOp::Pow => (125, 120),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn b<T>(t: T) -> Box<T> {
|
||||
Box::new(t)
|
||||
}
|
||||
|
||||
impl TokenKind {
|
||||
fn expr_first(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
T::Return
|
||||
| T::Var | T::Global
|
||||
| T::Fn | T::Not
|
||||
| T::Backslash
|
||||
| T::Colon | T::Minus
|
||||
| T::Identifier
|
||||
| T::LParen | T::LBrack
|
||||
| T::LBrace | T::Dollar
|
||||
| T::Do | T::If
|
||||
| T::While | T::For
|
||||
| T::Try | T::Integer
|
||||
| T::Float | T::Imaginary
|
||||
| T::String | T::Symbol
|
||||
| T::True | T::False
|
||||
| T::Nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct Parser<'s> {
|
||||
lexer: Peekable<Lexer<'s>>,
|
||||
}
|
||||
|
||||
impl<'s> Parser<'s> {
|
||||
fn new(src: &'s str) -> Self {
|
||||
Self {
|
||||
lexer: Lexer::new(src).peekable(),
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Result<Token<'s>> {
|
||||
self.lexer.next().unwrap()
|
||||
}
|
||||
|
||||
fn peek(&mut self) -> Result<Token<'s>> {
|
||||
self.lexer.peek().unwrap().clone()
|
||||
}
|
||||
|
||||
fn parse_table_items(&mut self) -> Result<Vec<(Expr, Expr)>> {
|
||||
let mut items = Vec::new();
|
||||
while self.peek()?.kind.expr_first() {
|
||||
let key = if let Some(id) = try_next!(self, T::Identifier) {
|
||||
E::Literal(Symbol::get(id.content).into()).span(id.span)
|
||||
} else {
|
||||
self.parse_term_not_ident()?
|
||||
};
|
||||
|
||||
expect!(self, T::Equal);
|
||||
|
||||
let value = self.parse_expr()?;
|
||||
|
||||
items.push((key, value));
|
||||
|
||||
if try_next!(self, T::Comma).is_none() {
|
||||
break
|
||||
}
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
fn parse_expr_list(&mut self) -> Result<Vec<Expr>> {
|
||||
let mut exprs = Vec::new();
|
||||
while self.peek()?.kind.expr_first() {
|
||||
exprs.push(self.parse_expr()?);
|
||||
if try_next!(self, T::Comma).is_none() {
|
||||
break
|
||||
}
|
||||
}
|
||||
Ok(exprs)
|
||||
}
|
||||
|
||||
fn parse_ident_list(&mut self) -> Result<Vec<Symbol>> {
|
||||
let mut idents = Vec::new();
|
||||
while let Some(tok) = try_next!(self, T::Identifier) {
|
||||
idents.push(Symbol::get(tok.content));
|
||||
if try_next!(self, T::Comma).is_none() {
|
||||
break
|
||||
}
|
||||
}
|
||||
Ok(idents)
|
||||
}
|
||||
|
||||
fn parse_symbol_list(&mut self) -> Result<Vec<Symbol>> {
|
||||
let mut syms = Vec::new();
|
||||
while let Some(tok) = try_next!(self, T::Symbol) {
|
||||
syms.push(Symbol::get(tok.content));
|
||||
if try_next!(self, T::Comma).is_none() {
|
||||
break
|
||||
}
|
||||
}
|
||||
Ok(syms)
|
||||
}
|
||||
|
||||
fn parse_catch_blocks(&mut self) -> Result<(Vec<CatchBlock>, Span)> {
|
||||
let mut blocks = Vec::new();
|
||||
let mut outer_span = self.peek()?.span;
|
||||
loop {
|
||||
let tok = expect!(self, T::Catch | T::End);
|
||||
if tok.kind == T::End {
|
||||
break
|
||||
}
|
||||
|
||||
let types = match try_next!(self, T::Star) {
|
||||
Some(_) => None,
|
||||
None => Some(self.parse_symbol_list()?),
|
||||
};
|
||||
|
||||
let name = match try_next!(self, T::In) {
|
||||
Some(_) => Some(Symbol::get(expect!(self, T::Identifier).content)),
|
||||
None => None,
|
||||
};
|
||||
|
||||
expect!(self, T::Do);
|
||||
let body = self.parse_block()?;
|
||||
|
||||
let span = tok.span + body.span;
|
||||
blocks.push(CatchBlock {
|
||||
span,
|
||||
name,
|
||||
types,
|
||||
body,
|
||||
});
|
||||
outer_span += span;
|
||||
}
|
||||
Ok((blocks, outer_span))
|
||||
}
|
||||
|
||||
fn parse_if_stmt_chain(&mut self) -> Result<Expr> {
|
||||
let cond = self.parse_expr()?;
|
||||
expect!(self, T::Then);
|
||||
let body = self.parse_block()?;
|
||||
let tok = expect!(self, T::End | T::Else | T::Elif);
|
||||
let span = cond.span + tok.span;
|
||||
match tok.kind {
|
||||
T::End => Ok(E::If(b(cond), b(body), None).span(span)),
|
||||
T::Else => {
|
||||
let else_body = self.parse_block()?;
|
||||
expect!(self, T::End);
|
||||
let span = span + else_body.span;
|
||||
Ok(E::If(b(cond), b(body), Some(b(else_body))).span(span))
|
||||
}
|
||||
T::Elif => {
|
||||
let elif_body = self.parse_if_stmt_chain()?;
|
||||
let span = span + elif_body.span;
|
||||
Ok(E::If(b(cond), b(body), Some(b(elif_body))).span(span))
|
||||
}
|
||||
_ => unreachable!("parse_if_stmt_chain: guaranteed by expect!"),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_term_not_ident(&mut self) -> Result<Expr> {
|
||||
let tok = self.next()?;
|
||||
match tok.kind {
|
||||
T::LParen => {
|
||||
let e = self.parse_expr()?;
|
||||
expect!(self, T::RParen);
|
||||
Ok(e)
|
||||
}
|
||||
T::LBrack => {
|
||||
let args = self.parse_expr_list()?;
|
||||
let end = expect!(self, T::RBrack);
|
||||
Ok(E::List(args).span(tok.span + end.span))
|
||||
}
|
||||
T::LBrace => {
|
||||
let args = self.parse_table_items()?;
|
||||
let end = expect!(self, T::RBrace);
|
||||
Ok(E::Table(args).span(tok.span + end.span))
|
||||
}
|
||||
T::Dollar => Ok(E::Ident(*SYM_DOLLAR_SIGN).span(tok.span)),
|
||||
T::Do => {
|
||||
let b = self.parse_block()?;
|
||||
expect!(self, T::End);
|
||||
Ok(b)
|
||||
}
|
||||
T::If => self.parse_if_stmt_chain(),
|
||||
T::While => {
|
||||
let cond = self.parse_expr()?;
|
||||
expect!(self, T::Do);
|
||||
let body = self.parse_block()?;
|
||||
let end = expect!(self, T::End);
|
||||
let span = cond.span + end.span;
|
||||
Ok(E::While(b(cond), b(body)).span(span))
|
||||
}
|
||||
T::For => {
|
||||
let var = expect!(self, T::Identifier);
|
||||
expect!(self, T::In);
|
||||
let iter = self.parse_expr()?;
|
||||
expect!(self, T::Do);
|
||||
let body = self.parse_block()?;
|
||||
let end = expect!(self, T::End);
|
||||
let span = var.span + end.span;
|
||||
Ok(E::For(Symbol::get(var.content), b(iter), b(body)).span(span))
|
||||
}
|
||||
T::Try => {
|
||||
let body = self.parse_block()?;
|
||||
let (catch, span) = self.parse_catch_blocks()?;
|
||||
Ok(E::Try(b(body), catch).span(tok.span + span))
|
||||
}
|
||||
T::Integer => {
|
||||
let n = parse_int_literal(tok.content).span_err(tok.span)?;
|
||||
Ok(E::Literal(n.into()).span(tok.span))
|
||||
}
|
||||
T::Float => {
|
||||
let x = parse_float(tok.content).span_err(tok.span)?;
|
||||
Ok(E::Literal(x.into()).span(tok.span))
|
||||
}
|
||||
T::Imaginary => {
|
||||
let x = parse_float(&tok.content[..tok.content.len() - 1])
|
||||
.span_err(tok.span)?;
|
||||
Ok(E::Literal(Complex64::new(0.0, x).into()).span(tok.span))
|
||||
}
|
||||
T::String => {
|
||||
let inner = &tok.content[1..tok.content.len() - 1];
|
||||
let s = if &tok.content[..1] == "\"" {
|
||||
parse_str_escapes(inner).span_err(tok.span)?
|
||||
} else {
|
||||
inner.into()
|
||||
};
|
||||
Ok(E::Literal(s.into()).span(tok.span))
|
||||
}
|
||||
T::Symbol => {
|
||||
let inner = &tok.content[1..];
|
||||
let s = match inner.chars().next() {
|
||||
Some('\'') => Symbol::get(&inner[1..inner.len() - 1]),
|
||||
Some('\"') => Symbol::get(
|
||||
&parse_str_escapes(&inner[1..inner.len() - 1])
|
||||
.span_err(tok.span)?,
|
||||
),
|
||||
_ => Symbol::get(inner),
|
||||
};
|
||||
Ok(E::Literal(s.into()).span(tok.span))
|
||||
}
|
||||
T::True => Ok(E::Literal(Value::Bool(true)).span(tok.span)),
|
||||
T::False => Ok(E::Literal(Value::Bool(false)).span(tok.span)),
|
||||
T::Nil => Ok(E::Literal(Value::Nil).span(tok.span)),
|
||||
t => throw!(
|
||||
tok.span,
|
||||
"unexpected token {}, expected expression",
|
||||
t.name()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_term(&mut self) -> Result<Expr> {
|
||||
if let Some(tok) = try_next!(self, T::Identifier) {
|
||||
Ok(E::Ident(Symbol::get(tok.content)).span(tok.span))
|
||||
} else {
|
||||
self.parse_term_not_ident()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_access(&mut self) -> Result<Expr> {
|
||||
let mut lhs = self.parse_term()?;
|
||||
loop {
|
||||
let tok = try_next!(self, T::LParen | T::LBrack | T::Arrow | T::Dot);
|
||||
match tok.map(|t| t.kind) {
|
||||
Some(T::LParen) => {
|
||||
let args = self.parse_expr_list()?;
|
||||
let end = expect!(self, T::RParen);
|
||||
let lhs_span = lhs.span;
|
||||
lhs = E::FnCall(b(lhs), args).span(lhs_span + end.span);
|
||||
}
|
||||
Some(T::LBrack) => {
|
||||
let idx = self.parse_expr()?;
|
||||
let end = expect!(self, T::RBrack);
|
||||
let lhs_span = lhs.span;
|
||||
lhs = E::Index(b(lhs), b(idx)).span(lhs_span + end.span);
|
||||
}
|
||||
Some(T::Arrow) => {
|
||||
let field = expect!(self, T::Identifier);
|
||||
let symbol = Symbol::get(field.content);
|
||||
expect!(self, T::LParen);
|
||||
let args = self.parse_expr_list()?;
|
||||
let end = expect!(self, T::RParen);
|
||||
let lhs_span = lhs.span;
|
||||
lhs = E::AssocFnCall(b(lhs), symbol, args).span(lhs_span + end.span);
|
||||
}
|
||||
Some(T::Dot) => {
|
||||
let field = expect!(self, T::Identifier);
|
||||
let symbol = Symbol::get(field.content);
|
||||
let idx = E::Literal(symbol.into()).span(field.span);
|
||||
let lhs_span = lhs.span;
|
||||
lhs = E::Index(b(lhs), b(idx)).span(lhs_span + field.span);
|
||||
}
|
||||
None => break,
|
||||
_ => unreachable!("parse_access: guaranteed by try_next!"),
|
||||
}
|
||||
}
|
||||
Ok(lhs)
|
||||
}
|
||||
|
||||
fn parse_precedence(&mut self, min_prec: u8) -> Result<Expr> {
|
||||
let mut lhs = if let Some(op) = self.peek()?.kind.unary_op() {
|
||||
let tok = self.next()?;
|
||||
let rhs = self.parse_precedence(op.precedence())?;
|
||||
let span = tok.span + rhs.span;
|
||||
E::UnaryOp(op, b(rhs)).span(span)
|
||||
} else {
|
||||
self.parse_access()?
|
||||
};
|
||||
let mut span = lhs.span;
|
||||
|
||||
loop {
|
||||
let tok = self.peek()?;
|
||||
if let Some(op) = tok.kind.postfix_unary_op() {
|
||||
if op.precedence() < min_prec {
|
||||
break
|
||||
}
|
||||
self.next()?;
|
||||
span += tok.span;
|
||||
lhs = E::UnaryOp(op, b(lhs)).span(span);
|
||||
continue
|
||||
};
|
||||
let Some(op) = tok.kind.binary_op() else {
|
||||
break
|
||||
};
|
||||
let (lp, rp) = op.precedence();
|
||||
if lp < min_prec {
|
||||
break
|
||||
}
|
||||
|
||||
self.next()?;
|
||||
let rhs = self.parse_precedence(rp)?;
|
||||
span += rhs.span;
|
||||
|
||||
lhs = E::BinaryOp(op, Box::new(lhs), Box::new(rhs)).span(span);
|
||||
}
|
||||
|
||||
Ok(lhs)
|
||||
}
|
||||
|
||||
fn parse_lambda(&mut self) -> Result<Expr> {
|
||||
let tok = try_next!(self, T::Backslash | T::Colon);
|
||||
match tok {
|
||||
Some(Token {
|
||||
kind: T::Backslash,
|
||||
span,
|
||||
..
|
||||
}) => {
|
||||
let args = self.parse_ident_list()?;
|
||||
expect!(self, T::Arrow);
|
||||
let body = self.parse_lambda()?;
|
||||
let body_span = body.span;
|
||||
Ok(E::Lambda(args, b(body)).span(span + body_span))
|
||||
}
|
||||
Some(Token {
|
||||
kind: T::Colon,
|
||||
span,
|
||||
..
|
||||
}) => {
|
||||
let args = vec![*SYM_DOLLAR_SIGN];
|
||||
let body = self.parse_lambda()?;
|
||||
let body_span = body.span;
|
||||
Ok(E::Lambda(args, b(body)).span(span + body_span))
|
||||
}
|
||||
None => self.parse_precedence(0),
|
||||
_ => unreachable!("parse_lambda: guaranteed by try_next!"),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_pipeline(&mut self) -> Result<Expr> {
|
||||
let mut lhs = self.parse_lambda()?;
|
||||
let mut span = lhs.span;
|
||||
while try_next!(self, T::Pipe).is_some() {
|
||||
let rhs = self.parse_lambda()?;
|
||||
span += rhs.span;
|
||||
lhs = E::Pipe(b(lhs), b(rhs)).span(span);
|
||||
}
|
||||
Ok(lhs)
|
||||
}
|
||||
|
||||
fn parse_not(&mut self) -> Result<Expr> {
|
||||
if let Some(tok) = try_next!(self, T::Not) {
|
||||
let expr = self.parse_not()?;
|
||||
let span = tok.span + expr.span;
|
||||
Ok(E::UnaryOp(UnaryOp::Not, b(expr)).span(span))
|
||||
} else {
|
||||
self.parse_pipeline()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_and(&mut self) -> Result<Expr> {
|
||||
let mut lhs = self.parse_not()?;
|
||||
let mut span = lhs.span;
|
||||
while try_next!(self, T::And).is_some() {
|
||||
let rhs = self.parse_not()?;
|
||||
span += rhs.span;
|
||||
lhs = E::And(b(lhs), b(rhs)).span(span);
|
||||
}
|
||||
Ok(lhs)
|
||||
}
|
||||
|
||||
fn parse_or(&mut self) -> Result<Expr> {
|
||||
let mut lhs = self.parse_and()?;
|
||||
let mut span = lhs.span;
|
||||
while try_next!(self, T::Or).is_some() {
|
||||
let rhs = self.parse_and()?;
|
||||
span += rhs.span;
|
||||
lhs = E::Or(b(lhs), b(rhs)).span(span);
|
||||
}
|
||||
Ok(lhs)
|
||||
}
|
||||
|
||||
fn parse_assign(&mut self) -> Result<Expr> {
|
||||
let lhs = self.parse_or()?;
|
||||
let lhs_span = lhs.span;
|
||||
if let Some(op) = self.peek()?.kind.assign_op() {
|
||||
let Some(lval) = LValue::from_expr(lhs) else {
|
||||
throw!(lhs_span, "invalid lvalue for assingment")
|
||||
};
|
||||
self.next()?;
|
||||
let rhs = self.parse_decl()?;
|
||||
let rhs_span = rhs.span;
|
||||
Ok(E::Assign(op, b(lval), b(rhs)).span(lhs_span + rhs_span))
|
||||
} else {
|
||||
Ok(lhs)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_var_decl(&mut self) -> Result<Expr> {
|
||||
let first = expect!(self, T::Var | T::Global);
|
||||
let name = expect!(self, T::Identifier);
|
||||
expect!(self, T::Equal);
|
||||
let val = self.parse_decl()?;
|
||||
let val_span = val.span;
|
||||
let kind = if first.kind == T::Global {
|
||||
E::AssignGlobal
|
||||
} else {
|
||||
E::AssignVar
|
||||
};
|
||||
Ok(kind(Symbol::get(name.content), b(val)).span(first.span + val_span))
|
||||
}
|
||||
|
||||
fn parse_fn_decl(&mut self) -> Result<Expr> {
|
||||
let tok_fn = expect!(self, T::Fn);
|
||||
let name = try_next!(self, T::Identifier).map(|t| Symbol::get(t.content));
|
||||
expect!(self, T::LParen);
|
||||
let args = self.parse_ident_list()?;
|
||||
expect!(self, T::RParen);
|
||||
match expect!(self, T::Do | T::Equal).kind {
|
||||
T::Do => {
|
||||
let content = self.parse_block()?;
|
||||
let end = expect!(self, T::End);
|
||||
Ok(E::FnDef(name, args, b(content)).span(tok_fn.span + end.span))
|
||||
}
|
||||
T::Equal => {
|
||||
let content = self.parse_expr()?;
|
||||
let span = tok_fn.span + content.span;
|
||||
Ok(E::FnDef(name, args, b(content)).span(span))
|
||||
}
|
||||
_ => unreachable!("parse_fn_decl: guaranteed by try_next!"),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_decl(&mut self) -> Result<Expr> {
|
||||
match self.peek()?.kind {
|
||||
T::Var | T::Global => self.parse_var_decl(),
|
||||
T::Fn => self.parse_fn_decl(),
|
||||
_ => self.parse_assign(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_expr(&mut self) -> Result<Expr> {
|
||||
if let Some(tok) = try_next!(self, T::Return) {
|
||||
let expr = self.parse_decl()?;
|
||||
let span = expr.span;
|
||||
Ok(E::Return(b(expr)).span(tok.span + span))
|
||||
} else {
|
||||
self.parse_decl()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_block(&mut self) -> Result<Expr> {
|
||||
while try_next!(self, T::LineSeparator).is_some() {}
|
||||
|
||||
let mut span = self.peek()?.span;
|
||||
let mut exprs = Vec::new();
|
||||
while self.peek()?.kind.expr_first() {
|
||||
let expr = self.parse_expr()?;
|
||||
span += expr.span;
|
||||
exprs.push(expr);
|
||||
|
||||
if try_next!(self, T::LineSeparator).is_none() {
|
||||
break
|
||||
}
|
||||
while try_next!(self, T::LineSeparator).is_some() {}
|
||||
}
|
||||
Ok(E::Block(exprs).span(span))
|
||||
}
|
||||
|
||||
fn parse(mut self) -> Result<Expr> {
|
||||
let block = self.parse_block()?;
|
||||
expect!(self, T::Eof);
|
||||
Ok(block)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(src: &str) -> Result<Expr> {
|
||||
Parser::new(src).parse()
|
||||
}
|
109
talc-lang/src/parser/pos.rs
Normal file
109
talc-lang/src/parser/pos.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
|
||||
pub struct Pos {
|
||||
pub idx: u32,
|
||||
pub line: u32,
|
||||
pub col: u32,
|
||||
}
|
||||
|
||||
impl std::cmp::PartialOrd for Pos {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::Ord for Pos {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.idx.cmp(&other.idx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pos {
|
||||
pub const fn new() -> Pos {
|
||||
Pos {
|
||||
idx: 0,
|
||||
line: 1,
|
||||
col: 1,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn advance(self, c: char) -> Pos {
|
||||
let idx = self
|
||||
.idx
|
||||
.checked_add(c.len_utf8() as u32)
|
||||
.expect("source file contains more than u32::MAX chars");
|
||||
if c == '\n' {
|
||||
Pos {
|
||||
idx,
|
||||
line: self.line + 1,
|
||||
col: 1,
|
||||
}
|
||||
} else {
|
||||
Pos {
|
||||
idx,
|
||||
line: self.line,
|
||||
col: self.col + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Pos {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}:{}", self.line, self.col)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Span {
|
||||
pub start: Pos,
|
||||
pub end: Pos,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub const fn new(start: Pos, end: Pos) -> Self {
|
||||
if end.idx < start.idx {
|
||||
Self {
|
||||
start: end,
|
||||
end: start,
|
||||
}
|
||||
} else {
|
||||
Self { start, end }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn of<'a>(&self, s: &'a str) -> &'a str {
|
||||
&s[(self.start.idx as usize)..(self.end.idx as usize)]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for Span {
|
||||
type Output = Span;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
let start = self.start.min(rhs.start);
|
||||
let end = self.end.max(rhs.end);
|
||||
Self::new(start, end)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign for Span {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
self.start = self.start.min(rhs.start);
|
||||
self.end = self.end.max(rhs.end);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Pos, Pos)> for Span {
|
||||
fn from((start, end): (Pos, Pos)) -> Self {
|
||||
Self { start, end }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Span {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}-{}", self.start, self.end)
|
||||
}
|
||||
}
|
171
talc-lang/src/parser/util.rs
Normal file
171
talc-lang/src/parser/util.rs
Normal file
|
@ -0,0 +1,171 @@
|
|||
use core::fmt;
|
||||
use std::num::{ParseFloatError, ParseIntError};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::lstring::{LStr, LString};
|
||||
|
||||
use super::Span;
|
||||
|
||||
#[derive(Clone, Debug, Error)]
|
||||
pub struct ParserError {
|
||||
pub span: Span,
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for ParserError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} | {}", self.span, self.msg)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SpanParserError {
|
||||
type Output;
|
||||
fn span_err(self, span: Span) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<T, E: std::error::Error> SpanParserError for Result<T, E> {
|
||||
type Output = Result<T, ParserError>;
|
||||
fn span_err(self, span: Span) -> Self::Output {
|
||||
self.map_err(|e| ParserError {
|
||||
span,
|
||||
msg: e.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Error)]
|
||||
pub enum StrEscapeError {
|
||||
#[error("EOF in string escape")]
|
||||
Eof,
|
||||
#[error("EOF in string escape \\x")]
|
||||
HexEof,
|
||||
#[error("EOF in string escape \\u")]
|
||||
UnicodeEof,
|
||||
#[error("invalid string escape \\{0}")]
|
||||
Invalid(char),
|
||||
#[error("invalid hex digit '{0}' in string escape")]
|
||||
InvalidHex(char),
|
||||
#[error("missing brace after string escape \\u")]
|
||||
MissingBrace,
|
||||
#[error("invalid codepoint in string escape: {0:x}")]
|
||||
InvalidCodepoint(u32),
|
||||
#[error("codepoint in string escape too large")]
|
||||
CodepointTooLarge,
|
||||
}
|
||||
|
||||
pub fn parse_str_escapes(src: &str) -> Result<LString, StrEscapeError> {
|
||||
let mut s = LString::with_capacity(src.len());
|
||||
let mut chars = src.chars();
|
||||
|
||||
while let Some(c) = chars.next() {
|
||||
if c != '\\' {
|
||||
s.push_char(c);
|
||||
continue
|
||||
}
|
||||
let c = chars.next().ok_or(StrEscapeError::Eof)?;
|
||||
match c {
|
||||
'"' | '\'' | '\\' => s.push_char(c),
|
||||
'0' => s.push_char('\0'),
|
||||
'a' => s.push_char('\x07'),
|
||||
'b' => s.push_char('\x08'),
|
||||
't' => s.push_char('\t'),
|
||||
'n' => s.push_char('\n'),
|
||||
'v' => s.push_char('\x0b'),
|
||||
'f' => s.push_char('\x0c'),
|
||||
'r' => s.push_char('\r'),
|
||||
'e' => s.push_char('\x1b'),
|
||||
'x' => {
|
||||
let c = chars.next().ok_or(StrEscapeError::HexEof)?;
|
||||
let n1 = c.to_digit(16).ok_or(StrEscapeError::InvalidHex(c))?;
|
||||
let c = chars.next().ok_or(StrEscapeError::HexEof)?;
|
||||
let n2 = c.to_digit(16).ok_or(StrEscapeError::InvalidHex(c))?;
|
||||
s.push_byte((n1 * 16 + n2) as u8);
|
||||
}
|
||||
'u' => {
|
||||
let Some('{') = chars.next() else {
|
||||
return Err(StrEscapeError::MissingBrace)
|
||||
};
|
||||
let mut n = 0_u32;
|
||||
loop {
|
||||
let Some(c) = chars.next() else {
|
||||
return Err(StrEscapeError::UnicodeEof)
|
||||
};
|
||||
if c == '}' {
|
||||
break
|
||||
}
|
||||
if n > 0x10ffff {
|
||||
return Err(StrEscapeError::CodepointTooLarge)
|
||||
}
|
||||
n = n * 16 + c.to_digit(16).ok_or(StrEscapeError::InvalidHex(c))?;
|
||||
}
|
||||
let ch = char::from_u32(n).ok_or(StrEscapeError::InvalidCodepoint(n))?;
|
||||
s.push_char(ch);
|
||||
}
|
||||
c => return Err(StrEscapeError::Invalid(c)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub fn parse_float<'a, S: Into<&'a LStr>>(f: S) -> Result<f64, ParseFloatError> {
|
||||
let mut s = String::new();
|
||||
for c in f.into().chars() {
|
||||
if c != '_' {
|
||||
s.push(c);
|
||||
}
|
||||
}
|
||||
s.parse()
|
||||
}
|
||||
|
||||
pub fn parse_int<'a, S: Into<&'a LStr>>(f: S, radix: u32) -> Result<i64, ParseIntError> {
|
||||
let mut s = String::new();
|
||||
for c in f.into().chars() {
|
||||
if c != '_' {
|
||||
s.push(c);
|
||||
}
|
||||
}
|
||||
i64::from_str_radix(&s, radix)
|
||||
}
|
||||
|
||||
pub fn parse_int_literal<'a, S: Into<&'a LStr>>(f: S) -> Result<i64, ParseIntError> {
|
||||
let f = f.into();
|
||||
match f.chars().nth(2) {
|
||||
Some('x') => parse_int(&f[2..], 16),
|
||||
Some('o') => parse_int(&f[2..], 8),
|
||||
Some('s') => parse_int(&f[2..], 6),
|
||||
Some('b') => parse_int(&f[2..], 2),
|
||||
_ => parse_int(f, 10),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_lstring_radix(n: i64, radix: u32, upper: bool) -> LString {
|
||||
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;
|
||||
|
||||
let mut c = char::from_digit(m as u32, radix).unwrap();
|
||||
if upper {
|
||||
c.make_ascii_uppercase();
|
||||
}
|
||||
result.push(c as u8);
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
result[begin..].reverse();
|
||||
LString::from(result)
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
use std::num::{ParseIntError, ParseFloatError};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::lstring::{LStr, LString};
|
||||
|
||||
#[derive(Clone, Debug, Error)]
|
||||
pub enum ParseError {
|
||||
#[error("{0}")]
|
||||
StrEscape(#[from] StrEscapeError),
|
||||
#[error("{0}")]
|
||||
Integer(#[from] ParseIntError),
|
||||
#[error("{0}")]
|
||||
Float(#[from] ParseFloatError),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Error)]
|
||||
pub enum StrEscapeError {
|
||||
#[error("EOF in string escape")]
|
||||
Eof,
|
||||
#[error("EOF in string escape \\x")]
|
||||
HexEof,
|
||||
#[error("EOF in string escape \\u")]
|
||||
UnicodeEof,
|
||||
#[error("Invalid string escape \\{0}")]
|
||||
Invalid(char),
|
||||
#[error("Invalid hex digit '{0}' in string escape")]
|
||||
InvalidHex(char),
|
||||
#[error("Missing brace after string escape \\u")]
|
||||
MissingBrace,
|
||||
#[error("Invalid codepoint in string escape: {0:x}")]
|
||||
InvalidCodepoint(u32),
|
||||
#[error("Codepoint in string escape too large")]
|
||||
CodepointTooLarge,
|
||||
}
|
||||
|
||||
pub fn parse_str_escapes(src: &str) -> Result<LString, StrEscapeError> {
|
||||
let mut s = LString::with_capacity(src.len());
|
||||
let mut chars = src.chars();
|
||||
|
||||
while let Some(c) = chars.next() {
|
||||
if c != '\\' { s.push_char(c); continue }
|
||||
let c = chars.next().ok_or(StrEscapeError::Eof)?;
|
||||
match c {
|
||||
'"' | '\'' | '\\' => s.push_char(c),
|
||||
'0' => s.push_char('\0'),
|
||||
'a' => s.push_char('\x07'),
|
||||
'b' => s.push_char('\x08'),
|
||||
't' => s.push_char('\t'),
|
||||
'n' => s.push_char('\n'),
|
||||
'v' => s.push_char('\x0b'),
|
||||
'f' => s.push_char('\x0c'),
|
||||
'r' => s.push_char('\r'),
|
||||
'e' => s.push_char('\x1b'),
|
||||
'x' => {
|
||||
let c = chars.next().ok_or(StrEscapeError::HexEof)?;
|
||||
let n1 = c.to_digit(16).ok_or(StrEscapeError::InvalidHex(c))?;
|
||||
let c = chars.next().ok_or(StrEscapeError::HexEof)?;
|
||||
let n2 = c.to_digit(16).ok_or(StrEscapeError::InvalidHex(c))?;
|
||||
s.push_byte((n1 * 16 + n2) as u8);
|
||||
},
|
||||
'u' => {
|
||||
let Some('{') = chars.next() else {
|
||||
return Err(StrEscapeError::MissingBrace)
|
||||
};
|
||||
let mut n = 0u32;
|
||||
loop {
|
||||
let Some(c) = chars.next() else {
|
||||
return Err(StrEscapeError::UnicodeEof)
|
||||
};
|
||||
if c == '}' { break }
|
||||
if n >= 0x1000_0000u32 {
|
||||
return Err(StrEscapeError::CodepointTooLarge)
|
||||
}
|
||||
n = n * 16 + c.to_digit(16).ok_or(StrEscapeError::InvalidHex(c))?;
|
||||
}
|
||||
let ch = char::from_u32(n).ok_or(StrEscapeError::InvalidCodepoint(n))?;
|
||||
s.push_char(ch);
|
||||
|
||||
},
|
||||
c => return Err(StrEscapeError::Invalid(c)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub fn parse_float<'a, S: Into<&'a LStr>>(f: S) -> Result<f64, ParseFloatError> {
|
||||
let mut s = String::new();
|
||||
for c in f.into().chars() {
|
||||
if c != '_' {
|
||||
s.push(c);
|
||||
}
|
||||
}
|
||||
s.parse()
|
||||
}
|
||||
|
||||
pub fn parse_int<'a, S: Into<&'a LStr>>(f: S, radix: u32) -> Result<i64, ParseIntError> {
|
||||
let mut s = String::new();
|
||||
for c in f.into().chars() {
|
||||
if c != '_' {
|
||||
s.push(c);
|
||||
}
|
||||
}
|
||||
i64::from_str_radix(&s, radix)
|
||||
}
|
|
@ -1,14 +1,19 @@
|
|||
use std::{collections::HashMap, sync::{Mutex, OnceLock}};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Mutex, OnceLock},
|
||||
};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
#[derive(Default)]
|
||||
struct SymbolTable {
|
||||
names: Vec<&'static LStr>,
|
||||
values: HashMap<&'static LStr, Symbol>
|
||||
values: HashMap<&'static LStr, Symbol>,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SYM_SELF: Symbol = symbol!(self);
|
||||
pub static ref SYM_DOLLAR_SIGN: Symbol = symbol!("$");
|
||||
pub static ref SYM_NIL: Symbol = symbol!(nil);
|
||||
pub static ref SYM_BOOL: Symbol = symbol!(bool);
|
||||
pub static ref SYM_SYMBOL: Symbol = symbol!(symbol);
|
||||
|
@ -23,14 +28,12 @@ lazy_static! {
|
|||
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_END_ITERATION: Symbol = symbol!(end_iteration);
|
||||
|
||||
pub static ref SYM_TYPE: Symbol = symbol!(type);
|
||||
pub static ref SYM_MSG: Symbol = symbol!(msg);
|
||||
pub static ref SYM_DATA: Symbol = symbol!(data);
|
||||
|
||||
pub static ref SYM_TYPE_ERROR: Symbol = symbol!(type_error);
|
||||
pub static ref SYM_VALUE_ERROR: Symbol = symbol!(value_error);
|
||||
pub static ref SYM_NAME_ERROR: Symbol = symbol!(name_error);
|
||||
pub static ref SYM_INDEX_ERROR: Symbol = symbol!(index_error);
|
||||
pub static ref SYM_HASH_ERROR: Symbol = symbol!(hash_error);
|
||||
|
@ -106,7 +109,11 @@ impl Symbol {
|
|||
/// If the mutex storing the symbol table is poisoned
|
||||
pub fn name(self) -> &'static LStr {
|
||||
let table = get_table().lock().expect("couldn't lock symbol table");
|
||||
table.names.get(self.0 as usize).copied().expect("symbol does not exist")
|
||||
table
|
||||
.names
|
||||
.get(self.0 as usize)
|
||||
.copied()
|
||||
.expect("symbol does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,4 +130,3 @@ macro_rules! symbol {
|
|||
pub use symbol;
|
||||
|
||||
use crate::lstring::{LStr, LString};
|
||||
|
||||
|
|
|
@ -1,27 +1,35 @@
|
|||
use std::rc::Rc;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::{chunk::Chunk, Vm, exception::Result};
|
||||
use crate::{chunk::Chunk, exception::Result, symbol::Symbol, Vm};
|
||||
|
||||
use super::Value;
|
||||
use super::{CellValue, Value};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct FuncAttrs {
|
||||
pub arity: usize,
|
||||
pub variadic: bool,
|
||||
pub name: Option<Symbol>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Function {
|
||||
pub attrs: FuncAttrs,
|
||||
pub chunk: Rc<Chunk>,
|
||||
pub state: Box<[CellValue]>,
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn new(chunk: Rc<Chunk>, arity: usize) -> Self {
|
||||
Self { chunk, attrs: FuncAttrs { arity, variadic: false } }
|
||||
pub fn new(chunk: Rc<Chunk>, attrs: FuncAttrs, closes: usize) -> Self {
|
||||
let v = Rc::new(RefCell::new(Value::Nil));
|
||||
let state = std::iter::repeat(v).take(closes).collect();
|
||||
Self::from_parts(chunk, attrs, state)
|
||||
}
|
||||
|
||||
pub fn from_parts(chunk: Rc<Chunk>, attrs: FuncAttrs, state: Box<[CellValue]>) -> Self {
|
||||
Self {
|
||||
chunk,
|
||||
attrs,
|
||||
state,
|
||||
}
|
||||
pub fn new_variadic(chunk: Rc<Chunk>, arity: usize) -> Self {
|
||||
Self { chunk, attrs: FuncAttrs { arity, variadic: true } }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,11 +41,21 @@ pub struct NativeFunc {
|
|||
}
|
||||
|
||||
impl NativeFunc {
|
||||
pub fn new(func: FnNative, arity: usize) -> Self {
|
||||
Self { func, attrs: FuncAttrs { arity, variadic: false } }
|
||||
pub fn new(func: FnNative, arity: usize, name: Symbol) -> Self {
|
||||
Self {
|
||||
func,
|
||||
attrs: FuncAttrs {
|
||||
arity,
|
||||
name: Some(name),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_anon(func: FnNative, arity: usize) -> Self {
|
||||
Self {
|
||||
func,
|
||||
attrs: FuncAttrs { arity, name: None },
|
||||
}
|
||||
pub fn new_variadic(func: FnNative, arity: usize) -> Self {
|
||||
Self { func, attrs: FuncAttrs { arity, variadic: true } }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,11 +67,11 @@ impl std::fmt::Debug for NativeFunc {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn disasm_recursive(f: &Rc<Function>, w: &mut impl std::io::Write) -> std::io::Result<()> {
|
||||
writeln!(w, "{} ({}{})",
|
||||
Value::Function(f.clone()),
|
||||
f.attrs.arity,
|
||||
if f.attrs.variadic { ".." } else { "" })?;
|
||||
pub fn disasm_recursive(
|
||||
f: &Rc<Function>,
|
||||
w: &mut impl std::io::Write,
|
||||
) -> std::io::Result<()> {
|
||||
writeln!(w, "{} ({})", Value::Function(f.clone()), f.attrs.arity)?;
|
||||
if !f.chunk.consts.is_empty() {
|
||||
writeln!(w, "constants")?;
|
||||
for (i, c) in f.chunk.consts.iter().enumerate() {
|
||||
|
@ -74,7 +92,9 @@ pub fn disasm_recursive(f: &Rc<Function>, w: &mut impl std::io::Write) -> std::i
|
|||
if let Some(types) = &catch.types {
|
||||
write!(w, "{:04} [", catch.addr)?;
|
||||
for (i, ty) in types.iter().enumerate() {
|
||||
if i != 0 { write!(w, ", ")?; }
|
||||
if i != 0 {
|
||||
write!(w, ", ")?;
|
||||
}
|
||||
write!(w, "{}", ty.name())?;
|
||||
}
|
||||
writeln!(w, "]")?;
|
||||
|
@ -97,4 +117,3 @@ pub fn disasm_recursive(f: &Rc<Function>, w: &mut impl std::io::Write) -> std::i
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
use crate::{symbol::{SYM_END_ITERATION, SYM_INDEX_ERROR, SYM_TYPE_ERROR}, value::function::NativeFunc, vmcalliter, Vm, exception::{Result, throw}};
|
||||
use crate::{
|
||||
exception::{throw, Result},
|
||||
symbol::{symbol, SYM_END_ITERATION, SYM_INDEX_ERROR, SYM_TYPE_ERROR},
|
||||
value::function::NativeFunc,
|
||||
vmcalliter, Vm,
|
||||
};
|
||||
|
||||
use super::{Value, range::RangeType};
|
||||
use super::{range::RangeType, Value};
|
||||
|
||||
impl Value {
|
||||
pub fn index(&self, idx: Self) -> Result<Self> {
|
||||
|
@ -11,73 +16,96 @@ impl Value {
|
|||
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())
|
||||
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)
|
||||
) {
|
||||
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))
|
||||
},
|
||||
}
|
||||
(V::String(s), V::Range(r)) => {
|
||||
let slen = s.len();
|
||||
match r.ty {
|
||||
RangeType::Open => {
|
||||
if r.start < 0 || r.start > slen as i64 {
|
||||
throw!(*SYM_INDEX_ERROR,
|
||||
"index {} out of bounds for string of length {}", r.stop, slen)
|
||||
throw!(
|
||||
*SYM_INDEX_ERROR,
|
||||
"index {} out of bounds for string of length {}",
|
||||
r.stop,
|
||||
slen
|
||||
)
|
||||
}
|
||||
if r.stop < 0 || r.stop > slen as i64 {
|
||||
throw!(*SYM_INDEX_ERROR,
|
||||
"index {} out of bounds for string of length {}", r.stop, slen)
|
||||
throw!(
|
||||
*SYM_INDEX_ERROR,
|
||||
"index {} out of bounds for string of length {}",
|
||||
r.stop,
|
||||
slen
|
||||
)
|
||||
}
|
||||
Ok(s[r.start as usize..r.stop as usize].into())
|
||||
},
|
||||
}
|
||||
RangeType::Closed => {
|
||||
if r.start < 0 || r.start > slen as i64 {
|
||||
throw!(*SYM_INDEX_ERROR,
|
||||
"index {} out of bounds for string of length {}", r.stop, slen)
|
||||
throw!(
|
||||
*SYM_INDEX_ERROR,
|
||||
"index {} out of bounds for string of length {}",
|
||||
r.stop,
|
||||
slen
|
||||
)
|
||||
}
|
||||
if r.stop < 0 || r.stop >= slen as i64 {
|
||||
throw!(*SYM_INDEX_ERROR,
|
||||
"index {} out of bounds for string of length {}", r.stop, slen)
|
||||
throw!(
|
||||
*SYM_INDEX_ERROR,
|
||||
"index {} out of bounds for string of length {}",
|
||||
r.stop,
|
||||
slen
|
||||
)
|
||||
}
|
||||
Ok(s[r.start as usize..=r.stop as usize].into())
|
||||
},
|
||||
}
|
||||
RangeType::Endless => {
|
||||
if r.start < 0 || r.start > slen as i64 {
|
||||
throw!(*SYM_INDEX_ERROR,
|
||||
"index {} out of bounds for string of length {}", r.stop, slen)
|
||||
throw!(
|
||||
*SYM_INDEX_ERROR,
|
||||
"index {} out of bounds for string of length {}",
|
||||
r.stop,
|
||||
slen
|
||||
)
|
||||
}
|
||||
Ok(s[r.start as usize..].into())
|
||||
},
|
||||
}
|
||||
},
|
||||
(col, idx) => if let Ok(ii) = idx.clone().to_iter_function() {
|
||||
}
|
||||
}
|
||||
(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())? {
|
||||
let func = move |vm: &mut Vm, _| match vmcalliter!(vm; ii.clone())? {
|
||||
Some(i) => col.index(i),
|
||||
None => Ok(Value::from(*SYM_END_ITERATION)),
|
||||
}
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(func), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(func), 0, symbol!("index...")).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;
|
||||
|
@ -88,15 +116,19 @@ impl Value {
|
|||
l[i as usize] = val;
|
||||
Ok(())
|
||||
} else {
|
||||
throw!(*SYM_INDEX_ERROR, "index {i} out of bounds for list of length {}", l.len())
|
||||
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();
|
||||
|
@ -107,53 +139,78 @@ impl Value {
|
|||
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())
|
||||
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())
|
||||
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())
|
||||
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())
|
||||
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())
|
||||
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() {
|
||||
}
|
||||
(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")
|
||||
throw!(
|
||||
*SYM_INDEX_ERROR,
|
||||
"not enough values provided for store index"
|
||||
)
|
||||
};
|
||||
col.store_index(vm, i, v)?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
throw!(*SYM_TYPE_ERROR, "cannot index {self:#} with {idx:#}")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,19 +7,23 @@ pub use num_complex::Complex64;
|
|||
use num_complex::ComplexFloat;
|
||||
pub use num_rational::Rational64;
|
||||
|
||||
use crate::exception::{throw, Exception};
|
||||
use crate::lstring::{LStr, LString};
|
||||
use crate::symbol::{Symbol, SYM_HASH_ERROR};
|
||||
use crate::exception::{Exception, throw};
|
||||
|
||||
use self::{range::{Range, RangeType}, function::{Function, NativeFunc}};
|
||||
use self::{
|
||||
function::{Function, NativeFunc},
|
||||
range::{Range, RangeType},
|
||||
};
|
||||
|
||||
pub mod function;
|
||||
pub mod index;
|
||||
pub mod ops;
|
||||
pub mod range;
|
||||
pub mod index;
|
||||
|
||||
type RcList = Rc<RefCell<Vec<Value>>>;
|
||||
type RcTable = Rc<RefCell<HashMap<HashValue, Value>>>;
|
||||
pub type CellValue = Rc<RefCell<Value>>;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub enum Value {
|
||||
|
@ -48,7 +52,12 @@ pub trait NativeValue: std::fmt::Debug + Any {
|
|||
fn get_type(&self) -> Symbol;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
fn to_lstring(&self, w: &mut LString, _repr: bool) -> io::Result<()> {
|
||||
fn to_lstring(
|
||||
&self,
|
||||
w: &mut LString,
|
||||
_repr: bool,
|
||||
_recur: &mut Vec<*const ()>,
|
||||
) -> io::Result<()> {
|
||||
w.extend(b"<native value>");
|
||||
Ok(())
|
||||
}
|
||||
|
@ -62,7 +71,11 @@ pub trait NativeValue: std::fmt::Debug + Any {
|
|||
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let s = if f.alternate() { Cow::Owned(self.repr()) } else { self.str() };
|
||||
let s = if f.alternate() {
|
||||
Cow::Owned(self.repr())
|
||||
} else {
|
||||
self.str()
|
||||
};
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +93,12 @@ impl Value {
|
|||
table.into()
|
||||
}
|
||||
|
||||
pub fn write_to_lstring(&self, w: &mut LString, repr: bool) -> io::Result<()> {
|
||||
pub fn write_to_lstring(
|
||||
&self,
|
||||
w: &mut LString,
|
||||
repr: bool,
|
||||
recur: &mut Vec<*const ()>,
|
||||
) -> io::Result<()> {
|
||||
use std::io::Write;
|
||||
match self {
|
||||
Self::Nil => write!(w, "nil"),
|
||||
|
@ -94,7 +112,7 @@ impl Value {
|
|||
write!(w, "{name:?}")?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
Self::Range(r) => match r.ty {
|
||||
RangeType::Open => write!(w, "{}..{}", r.start, r.stop),
|
||||
RangeType::Closed => write!(w, "{}..={}", r.start, r.stop),
|
||||
|
@ -109,64 +127,122 @@ impl Value {
|
|||
w.push_byte(b'+');
|
||||
}
|
||||
write!(w, "{:?}i", z.im())
|
||||
},
|
||||
}
|
||||
Self::Cell(v) if repr => {
|
||||
if recur.contains(&(v.as_ptr() as _)) {
|
||||
return w.write_all(b"cell(...)")
|
||||
}
|
||||
w.write_all(b"cell(")?;
|
||||
v.borrow().write_to_lstring(w, repr)?;
|
||||
recur.push(v.as_ptr() as _);
|
||||
v.borrow().write_to_lstring(w, repr, recur)?;
|
||||
recur.pop();
|
||||
w.write_all(b")")
|
||||
},
|
||||
Self::Cell(v) => v.borrow().write_to_lstring(w, false),
|
||||
|
||||
}
|
||||
Self::Cell(v) => {
|
||||
if recur.contains(&(v.as_ptr() as _)) {
|
||||
return w.write_all(b"cell(...)")
|
||||
}
|
||||
recur.push(v.as_ptr() as _);
|
||||
v.borrow().write_to_lstring(w, true, recur)?;
|
||||
recur.pop();
|
||||
Ok(())
|
||||
}
|
||||
Self::String(s) if repr => write!(w, "{s:?}"),
|
||||
Self::String(s) => w.write_all(s.as_bytes()),
|
||||
|
||||
Self::List(l) => {
|
||||
if recur.contains(&(l.as_ptr() as _)) {
|
||||
return w.write_all(b"[...]")
|
||||
}
|
||||
w.write_all(b"[")?;
|
||||
recur.push(l.as_ptr() as _);
|
||||
for (i, item) in l.borrow().iter().enumerate() {
|
||||
if i != 0 {
|
||||
w.write_all(b", ")?;
|
||||
}
|
||||
item.write_to_lstring(w, true)?;
|
||||
item.write_to_lstring(w, true, recur)?;
|
||||
}
|
||||
recur.pop();
|
||||
w.write_all(b"]")
|
||||
},
|
||||
}
|
||||
Self::Table(t) => {
|
||||
if recur.contains(&(t.as_ptr() as _)) {
|
||||
return w.write_all(b"{...}")
|
||||
}
|
||||
w.write_all(b"{ ")?;
|
||||
recur.push(t.as_ptr() as _);
|
||||
for (i, (k, v)) in t.borrow().iter().enumerate() {
|
||||
if i != 0 {
|
||||
w.write_all(b", ")?;
|
||||
}
|
||||
k.0.write_table_key_repr(w)?;
|
||||
k.0.write_table_key_repr(w, recur)?;
|
||||
w.write_all(b" = ")?;
|
||||
v.write_to_lstring(w, true)?;
|
||||
v.write_to_lstring(w, true, recur)?;
|
||||
}
|
||||
recur.pop();
|
||||
w.write_all(b" }")
|
||||
},
|
||||
Self::Function(g)
|
||||
=> write!(w, "<function {:?}>", Rc::as_ptr(g)),
|
||||
Self::NativeFunc(g)
|
||||
=> write!(w, "<native function {:?}>", Rc::as_ptr(g)),
|
||||
Self::Native(n) => n.to_lstring(w, repr),
|
||||
}
|
||||
Self::Function(g) => {
|
||||
if let Some(name) = g.attrs.name {
|
||||
write!(
|
||||
w,
|
||||
"<function {}({}) @{:0>12x}>",
|
||||
name.name(),
|
||||
g.attrs.arity,
|
||||
Rc::as_ptr(g) as usize
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
w,
|
||||
"<anon function({}) @{:0>12x}>",
|
||||
g.attrs.arity,
|
||||
Rc::as_ptr(g) as usize
|
||||
)
|
||||
}
|
||||
}
|
||||
Self::NativeFunc(g) => {
|
||||
if let Some(name) = g.attrs.name {
|
||||
write!(
|
||||
w,
|
||||
"<native function {}({}) @{:0>12x}>",
|
||||
name.name(),
|
||||
g.attrs.arity,
|
||||
Rc::as_ptr(g) as usize
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
w,
|
||||
"<anon native function({}) @{:0>12x}>",
|
||||
g.attrs.arity,
|
||||
Rc::as_ptr(g) as usize
|
||||
)
|
||||
}
|
||||
}
|
||||
Self::Native(n) => n.to_lstring(w, repr, recur),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_table_key_repr(&self, w: &mut LString) -> std::io::Result<()> {
|
||||
fn write_table_key_repr(
|
||||
&self,
|
||||
w: &mut LString,
|
||||
recur: &mut Vec<*const ()>,
|
||||
) -> std::io::Result<()> {
|
||||
match self {
|
||||
Self::Nil | Self::Bool(_)
|
||||
| Self::Int(_) | Self::String(_)
|
||||
=> self.write_to_lstring(w, true),
|
||||
Self::Nil | Self::Bool(_) | Self::Int(_) | Self::String(_) => {
|
||||
self.write_to_lstring(w, true, recur)
|
||||
}
|
||||
Self::Symbol(s) => {
|
||||
let name = s.name();
|
||||
if name.is_identifier() {
|
||||
w.push_lstr(name);
|
||||
Ok(())
|
||||
} else {
|
||||
self.write_to_lstring(w, true)
|
||||
self.write_to_lstring(w, true, recur)
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
w.push_byte(b'(');
|
||||
self.write_to_lstring(w, true)?;
|
||||
self.write_to_lstring(w, true, recur)?;
|
||||
w.push_byte(b')');
|
||||
Ok(())
|
||||
}
|
||||
|
@ -178,14 +254,18 @@ impl Value {
|
|||
Cow::Borrowed(s)
|
||||
} else {
|
||||
let mut s = LString::new();
|
||||
self.write_to_lstring(&mut s, false).expect("write_to_lstring failed");
|
||||
let mut recur = Vec::new();
|
||||
self.write_to_lstring(&mut s, false, &mut recur)
|
||||
.expect("write_to_lstring failed");
|
||||
Cow::Owned(s)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn repr(&self) -> LString {
|
||||
let mut s = LString::new();
|
||||
self.write_to_lstring(&mut s, true).expect("write_to_lstring failed");
|
||||
let mut recur = Vec::new();
|
||||
self.write_to_lstring(&mut s, true, &mut recur)
|
||||
.expect("write_to_lstring failed");
|
||||
s
|
||||
}
|
||||
|
||||
|
@ -213,15 +293,19 @@ impl Value {
|
|||
pub fn hashable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Value::Nil | Value::Bool(_) | Value::Symbol(_)
|
||||
| Value::Int(_) | Value::Ratio(_) | Value::String(_)
|
||||
Value::Nil
|
||||
| Value::Bool(_)
|
||||
| Value::Symbol(_)
|
||||
| Value::Int(_)
|
||||
| Value::Ratio(_)
|
||||
| Value::String(_)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn downcast_native<T: NativeValue>(&self) -> Option<&T> {
|
||||
match self {
|
||||
Value::Native(n) => n.as_any().downcast_ref(),
|
||||
_ => None
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -242,8 +326,12 @@ impl TryFrom<Value> for HashValue {
|
|||
}
|
||||
|
||||
impl HashValue {
|
||||
pub fn into_inner(self) -> Value { self.0 }
|
||||
pub fn inner(&self) -> &Value { &self.0 }
|
||||
pub fn into_inner(self) -> Value {
|
||||
self.0
|
||||
}
|
||||
pub fn inner(&self) -> &Value {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for HashValue {
|
||||
|
@ -264,39 +352,57 @@ impl Hash for HashValue {
|
|||
macro_rules! impl_from {
|
||||
($ty:ty, $var:ident) => {
|
||||
impl From<$ty> for Value {
|
||||
fn from(value: $ty) -> Self { Self::$var(value) }
|
||||
fn from(value: $ty) -> Self {
|
||||
Self::$var(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
($ty:ty, $var:ident, hash) => {
|
||||
impl From<$ty> for Value {
|
||||
fn from(value: $ty) -> Self { Self::$var(value) }
|
||||
fn from(value: $ty) -> Self {
|
||||
Self::$var(value)
|
||||
}
|
||||
}
|
||||
impl From<$ty> for HashValue {
|
||||
fn from(value: $ty) -> Self { Self(Value::$var(value)) }
|
||||
fn from(value: $ty) -> Self {
|
||||
Self(Value::$var(value))
|
||||
}
|
||||
}
|
||||
};
|
||||
($ty:ty, $var:ident, rc) => {
|
||||
impl From<$ty> for Value {
|
||||
fn from(value: $ty) -> Self { Self::$var(Rc::new(value)) }
|
||||
fn from(value: $ty) -> Self {
|
||||
Self::$var(Rc::new(value))
|
||||
}
|
||||
}
|
||||
impl From<Rc<$ty>> for Value {
|
||||
fn from(value: Rc<$ty>) -> Self { Self::$var(value) }
|
||||
fn from(value: Rc<$ty>) -> Self {
|
||||
Self::$var(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
($ty:ty, $var:ident, rcref) => {
|
||||
impl From<$ty> for Value {
|
||||
fn from(value: $ty) -> Self { Self::$var(Rc::new(RefCell::new(value))) }
|
||||
fn from(value: $ty) -> Self {
|
||||
Self::$var(Rc::new(RefCell::new(value)))
|
||||
}
|
||||
}
|
||||
impl From<RefCell<$ty>> for Value {
|
||||
fn from(value: RefCell<$ty>) -> Self { Self::$var(Rc::new(value)) }
|
||||
fn from(value: RefCell<$ty>) -> Self {
|
||||
Self::$var(Rc::new(value))
|
||||
}
|
||||
}
|
||||
impl From<Rc<RefCell<$ty>>> for Value {
|
||||
fn from(value: Rc<RefCell<$ty>>) -> Self { Self::$var(value) }
|
||||
fn from(value: Rc<RefCell<$ty>>) -> Self {
|
||||
Self::$var(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
($ty:ty, $var:ident, into) => {
|
||||
impl From<$ty> for Value {
|
||||
fn from(value: $ty) -> Self { Self::$var(value.into()) }
|
||||
fn from(value: $ty) -> Self {
|
||||
Self::$var(value.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,11 +1,27 @@
|
|||
use std::{cell::RefCell, cmp::Ordering, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Shl, Shr, Sub}, rc::Rc};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
cmp::Ordering,
|
||||
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Shl, Shr, Sub},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use num_complex::Complex64;
|
||||
use num_complex::{Complex64, ComplexFloat};
|
||||
use num_rational::Rational64;
|
||||
use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Signed, Zero};
|
||||
|
||||
use crate::{exception::{throw, Result}, lstring::LString, symbol::{SYM_END_ITERATION, SYM_TYPE_ERROR}, value::range::RangeType, Vm};
|
||||
use crate::{
|
||||
exception::{throw, Result},
|
||||
lstring::LString,
|
||||
symbol::{symbol, SYM_END_ITERATION, SYM_TYPE_ERROR, SYM_VALUE_ERROR},
|
||||
value::range::RangeType,
|
||||
Vm,
|
||||
};
|
||||
|
||||
use super::{function::{FuncAttrs, NativeFunc}, range::Range, HashValue, Value};
|
||||
use super::{
|
||||
function::{FuncAttrs, NativeFunc},
|
||||
range::Range,
|
||||
HashValue, Value,
|
||||
};
|
||||
|
||||
pub trait RatioExt {
|
||||
fn to_f64(&self) -> f64;
|
||||
|
@ -31,14 +47,15 @@ impl Value {
|
|||
Value::List(l) => l.borrow().len() > 0,
|
||||
Value::Cell(v) => v.borrow().truthy(),
|
||||
|
||||
Value::Symbol(_) | Value::Table(_)
|
||||
| Value::Function(_) | Value::NativeFunc(_)
|
||||
Value::Symbol(_)
|
||||
| Value::Table(_)
|
||||
| Value::Function(_)
|
||||
| Value::NativeFunc(_)
|
||||
| Value::Native(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
pub fn promote(a: Value, b: Value) -> (Value, Value) {
|
||||
use Value as V;
|
||||
match (&a, &b) {
|
||||
|
@ -58,145 +75,313 @@ pub fn promote(a: Value, b: Value) -> (Value, Value) {
|
|||
}
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
// unary arithmetic //
|
||||
////////////////////////
|
||||
|
||||
impl Neg for Value {
|
||||
type Output = Result<Self>;
|
||||
fn neg(self) -> Self::Output {
|
||||
use Value as V;
|
||||
match self {
|
||||
V::Int(x) => Ok(V::Int(-x)),
|
||||
V::Ratio(x) => Ok(V::Ratio(-x)),
|
||||
V::Int(x) => {
|
||||
if let Some(x) = x.checked_neg() {
|
||||
Ok(V::Int(x))
|
||||
} else {
|
||||
throw!(*SYM_VALUE_ERROR, "overflow when negating {self}")
|
||||
}
|
||||
}
|
||||
V::Ratio(x) => {
|
||||
if let Some(x) = Rational64::ZERO.checked_sub(&x) {
|
||||
Ok(V::Ratio(x))
|
||||
} else {
|
||||
throw!(*SYM_VALUE_ERROR, "overflow when negating {self}")
|
||||
}
|
||||
}
|
||||
V::Float(x) => Ok(V::Float(-x)),
|
||||
V::Complex(x) => Ok(V::Complex(-x)),
|
||||
a => throw!(*SYM_TYPE_ERROR, "cannot negate {a:#}")
|
||||
a => throw!(*SYM_TYPE_ERROR, "cannot negate {a:#}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Value> for Value {
|
||||
type Output = Result<Self>;
|
||||
fn add(self, rhs: Value) -> Self::Output {
|
||||
impl Value {
|
||||
pub fn abs(self) -> Result<Self> {
|
||||
use Value as V;
|
||||
match promote(self, rhs) {
|
||||
(V::Int(x), V::Int(y)) => Ok(V::Int(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::Complex(x), V::Complex(y)) => Ok(V::Complex(x + y)),
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot add {l:#} and {r:#}")
|
||||
match self {
|
||||
V::Int(x) => {
|
||||
if let Some(x) = x.checked_abs() {
|
||||
Ok(V::Int(x))
|
||||
} else {
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"overflow when finding absolute value of {self}"
|
||||
)
|
||||
}
|
||||
}
|
||||
V::Ratio(x) => {
|
||||
if let Some((x, _)) = ratio_checked_absign(&x) {
|
||||
Ok(V::Ratio(x))
|
||||
} else {
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"overflow when finding absolute value of {self}"
|
||||
)
|
||||
}
|
||||
}
|
||||
V::Float(x) => Ok(V::Float(x.abs())),
|
||||
V::Complex(x) => Ok(V::Float(x.norm())),
|
||||
a => throw!(*SYM_TYPE_ERROR, "cannot negate {a:#}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Value> for Value {
|
||||
/////////////////////////
|
||||
// binary arithmetic //
|
||||
/////////////////////////
|
||||
|
||||
macro_rules! impl_value_arith {
|
||||
($trait:ident, $name:ident, $checked:ident, $op:tt, $verb:literal) => {
|
||||
|
||||
impl $trait<Value> for Value {
|
||||
type Output = Result<Self>;
|
||||
fn sub(self, rhs: Value) -> Self::Output {
|
||||
fn $name(self, rhs: Value) -> Self::Output {
|
||||
use Value as V;
|
||||
match promote(self, rhs) {
|
||||
(V::Int(x), V::Int(y)) => Ok(V::Int(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::Complex(x), V::Complex(y)) => Ok(V::Complex(x - y)),
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot subtract {l:#} and {r:#}")
|
||||
let (a, b) = promote(self, rhs);
|
||||
match (&a, &b) {
|
||||
(V::Int(x), V::Int(y)) => if let Some(v) = x.$checked(y) {
|
||||
Ok(V::Int(v))
|
||||
} else {
|
||||
throw!(*SYM_VALUE_ERROR, concat!("overflow when ", $verb, "ing {} and {}"), a, b)
|
||||
},
|
||||
(V::Ratio(x), V::Ratio(y)) => if let Some(v) = x.$checked(y) {
|
||||
Ok(V::Ratio(v))
|
||||
} else {
|
||||
throw!(*SYM_VALUE_ERROR, concat!("overflow when ", $verb, "ing {} and {}"), a, b)
|
||||
},
|
||||
(V::Float(x), V::Float(y)) => Ok(V::Float(x $op y)),
|
||||
(V::Complex(x), V::Complex(y)) => Ok(V::Complex(x $op y)),
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, concat!("cannot ", $verb, " {:#} and {:#}"), l, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
impl Mul<Value> for Value {
|
||||
type Output = Result<Self>;
|
||||
fn mul(self, rhs: Value) -> Self::Output {
|
||||
use Value as V;
|
||||
match promote(self, rhs) {
|
||||
(V::Int(x), V::Int(y)) => Ok(V::Int(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::Complex(x), V::Complex(y)) => Ok(V::Complex(x * y)),
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot multiply {l:#} and {r:#}")
|
||||
}
|
||||
}
|
||||
}
|
||||
impl_value_arith!(Add, add, checked_add, +, "add");
|
||||
impl_value_arith!(Sub, sub, checked_sub, -, "subtract");
|
||||
impl_value_arith!(Mul, mul, checked_mul, *, "multiply");
|
||||
|
||||
impl Div<Value> for Value {
|
||||
type Output = Result<Self>;
|
||||
fn div(self, rhs: Value) -> Self::Output {
|
||||
use Value as V;
|
||||
match promote(self, rhs) {
|
||||
(_, V::Int(0)) => throw!(*SYM_TYPE_ERROR, "integer division by 0"),
|
||||
(_, V::Ratio(r)) if *r.numer() == 0 && *r.denom() != 0
|
||||
=> throw!(*SYM_TYPE_ERROR, "integer division by 0"),
|
||||
(V::Int(x), V::Int(y)) => Ok(V::Ratio(Rational64::new(x, y))),
|
||||
(V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x / y)),
|
||||
let (a, b) = promote(self, rhs);
|
||||
match (&a, &b) {
|
||||
(V::Int(_), V::Int(0)) => throw!(*SYM_VALUE_ERROR, "integer division by 0"),
|
||||
(V::Int(x), V::Int(y)) => Ok(Value::Ratio((*x, *y).into())),
|
||||
(V::Ratio(_), V::Ratio(r)) if r.is_zero() => {
|
||||
throw!(*SYM_VALUE_ERROR, "rational division by 0")
|
||||
}
|
||||
(V::Ratio(x), V::Ratio(y)) => {
|
||||
if let Some(v) = x.checked_div(y) {
|
||||
Ok(V::Ratio(v))
|
||||
} else {
|
||||
throw!(*SYM_VALUE_ERROR, "overflow when dividing {a} and {b}")
|
||||
}
|
||||
}
|
||||
(V::Float(x), V::Float(y)) => Ok(V::Float(x / y)),
|
||||
(V::Complex(x), V::Complex(y)) => Ok(V::Complex(x / y)),
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot divide {l:#} and {r:#}")
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot divide {l:#} and {r:#}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
fn ipow(n: i64, p: u64) -> Result<i64> {
|
||||
match (n, p) {
|
||||
(0, 0) => throw!(*SYM_TYPE_ERROR, "integer 0 raised to power 0"),
|
||||
(0, _) => Ok(0),
|
||||
(_, 0) => Ok(1),
|
||||
(n, p) if p > u32::MAX as u64 => {
|
||||
let (lo, hi) = (p as u32, (p >> 32) as u32);
|
||||
let (a, b) = (n.pow(lo), n.pow(hi));
|
||||
Ok(a * b.pow(0x1_0000).pow(0x1_0000))
|
||||
}
|
||||
(n, p) => Ok(n.pow(p as u32)),
|
||||
}
|
||||
///////////////////////////////////
|
||||
// modulo and integer division //
|
||||
///////////////////////////////////
|
||||
|
||||
#[inline]
|
||||
fn ratio_checked_absign(r: &Rational64) -> Option<(Rational64, Rational64)> {
|
||||
let a = if r.is_negative() {
|
||||
Rational64::ZERO.checked_sub(r)?
|
||||
} else {
|
||||
*r
|
||||
};
|
||||
Some((a, r.signum()))
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
fn rpow(n: i64, d: i64, p: i64) -> Result<(i64, i64)> {
|
||||
Ok(match p {
|
||||
0.. => (ipow(n, p as u64)?, ipow(d, p as u64)?),
|
||||
_ => (ipow(d, (-p) as u64)?, ipow(n, (-p) as u64)?),
|
||||
})
|
||||
#[inline]
|
||||
fn ratio_checked_div_euclid(r1: &Rational64, r2: &Rational64) -> Option<Rational64> {
|
||||
let (r2_abs, r2_sgn) = ratio_checked_absign(r2)?;
|
||||
Some(r1.checked_div(&r2_abs)?.floor() * r2_sgn)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ratio_checked_rem_euclid(r1: &Rational64, r2: &Rational64) -> Option<Rational64> {
|
||||
let q = ratio_checked_div_euclid(r1, r2)?;
|
||||
r1.checked_sub(&r2.checked_mul(&q)?)
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn modulo(self, rhs: Value) -> Result<Self> {
|
||||
use Value as V;
|
||||
match promote(self, rhs) {
|
||||
(V::Int(x), V::Int(y)) => Ok(V::Int(x.rem_euclid(y))),
|
||||
(V::Ratio(_x), V::Ratio(_y)) => todo!("ratio modulo"),
|
||||
(V::Float(x), V::Float(y)) => Ok(V::Float(x.rem_euclid(y))),
|
||||
(V::Complex(_x), V::Complex(_y)) => todo!("complex modulo"),
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot modulo {l:#} and {r:#}")
|
||||
let (a, b) = promote(self, rhs);
|
||||
match (&a, &b) {
|
||||
(V::Int(_), V::Int(0)) => throw!(*SYM_VALUE_ERROR, "integer modulo by 0"),
|
||||
(V::Int(x), V::Int(y)) => {
|
||||
if let Some(v) = x.checked_rem_euclid(*y) {
|
||||
Ok(V::Int(v))
|
||||
} else {
|
||||
throw!(*SYM_VALUE_ERROR, "overflow when calculating {a} modulo {b}")
|
||||
}
|
||||
}
|
||||
|
||||
(V::Ratio(_), V::Ratio(y)) if y.is_zero() => {
|
||||
throw!(*SYM_VALUE_ERROR, "rational modulo by 0")
|
||||
}
|
||||
(V::Ratio(x), V::Ratio(y)) => {
|
||||
if let Some(v) = ratio_checked_rem_euclid(x, y) {
|
||||
Ok(V::Ratio(v))
|
||||
} else {
|
||||
throw!(*SYM_VALUE_ERROR, "overflow when calculating {a} modulo {b}")
|
||||
}
|
||||
}
|
||||
|
||||
(V::Float(x), V::Float(y)) => Ok(V::Float(x.rem_euclid(*y))),
|
||||
(V::Complex(x), V::Complex(y)) => {
|
||||
let n = x / y;
|
||||
let n = Complex64::new(n.re().floor(), n.im().floor());
|
||||
Ok(Value::Complex(x - n * y))
|
||||
}
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot modulo {l:#} and {r:#}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn int_div(self, rhs: Value) -> Result<Self> {
|
||||
use Value as V;
|
||||
match promote(self, rhs) {
|
||||
(V::Int(x), V::Int(y)) => Ok(V::Int(x.div_euclid(y))),
|
||||
(V::Ratio(_x), V::Ratio(_y)) => todo!("ratio integer division"),
|
||||
(V::Float(x), V::Float(y)) => Ok(V::Float(x.div_euclid(y))),
|
||||
(V::Complex(_x), V::Complex(_y)) => todo!("complex integer division"),
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot integer divide {l:#} and {r:#}")
|
||||
let (a, b) = promote(self, rhs);
|
||||
match (&a, &b) {
|
||||
(V::Int(_), V::Int(0)) => throw!(*SYM_VALUE_ERROR, "integer divsion by 0"),
|
||||
(V::Int(x), V::Int(y)) => {
|
||||
if let Some(v) = x.checked_div_euclid(*y) {
|
||||
Ok(V::Int(v))
|
||||
} else {
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"overflow when integer dividing {a} and {b}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pow(self, rhs: Value) -> Result<Self> {
|
||||
use Value as V;
|
||||
if let (V::Ratio(x), V::Int(y)) = (&self, &rhs) {
|
||||
return Ok(V::Ratio(rpow(*(*x).numer(), *(*x).denom(), *y)?.into()));
|
||||
(V::Ratio(_), V::Ratio(y)) if y.is_zero() => {
|
||||
throw!(*SYM_VALUE_ERROR, "integer division by 0")
|
||||
}
|
||||
match promote(self, rhs) {
|
||||
(V::Int(x), V::Int(y)) if y >= 0 => Ok(V::Int(ipow(x, y as u64)?)),
|
||||
(V::Int(x), V::Int(y)) => Ok(V::Ratio(rpow(x, 1, y)?.into())),
|
||||
(V::Float(x), V::Float(y))
|
||||
=> Ok(V::Float(x.powf(y))),
|
||||
(V::Ratio(x), V::Ratio(y))
|
||||
=> Ok(V::Float(x.to_f64().powf(y.to_f64()))),
|
||||
(V::Complex(x), V::Complex(y))
|
||||
=> Ok(V::Complex(x.powc(y))),
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot exponentiate {l:#} and {r:#}")
|
||||
(V::Ratio(x), V::Ratio(y)) => {
|
||||
if let Some(v) = ratio_checked_div_euclid(x, y) {
|
||||
Ok(V::Ratio(v))
|
||||
} else {
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"overflow when integer dividing {a} and {b}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
(V::Float(x), V::Float(y)) => Ok(V::Float(x.div_euclid(*y))),
|
||||
(V::Complex(x), V::Complex(y)) => {
|
||||
let n = x / y;
|
||||
Ok(V::from(Complex64::new(n.re().floor(), n.im().floor())))
|
||||
}
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot integer divide {l:#} and {r:#}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// exponentiation //
|
||||
//////////////////////
|
||||
|
||||
#[inline]
|
||||
fn ipow(n: i64, p: u64) -> Option<i64> {
|
||||
match (n, p) {
|
||||
(0, 0) => None,
|
||||
(0, _) => Some(0),
|
||||
(_, 0) => Some(1),
|
||||
(1, _) => Some(1),
|
||||
(-1, p) => (-1_i64).checked_pow((p % 2) as u32),
|
||||
(_, p) if p > u32::MAX as u64 => None,
|
||||
(n, p) => n.checked_pow(p as u32),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn rpow(n: i64, d: i64, p: i64) -> Option<(i64, i64)> {
|
||||
match p {
|
||||
i64::MIN => match (n, d) {
|
||||
(0, _) => Some((0, 1)),
|
||||
(1, 1) => Some((1, 1)),
|
||||
(-1, 1) => Some((-1, 1)),
|
||||
_ => None,
|
||||
},
|
||||
0.. => Some((ipow(n, p as u64)?, ipow(d, p as u64)?)),
|
||||
_ => Some((ipow(d, (-p) as u64)?, ipow(n, (-p) as u64)?)),
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn pow(self, rhs: Value) -> Result<Self> {
|
||||
use Value as V;
|
||||
if let (V::Ratio(x), V::Int(y)) = (&self, &rhs) {
|
||||
if x.is_zero() && *y == 0 {
|
||||
throw!(*SYM_VALUE_ERROR, "rational zero to integer zero power")
|
||||
}
|
||||
let Some(v) = rpow(*(*x).numer(), *(*x).denom(), *y) else {
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"overflow when raising {self} to the power {rhs}"
|
||||
)
|
||||
};
|
||||
return Ok(V::Ratio(v.into()))
|
||||
}
|
||||
let (a, b) = promote(self, rhs);
|
||||
match (&a, &b) {
|
||||
(V::Int(0), V::Int(0)) => {
|
||||
throw!(*SYM_VALUE_ERROR, "integer zero to integer zero power")
|
||||
}
|
||||
(V::Int(x), V::Int(y @ 0..)) => {
|
||||
if let Some(v) = ipow(*x, *y as u64) {
|
||||
Ok(V::Int(v))
|
||||
} else {
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"overflow when raising {a} to the power {b}"
|
||||
)
|
||||
}
|
||||
}
|
||||
(V::Int(x), V::Int(y)) => {
|
||||
if let Some(v) = rpow(*x, 1, *y) {
|
||||
Ok(V::Ratio(v.into()))
|
||||
} else {
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"overflow when raising {a} to the power {b}"
|
||||
)
|
||||
}
|
||||
}
|
||||
(V::Ratio(x), V::Ratio(y)) => Ok(V::Float(x.to_f64().powf(y.to_f64()))),
|
||||
(V::Float(x), V::Float(y)) => Ok(V::Float(x.powf(*y))),
|
||||
(V::Complex(x), V::Complex(y)) => Ok(V::Complex(x.powc(*y))),
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot exponentiate {l:#} and {r:#}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Bitwise operations //
|
||||
//////////////////////////
|
||||
|
||||
impl Shl<Value> for Value {
|
||||
type Output = Result<Value>;
|
||||
|
||||
|
@ -204,7 +389,7 @@ impl Shl<Value> for Value {
|
|||
use Value as V;
|
||||
match (self, rhs) {
|
||||
(V::Int(a), V::Int(b)) => Ok(Value::Int(((a as u64) << b as u64) as i64)),
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} left by {r:#}")
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} left by {r:#}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -216,7 +401,7 @@ impl Shr<Value> for Value {
|
|||
use Value as V;
|
||||
match (self, rhs) {
|
||||
(V::Int(a), V::Int(b)) => Ok(Value::Int((a as u64 >> b as u64) as i64)),
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} right by {r:#}")
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} right by {r:#}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -228,7 +413,7 @@ impl BitAnd<Value> for Value {
|
|||
use Value as V;
|
||||
match (self, rhs) {
|
||||
(V::Int(a), V::Int(b)) => Ok(Value::Int(a & b)),
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} right by {r:#}")
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} right by {r:#}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -240,7 +425,7 @@ impl BitXor<Value> for Value {
|
|||
use Value as V;
|
||||
match (self, rhs) {
|
||||
(V::Int(a), V::Int(b)) => Ok(Value::Int(a ^ b)),
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} right by {r:#}")
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} right by {r:#}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -252,16 +437,19 @@ impl BitOr<Value> for Value {
|
|||
use Value as V;
|
||||
match (self, rhs) {
|
||||
(V::Int(a), V::Int(b)) => Ok(Value::Int(a | b)),
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} right by {r:#}")
|
||||
(l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} right by {r:#}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// Equality and ordering //
|
||||
/////////////////////////////
|
||||
|
||||
impl PartialEq for Value {
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
use Value as V;
|
||||
use super::range::RangeType as Rty;
|
||||
use Value as V;
|
||||
match (self, other) {
|
||||
(V::Nil, V::Nil) => true,
|
||||
(V::Bool(a), V::Bool(b)) => a == b,
|
||||
|
@ -286,8 +474,9 @@ impl PartialEq for Value {
|
|||
(V::Symbol(a), V::Symbol(b)) => a == b,
|
||||
(V::Cell(a), V::Cell(b)) => *a.borrow() == *b.borrow(),
|
||||
(V::Range(a), V::Range(b)) => match (a.ty, b.ty) {
|
||||
(Rty::Open, Rty::Open)
|
||||
| (Rty::Closed, Rty::Closed) => a.start == b.start && a.stop == b.stop,
|
||||
(Rty::Open, Rty::Open) | (Rty::Closed, Rty::Closed) => {
|
||||
a.start == b.start && a.stop == b.stop
|
||||
}
|
||||
(Rty::Open, Rty::Closed) => a.start == b.start && a.stop == b.stop - 1,
|
||||
(Rty::Closed, Rty::Open) => a.start == b.start && a.stop - 1 == b.stop,
|
||||
(Rty::Endless, Rty::Endless) => a.start == b.start,
|
||||
|
@ -301,7 +490,6 @@ impl PartialEq for Value {
|
|||
}
|
||||
|
||||
impl PartialOrd for Value {
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
use Value as V;
|
||||
match (self, other) {
|
||||
|
@ -324,6 +512,10 @@ impl PartialOrd for Value {
|
|||
}
|
||||
}
|
||||
|
||||
////////////
|
||||
// misc //
|
||||
////////////
|
||||
|
||||
impl Value {
|
||||
pub fn val_cmp(&self, other: &Self) -> Result<Ordering> {
|
||||
match self.partial_cmp(other) {
|
||||
|
@ -339,7 +531,7 @@ impl Value {
|
|||
let mut l = l1.borrow().clone();
|
||||
l.extend_from_slice(&l2.borrow());
|
||||
Ok(l.into())
|
||||
},
|
||||
}
|
||||
(V::String(s1), V::String(s2)) => {
|
||||
let mut s: LString = s1.as_ref().to_owned();
|
||||
s.push_lstr(s2);
|
||||
|
@ -361,23 +553,40 @@ impl Value {
|
|||
let mut l = list.borrow().clone();
|
||||
l.push(val);
|
||||
Ok(l.into())
|
||||
},
|
||||
}
|
||||
lhs => throw!(*SYM_TYPE_ERROR, "cannot append to {lhs:#}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range(&self, other: &Self, closed: bool) -> Result<Self> {
|
||||
if let (Value::Int(start), Value::Int(stop)) = (self, other) {
|
||||
let ty = if closed { RangeType::Closed } else { RangeType::Open };
|
||||
Ok(Range { start: *start, stop: *stop, ty }.into())
|
||||
let ty = if closed {
|
||||
RangeType::Closed
|
||||
} else {
|
||||
throw!(*SYM_TYPE_ERROR, "cannot create range between {self:#} and {other:#}")
|
||||
RangeType::Open
|
||||
};
|
||||
Ok(Range {
|
||||
start: *start,
|
||||
stop: *stop,
|
||||
ty,
|
||||
}
|
||||
.into())
|
||||
} else {
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"cannot create range between {self:#} and {other:#}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range_endless(&self) -> Result<Self> {
|
||||
if let Value::Int(start) = self {
|
||||
Ok(Range { start: *start, stop: 0, ty: RangeType::Endless }.into())
|
||||
Ok(Range {
|
||||
start: *start,
|
||||
stop: 0,
|
||||
ty: RangeType::Endless,
|
||||
}
|
||||
.into())
|
||||
} else {
|
||||
throw!(*SYM_TYPE_ERROR, "cannot create endless range from {self:#}")
|
||||
}
|
||||
|
@ -397,7 +606,7 @@ impl Value {
|
|||
pub fn iter_pack(v: Option<Self>) -> Self {
|
||||
match v {
|
||||
Some(v) => v,
|
||||
None => Value::from(*SYM_END_ITERATION)
|
||||
None => Value::from(*SYM_END_ITERATION),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -407,11 +616,12 @@ impl Value {
|
|||
Self::Range(range) => {
|
||||
let range_iter = RefCell::new(range.into_iter());
|
||||
let f = move |_: &mut Vm, _: Vec<Value>| -> Result<Value> {
|
||||
Ok(Value::iter_pack(range_iter.borrow_mut().next()
|
||||
.map(Value::from)))
|
||||
Ok(Value::iter_pack(
|
||||
range_iter.borrow_mut().next().map(Value::from),
|
||||
))
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
},
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[range]")).into())
|
||||
}
|
||||
Self::String(s) => {
|
||||
let byte_pos = RefCell::new(0);
|
||||
let f = move |_: &mut Vm, _: Vec<Value>| -> Result<Value> {
|
||||
|
@ -423,8 +633,8 @@ impl Value {
|
|||
Ok(Value::from(*SYM_END_ITERATION))
|
||||
}
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
},
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[string]")).into())
|
||||
}
|
||||
Self::List(list) => {
|
||||
let idx = RefCell::new(0);
|
||||
let f = move |_: &mut Vm, _: Vec<Value>| -> Result<Value> {
|
||||
|
@ -436,17 +646,18 @@ impl Value {
|
|||
Ok(Value::from(*SYM_END_ITERATION))
|
||||
}
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
},
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[list]")).into())
|
||||
}
|
||||
Self::Table(table) => {
|
||||
let keys: Vec<HashValue> = table.borrow().keys().cloned().collect();
|
||||
let keys = RefCell::new(keys.into_iter());
|
||||
let f = move |_: &mut Vm, _: Vec<Value>| -> Result<Value> {
|
||||
Ok(Value::iter_pack(keys.borrow_mut().next()
|
||||
.map(HashValue::into_inner)))
|
||||
Ok(Value::iter_pack(
|
||||
keys.borrow_mut().next().map(HashValue::into_inner),
|
||||
))
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
},
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[table]")).into())
|
||||
}
|
||||
_ => throw!(*SYM_TYPE_ERROR, "cannot iterate {self:#}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum RangeType {
|
||||
Open, Closed, Endless,
|
||||
Open,
|
||||
Closed,
|
||||
Endless,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
@ -31,7 +32,7 @@ impl Range {
|
|||
|
||||
impl IntoIterator for Range {
|
||||
type Item = i64;
|
||||
type IntoIter = Box<dyn Iterator<Item=i64>>;
|
||||
type IntoIter = Box<dyn Iterator<Item = i64>>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
match self.ty {
|
||||
RangeType::Open => Box::new(self.start..self.stop),
|
||||
|
@ -40,5 +41,3 @@ impl IntoIterator for Range {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,28 @@
|
|||
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, exception::{throw, Exception, Result}, lstring::LStr, symbol::{Symbol, SYM_CALL_STACK_OVERFLOW, SYM_INTERRUPTED, SYM_NAME_ERROR, SYM_TYPE_ERROR}, value::{function::{FuncAttrs, Function, NativeFunc}, Value}};
|
||||
use crate::{
|
||||
chunk::Instruction,
|
||||
exception::{throw, Exception, Result},
|
||||
lstring::LStr,
|
||||
parser::ast::{BinaryOp, UnaryOp},
|
||||
symbol::{
|
||||
Symbol, SYM_CALL_STACK_OVERFLOW, SYM_INTERRUPTED, SYM_NAME_ERROR, SYM_TYPE_ERROR,
|
||||
},
|
||||
value::{
|
||||
function::{FuncAttrs, Function, NativeFunc},
|
||||
Value,
|
||||
},
|
||||
};
|
||||
|
||||
struct TryFrame { idx: usize, stack_len: usize }
|
||||
struct TryFrame {
|
||||
idx: usize,
|
||||
stack_len: usize,
|
||||
}
|
||||
|
||||
struct CallFrame {
|
||||
func: Rc<Function>,
|
||||
|
@ -72,43 +92,34 @@ enum CallOutcome {
|
|||
Partial(Value),
|
||||
}
|
||||
|
||||
fn get_call_outcome(mut args: Vec<Value>) -> Result<CallOutcome> {
|
||||
fn get_call_outcome(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 {
|
||||
match argc.cmp(&attrs.arity) {
|
||||
Ordering::Equal => Ok(CallOutcome::Call(args)),
|
||||
Ordering::Greater => throw!(*SYM_TYPE_ERROR, "too many arguments for function"),
|
||||
Ordering::Less => {
|
||||
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")
|
||||
};
|
||||
let varargs = Rc::unwrap_or_clone(varargs).take();
|
||||
args.extend(varargs);
|
||||
}
|
||||
let args: Vec<Value> = args.clone().into_iter().chain(ia).collect();
|
||||
vm.call_value(f.clone(), args)
|
||||
};
|
||||
let nf = NativeFunc {
|
||||
attrs: FuncAttrs { arity: remaining, variadic: attrs.variadic },
|
||||
attrs: FuncAttrs {
|
||||
arity: remaining,
|
||||
name: None,
|
||||
},
|
||||
func: Box::new(nf),
|
||||
};
|
||||
Ok(CallOutcome::Partial(nf.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
|
@ -131,7 +142,9 @@ impl Vm {
|
|||
}
|
||||
|
||||
pub fn set_global_name<'a, S>(&mut self, name: S, val: Value)
|
||||
where S: Into<&'a LStr> {
|
||||
where
|
||||
S: Into<&'a LStr>,
|
||||
{
|
||||
self.globals.insert(Symbol::get(name.into()), val);
|
||||
}
|
||||
|
||||
|
@ -150,8 +163,8 @@ impl Vm {
|
|||
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")
|
||||
}
|
||||
_ => unreachable!("already verified by calling get_call_type"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,7 +200,9 @@ impl Vm {
|
|||
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) {
|
||||
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);
|
||||
|
@ -221,30 +236,33 @@ impl Vm {
|
|||
}
|
||||
|
||||
fn check_interrupt(&mut self) -> Result<()> {
|
||||
if self.interrupt.fetch_and(false, std::sync::atomic::Ordering::Relaxed) {
|
||||
if self
|
||||
.interrupt
|
||||
.fetch_and(false, std::sync::atomic::Ordering::Relaxed)
|
||||
{
|
||||
throw!(*SYM_INTERRUPTED)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_instr(&mut self, frame: &mut CallFrame, instr: Instruction) -> Result<Option<Value>> {
|
||||
fn run_instr(
|
||||
&mut self,
|
||||
frame: &mut CallFrame,
|
||||
instr: Instruction,
|
||||
) -> Result<Option<Value>> {
|
||||
use Instruction as I;
|
||||
|
||||
match instr {
|
||||
// do nothing
|
||||
I::Nop => (),
|
||||
// [] -> [locals[n]]
|
||||
I::LoadLocal(n)
|
||||
=> self.push(frame.locals[usize::from(n)].clone()),
|
||||
I::LoadLocal(n) => self.push(frame.locals[usize::from(n)].clone()),
|
||||
// [x] -> [], locals[n] = x
|
||||
I::StoreLocal(n)
|
||||
=> frame.locals[usize::from(n)] = self.pop(),
|
||||
I::StoreLocal(n) => frame.locals[usize::from(n)] = self.pop(),
|
||||
// [x] -> [], locals.push(x)
|
||||
I::NewLocal
|
||||
=> frame.locals.push(self.pop()),
|
||||
I::NewLocal => frame.locals.push(self.pop()),
|
||||
// locals.pop_n(n)
|
||||
I::DropLocal(n)
|
||||
=> frame.locals.truncate(frame.locals.len() - usize::from(n)),
|
||||
I::DropLocal(n) => frame.locals.truncate(frame.locals.len() - usize::from(n)),
|
||||
// [] -> [globals[s]]
|
||||
I::LoadGlobal(s) => {
|
||||
let sym = unsafe { s.to_symbol_unchecked() };
|
||||
|
@ -253,16 +271,67 @@ impl Vm {
|
|||
None => throw!(*SYM_NAME_ERROR, "undefined global {}", sym.name()),
|
||||
};
|
||||
self.push(v);
|
||||
},
|
||||
}
|
||||
// [x] -> [], globals[s] = x
|
||||
I::StoreGlobal(s) => {
|
||||
let sym = unsafe { s.to_symbol_unchecked() };
|
||||
let v = self.pop();
|
||||
self.globals.insert(sym, v);
|
||||
},
|
||||
}
|
||||
I::CloseOver(n) => {
|
||||
let n = usize::from(n);
|
||||
let v = std::mem::replace(&mut frame.locals[n], Value::Nil);
|
||||
let v = v.to_cell();
|
||||
frame.locals[n] = v.clone();
|
||||
self.push(v);
|
||||
}
|
||||
I::Closure(n) => {
|
||||
let f = frame.func.chunk.consts[usize::from(n)].clone();
|
||||
let Value::Function(f) = f else {
|
||||
panic!("attempt to build closure from non-closure constant")
|
||||
};
|
||||
let mut f = f.as_ref().clone();
|
||||
|
||||
let captured: Vec<_> = self
|
||||
.pop_n(f.state.len())
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
let Value::Cell(v) = v else {
|
||||
panic!("attempt to build closure from non-cell local");
|
||||
};
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
f.state = captured.into_boxed_slice();
|
||||
self.push(f.into());
|
||||
}
|
||||
I::LoadUpvalue(n) => {
|
||||
let v = frame.func.state[usize::from(n)].clone();
|
||||
self.push(v.borrow().clone());
|
||||
}
|
||||
I::StoreUpvalue(n) => {
|
||||
let v = frame.func.state[usize::from(n)].clone();
|
||||
*v.borrow_mut() = self.pop();
|
||||
}
|
||||
I::ContinueUpvalue(n) => {
|
||||
let v = frame.func.state[usize::from(n)].clone();
|
||||
self.push(Value::Cell(v));
|
||||
}
|
||||
I::LoadClosedLocal(n) => {
|
||||
let Value::Cell(c) = &frame.locals[usize::from(n)] else {
|
||||
panic!("attempt to load from closed non-cell local");
|
||||
};
|
||||
self.push(c.borrow().clone());
|
||||
}
|
||||
I::StoreClosedLocal(n) => {
|
||||
let Value::Cell(c) = &frame.locals[usize::from(n)] else {
|
||||
panic!("attempt to store to closed non-cell local");
|
||||
};
|
||||
*c.borrow_mut() = self.pop();
|
||||
}
|
||||
// [] -> [consts[n]]
|
||||
I::Const(n)
|
||||
=> self.push(frame.func.chunk.consts[usize::from(n)].clone()),
|
||||
I::Const(n) => self.push(frame.func.chunk.consts[usize::from(n)].clone()),
|
||||
// [] -> [nil]
|
||||
I::Nil => self.push(Value::Nil),
|
||||
// [] -> [b]
|
||||
|
@ -271,7 +340,7 @@ impl Vm {
|
|||
I::Symbol(s) => {
|
||||
let sym = unsafe { Symbol::from_id_unchecked(u32::from(s)) };
|
||||
self.push(Value::Symbol(sym));
|
||||
},
|
||||
}
|
||||
// [] -> [n]
|
||||
I::Int(n) => self.push(Value::Int(i64::from(n))),
|
||||
// [x] -> [x,x]
|
||||
|
@ -280,38 +349,44 @@ impl Vm {
|
|||
I::DupTwo => {
|
||||
self.push(self.stack[self.stack.len() - 2].clone());
|
||||
self.push(self.stack[self.stack.len() - 2].clone());
|
||||
},
|
||||
}
|
||||
// [a0,a1...an] -> []
|
||||
I::Drop(n) => for _ in 0..u32::from(n) { self.pop(); },
|
||||
I::Drop(n) => {
|
||||
for _ in 0..u32::from(n) {
|
||||
self.pop();
|
||||
}
|
||||
}
|
||||
// [x,y] -> [y,x]
|
||||
I::Swap => {
|
||||
let len = self.stack.len();
|
||||
self.stack.swap(len - 1, len - 2);
|
||||
},
|
||||
}
|
||||
// [x,y] -> [y op x]
|
||||
I::BinaryOp(op) => {
|
||||
let b = self.pop();
|
||||
let a = self.pop();
|
||||
self.push(binary_op(op, a, b)?);
|
||||
},
|
||||
}
|
||||
// [x] -> [op x]
|
||||
I::UnaryOp(op) => {
|
||||
let a = self.pop();
|
||||
self.push(unary_op(op, a)?);
|
||||
},
|
||||
}
|
||||
// [a0,a1...an] -.> [[a0,a1...an]]
|
||||
I::NewList(n) => {
|
||||
let list = self.pop_n(n as usize);
|
||||
self.push(list.into());
|
||||
},
|
||||
}
|
||||
// [l,a0,a1...an] -.> [l ++ [a0,a1...an]]
|
||||
I::GrowList(n) => {
|
||||
let ext = self.pop_n(n as usize);
|
||||
let list = self.pop();
|
||||
let Value::List(list) = list else { panic!("not a list") };
|
||||
let Value::List(list) = list else {
|
||||
panic!("not a list")
|
||||
};
|
||||
list.borrow_mut().extend(ext);
|
||||
self.push(Value::List(list));
|
||||
},
|
||||
}
|
||||
// [k0,v0...kn,vn] -.> [{k0=v0...kn=vn}]
|
||||
I::NewTable(n) => {
|
||||
let mut table = HashMap::new();
|
||||
|
@ -321,12 +396,14 @@ impl Vm {
|
|||
table.insert(k.try_into()?, v);
|
||||
}
|
||||
self.push(table.into());
|
||||
},
|
||||
}
|
||||
// [t,k0,v0...kn,vn] -> [t ++ {k0=v0...kn=vn}]
|
||||
I::GrowTable(n) => {
|
||||
let mut ext = self.pop_n(2 * n as usize);
|
||||
let table = self.pop();
|
||||
let Value::Table(table) = table else { panic!("not a table") };
|
||||
let Value::Table(table) = table else {
|
||||
panic!("not a table")
|
||||
};
|
||||
let mut table_ref = table.borrow_mut();
|
||||
for _ in 0..n {
|
||||
// can't panic: pop_n checked that ext would have len 2*n
|
||||
|
@ -336,13 +413,13 @@ impl Vm {
|
|||
}
|
||||
drop(table_ref);
|
||||
self.push(Value::Table(table));
|
||||
},
|
||||
}
|
||||
// [ct, idx] -> [ct!idx]
|
||||
I::Index => {
|
||||
let idx = self.pop();
|
||||
let ct = self.pop();
|
||||
self.push(ct.index(idx)?);
|
||||
},
|
||||
}
|
||||
// [ct, idx, v] -> [v], ct!idx = v
|
||||
I::StoreIndex => {
|
||||
let v = self.pop();
|
||||
|
@ -350,27 +427,31 @@ impl Vm {
|
|||
let ct = self.pop();
|
||||
ct.store_index(self, idx, v.clone())?;
|
||||
self.push(v);
|
||||
},
|
||||
}
|
||||
// ip = n
|
||||
I::Jump(n) => {
|
||||
self.check_interrupt()?;
|
||||
frame.ip = usize::from(n);
|
||||
},
|
||||
}
|
||||
// [v] ->, [], if v then ip = n
|
||||
I::JumpTrue(n) => if self.pop().truthy() {
|
||||
I::JumpTrue(n) => {
|
||||
if self.pop().truthy() {
|
||||
self.check_interrupt()?;
|
||||
frame.ip = usize::from(n);
|
||||
},
|
||||
}
|
||||
}
|
||||
// [v] ->, [], if not v then ip = n
|
||||
I::JumpFalse(n) => if !self.pop().truthy() {
|
||||
I::JumpFalse(n) => {
|
||||
if !self.pop().truthy() {
|
||||
self.check_interrupt()?;
|
||||
frame.ip = usize::from(n);
|
||||
},
|
||||
}
|
||||
}
|
||||
// [v] -> [iter(v)]
|
||||
I::IterBegin => {
|
||||
let iter = self.pop().to_iter_function()?;
|
||||
self.push(iter);
|
||||
},
|
||||
}
|
||||
// [i,cell(v)] -> [i,v]
|
||||
// [i,nil] -> [], ip = n
|
||||
I::IterTest(n) => {
|
||||
|
@ -380,19 +461,19 @@ impl Vm {
|
|||
self.pop();
|
||||
frame.ip = usize::from(n);
|
||||
}
|
||||
},
|
||||
}
|
||||
// try_frames.push(t, stack.len())
|
||||
I::BeginTry(t) => {
|
||||
let tryframe = TryFrame {
|
||||
idx: usize::from(t),
|
||||
stack_len: self.stack.len()
|
||||
stack_len: self.stack.len(),
|
||||
};
|
||||
frame.try_frames.push(tryframe);
|
||||
},
|
||||
}
|
||||
// try_frames.pop()
|
||||
I::EndTry => {
|
||||
frame.try_frames.pop().expect("no try to pop");
|
||||
},
|
||||
}
|
||||
// [f,a0,a1...an] -> [f(a0,a1...an)]
|
||||
I::Call(n) => {
|
||||
let n = usize::from(n);
|
||||
|
@ -432,9 +513,8 @@ impl Vm {
|
|||
// before propagating exceptions
|
||||
let res = res?;
|
||||
|
||||
self.stack.push(res);
|
||||
self.push(res);
|
||||
} else if let Value::Function(func) = &args[0] {
|
||||
|
||||
if self.call_stack.len() + 1 >= self.stack_max {
|
||||
throw!(*SYM_CALL_STACK_OVERFLOW, "call stack overflow")
|
||||
}
|
||||
|
@ -445,22 +525,18 @@ impl Vm {
|
|||
} else {
|
||||
unreachable!("already verified by calling get_call_type");
|
||||
}
|
||||
},
|
||||
}
|
||||
// [v] -> [], return v
|
||||
I::Return if frame.root => {
|
||||
return Ok(Some(self.pop()));
|
||||
},
|
||||
I::Return if frame.root => return Ok(Some(self.pop())),
|
||||
// [v] -> [], return v
|
||||
I::Return => {
|
||||
self.check_interrupt()?;
|
||||
*frame = self.call_stack.pop().expect("no root frame");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
|
|
@ -1,52 +1,8 @@
|
|||
use proc_macro::TokenStream;
|
||||
use syn::{parse::Parse, parse_macro_input, ItemFn, LitInt, Token};
|
||||
use quote::quote;
|
||||
|
||||
struct NativeFuncArgs {
|
||||
arity: LitInt,
|
||||
variadic: Option<Token![..]>,
|
||||
}
|
||||
|
||||
impl Parse for NativeFuncArgs {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let arity = input.parse()?;
|
||||
let variadic = input.parse()?;
|
||||
Ok(Self { arity, variadic })
|
||||
}
|
||||
}
|
||||
mod native_func;
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn native_func(input: TokenStream, annotated_item: TokenStream) -> TokenStream {
|
||||
let Ok(itemfn) = syn::parse::<ItemFn>(annotated_item.clone()) else {
|
||||
return annotated_item
|
||||
};
|
||||
let args: NativeFuncArgs = parse_macro_input!(input as NativeFuncArgs);
|
||||
|
||||
let visibility = itemfn.vis;
|
||||
let block = itemfn.block;
|
||||
let name = itemfn.sig.ident;
|
||||
let inputs = itemfn.sig.inputs;
|
||||
let output = itemfn.sig.output;
|
||||
let arity = args.arity;
|
||||
let variadic = args.variadic.is_some();
|
||||
|
||||
assert!(itemfn.sig.constness.is_none(), "item must not be const");
|
||||
assert!(itemfn.sig.asyncness.is_none(), "item must not be async");
|
||||
assert!(itemfn.sig.unsafety.is_none(), "item must not be unsafe");
|
||||
assert!(itemfn.sig.abi.is_none(), "item must not contain an ABI specifier");
|
||||
assert!(itemfn.sig.variadic.is_none(), "item must not be variadic");
|
||||
|
||||
let expanded = quote! {
|
||||
#visibility fn #name() -> ::talc_lang::value::function::NativeFunc {
|
||||
::talc_lang::value::function::NativeFunc {
|
||||
attrs: ::talc_lang::value::function::FuncAttrs{
|
||||
arity: #arity,
|
||||
variadic: #variadic,
|
||||
},
|
||||
func: Box::new(|#inputs| #output #block)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
native_func::native_func(input, annotated_item)
|
||||
}
|
||||
|
|
66
talc-macros/src/native_func.rs
Normal file
66
talc-macros/src/native_func.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{parse::Parse, parse_macro_input, Token};
|
||||
|
||||
struct NativeFuncArgs {
|
||||
arity: syn::LitInt,
|
||||
name: Option<syn::LitStr>,
|
||||
}
|
||||
|
||||
impl Parse for NativeFuncArgs {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let arity = input.parse()?;
|
||||
let t: Option<Token![,]> = input.parse()?;
|
||||
if t.is_some() {
|
||||
let name = input.parse()?;
|
||||
Ok(Self {
|
||||
arity,
|
||||
name: Some(name),
|
||||
})
|
||||
} else {
|
||||
Ok(Self { arity, name: None })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn native_func(input: TokenStream, annotated_item: TokenStream) -> TokenStream {
|
||||
let Ok(itemfn) = syn::parse::<syn::ItemFn>(annotated_item.clone()) else {
|
||||
return annotated_item
|
||||
};
|
||||
let args: NativeFuncArgs = parse_macro_input!(input as NativeFuncArgs);
|
||||
|
||||
let visibility = itemfn.vis;
|
||||
let block = itemfn.block;
|
||||
let name = itemfn.sig.ident;
|
||||
let inputs = itemfn.sig.inputs;
|
||||
let output = itemfn.sig.output;
|
||||
let arity = args.arity;
|
||||
|
||||
let talc_name = match args.name {
|
||||
Some(n) => n.to_token_stream(),
|
||||
None => name.to_token_stream(),
|
||||
};
|
||||
|
||||
assert!(itemfn.sig.constness.is_none(), "item must not be const");
|
||||
assert!(itemfn.sig.asyncness.is_none(), "item must not be async");
|
||||
assert!(itemfn.sig.unsafety.is_none(), "item must not be unsafe");
|
||||
assert!(
|
||||
itemfn.sig.abi.is_none(),
|
||||
"item must not contain an ABI specifier"
|
||||
);
|
||||
assert!(itemfn.sig.variadic.is_none(), "item must not be variadic");
|
||||
|
||||
let expanded = quote! {
|
||||
#visibility fn #name() -> ::talc_lang::value::function::NativeFunc {
|
||||
::talc_lang::value::function::NativeFunc {
|
||||
attrs: ::talc_lang::value::function::FuncAttrs{
|
||||
arity: #arity,
|
||||
name: Some(::talc_lang::symbol::symbol!(#talc_name))
|
||||
},
|
||||
func: Box::new(|#inputs| #output #block)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
|
@ -6,8 +6,8 @@ edition = "2021"
|
|||
[dependencies]
|
||||
talc-lang = { path = "../talc-lang" }
|
||||
talc-macros = { path = "../talc-macros" }
|
||||
lazy_static = "1.4"
|
||||
regex = "1.10"
|
||||
lazy_static = "1.5"
|
||||
regex = "1.11"
|
||||
rand = { version = "0.8", optional = true }
|
||||
|
||||
[features]
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use talc_lang::{exception::{exception, Result}, symbol::SYM_TYPE_ERROR, throw, value::{function::NativeFunc, Value}, vmcall, Vm};
|
||||
use talc_lang::{
|
||||
exception::{exception, Result},
|
||||
symbol::{symbol, SYM_TYPE_ERROR, SYM_VALUE_ERROR},
|
||||
throw,
|
||||
value::{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());
|
||||
|
@ -14,6 +19,9 @@ pub fn load(vm: &mut Vm) {
|
|||
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());
|
||||
vm.set_global_name("pair", pair().into());
|
||||
vm.set_global_name("fst", fst().into());
|
||||
vm.set_global_name("snd", snd().into());
|
||||
}
|
||||
|
||||
#[native_func(2)]
|
||||
|
@ -33,7 +41,7 @@ pub fn pop(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
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"))
|
||||
v.ok_or_else(|| exception!(*SYM_VALUE_ERROR, "attempt to pop empty list"))
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
|
@ -52,7 +60,10 @@ pub fn clear(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
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:#}")
|
||||
_ => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"clear expected list or table, found {col:#}"
|
||||
),
|
||||
}
|
||||
Ok(col)
|
||||
}
|
||||
|
@ -60,7 +71,10 @@ pub fn clear(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
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")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"comparison function should return an integer"
|
||||
)
|
||||
};
|
||||
Ok(ord)
|
||||
}
|
||||
|
@ -79,14 +93,14 @@ fn partition(vals: &mut [Value]) -> (usize, usize) {
|
|||
vals.swap(eq, lt);
|
||||
lt += 1;
|
||||
eq += 1;
|
||||
},
|
||||
}
|
||||
Some(Ordering::Greater) => {
|
||||
vals.swap(eq, gt);
|
||||
gt -= 1;
|
||||
},
|
||||
}
|
||||
Some(Ordering::Equal) | None => {
|
||||
eq += 1;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,14 +121,14 @@ fn partition_by(vals: &mut [Value], by: &Value, vm: &mut Vm) -> Result<(usize, u
|
|||
vals.swap(eq, lt);
|
||||
lt += 1;
|
||||
eq += 1;
|
||||
},
|
||||
}
|
||||
1.. => {
|
||||
vals.swap(eq, gt);
|
||||
gt -= 1;
|
||||
},
|
||||
}
|
||||
0 => {
|
||||
eq += 1;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,8 +138,8 @@ fn partition_by(vals: &mut [Value], by: &Value, vm: &mut Vm) -> Result<(usize, u
|
|||
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);
|
||||
while j > 0 && vals[j - 1] > vals[j] {
|
||||
vals.swap(j, j - 1);
|
||||
j -= 1;
|
||||
}
|
||||
}
|
||||
|
@ -135,11 +149,12 @@ 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())?;
|
||||
let ord =
|
||||
call_comparison(vm, by.clone(), vals[j - 1].clone(), vals[j].clone())?;
|
||||
if ord <= 0 {
|
||||
break;
|
||||
break
|
||||
}
|
||||
vals.swap(j, j-1);
|
||||
vals.swap(j, j - 1);
|
||||
j -= 1;
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +177,7 @@ fn sort_inner(vals: &mut [Value], by: Option<&Value>, vm: &mut Vm) -> Result<()>
|
|||
None => partition(vals),
|
||||
};
|
||||
sort_inner(&mut vals[..lt], by, vm)?;
|
||||
sort_inner(&mut vals[(gt+1)..], by, vm)
|
||||
sort_inner(&mut vals[(gt + 1)..], by, vm)
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
|
@ -199,10 +214,45 @@ pub fn sort_key(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
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"),
|
||||
None => throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"values returned from sort key were incomparable"
|
||||
),
|
||||
}
|
||||
};
|
||||
let nf = NativeFunc::new(Box::new(f), 2).into();
|
||||
let nf = NativeFunc::new(Box::new(f), 2, symbol!("inner[sort_key]")).into();
|
||||
sort_inner(&mut list.borrow_mut(), Some(&nf), vm)?;
|
||||
Ok(list.into())
|
||||
}
|
||||
|
||||
#[native_func(2)]
|
||||
pub fn pair(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, x, y] = unpack_args!(args);
|
||||
Ok(vec![x, y].into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn fst(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, l] = unpack_args!(args);
|
||||
let Value::List(l) = l else {
|
||||
throw!(*SYM_TYPE_ERROR, "fst: expected list")
|
||||
};
|
||||
let l = l.borrow();
|
||||
let [x, _] = l.as_slice() else {
|
||||
throw!(*SYM_VALUE_ERROR, "fst: list must have length 2")
|
||||
};
|
||||
Ok(x.clone())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn snd(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, l] = unpack_args!(args);
|
||||
let Value::List(l) = l else {
|
||||
throw!(*SYM_TYPE_ERROR, "snd: expected list")
|
||||
};
|
||||
let l = l.borrow();
|
||||
let [_, y] = l.as_slice() else {
|
||||
throw!(*SYM_VALUE_ERROR, "snd: list must have length 2")
|
||||
};
|
||||
Ok(y.clone())
|
||||
}
|
||||
|
|
|
@ -1,26 +1,40 @@
|
|||
use talc_lang::{exception::{throw, Exception, Result}, symbol::SYM_TYPE_ERROR, value::Value, Vm};
|
||||
use talc_lang::{
|
||||
exception::{throw, Exception, Result},
|
||||
symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR},
|
||||
value::Value,
|
||||
Vm,
|
||||
};
|
||||
use talc_macros::native_func;
|
||||
|
||||
use crate::{unpack_args, unpack_varargs};
|
||||
use crate::unpack_args;
|
||||
|
||||
#[native_func(1..)]
|
||||
#[native_func(1)]
|
||||
pub fn throw(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let ([_, ty], varargs) = unpack_varargs!(args);
|
||||
let Value::Symbol(ty) = ty else {
|
||||
throw!(*SYM_TYPE_ERROR, "exception type must be a symbol")
|
||||
let [_, arg] = unpack_args!(args);
|
||||
let exc = match arg {
|
||||
Value::Symbol(ty) => Exception::new(ty),
|
||||
Value::List(l) => match l.borrow().as_slice() {
|
||||
[Value::Symbol(ty)] | [Value::Symbol(ty), Value::Nil] => Exception::new(*ty),
|
||||
[Value::Symbol(ty), Value::Nil, data] => {
|
||||
Exception::new_with_data(*ty, data.clone())
|
||||
}
|
||||
[Value::Symbol(ty), Value::String(s)] => {
|
||||
Exception::new_with_msg(*ty, s.clone())
|
||||
}
|
||||
[Value::Symbol(ty), Value::String(s), data] => {
|
||||
Exception::new_with_msg_data(*ty, s.clone(), data.clone())
|
||||
}
|
||||
[] | [_] | [_, _] | [_, _, _] => {
|
||||
throw!(*SYM_TYPE_ERROR, "wrong argument for throw")
|
||||
}
|
||||
[_, _, _, _, ..] => throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"too many elements in list argument for throw"
|
||||
),
|
||||
},
|
||||
_ => throw!(*SYM_TYPE_ERROR, "throw expected symbol or list"),
|
||||
};
|
||||
Err(match &*varargs {
|
||||
[] | [Value::Nil]
|
||||
=> Exception::new(ty),
|
||||
[Value::Nil, data]
|
||||
=> Exception::new_with_data(ty, data.clone()),
|
||||
[Value::String(s)]
|
||||
=> Exception::new_with_msg(ty, s.clone()),
|
||||
[Value::String(s), data]
|
||||
=> Exception::new_with_msg_data(ty, s.clone(), data.clone()),
|
||||
[_] | [_,_] => throw!(*SYM_TYPE_ERROR, "wrong arguments for throw"),
|
||||
[_,_,_,..] => throw!(*SYM_TYPE_ERROR, "too many arguments for throw"),
|
||||
})
|
||||
Err(exc)
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
|
@ -30,6 +44,7 @@ pub fn rethrow(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
if let Some(e) = Exception::from_table(&table) {
|
||||
return Err(e)
|
||||
}
|
||||
throw!(*SYM_VALUE_ERROR, "argument not a valid exception")
|
||||
}
|
||||
throw!(*SYM_TYPE_ERROR, "argument not a valid exception")
|
||||
}
|
||||
|
|
|
@ -1,8 +1,24 @@
|
|||
use std::{cell::RefCell, collections::HashMap, fs::{File, OpenOptions}, io::{BufRead, BufReader, BufWriter, ErrorKind, IntoInnerError, Read, Write}, net::{TcpListener, TcpStream, ToSocketAddrs}, os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd}, process::{Child, Command, Stdio}, time::Duration};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
fs::{File, OpenOptions},
|
||||
io::{BufRead, BufReader, BufWriter, ErrorKind, IntoInnerError, Read, Write},
|
||||
net::{TcpListener, TcpStream, ToSocketAddrs},
|
||||
os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
|
||||
process::{Child, Command, Stdio},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use talc_lang::{exception::Result, lstring::LString, symbol::{Symbol, SYM_TYPE_ERROR}, throw, value::{function::NativeFunc, HashValue, NativeValue, Value}, Vm};
|
||||
use talc_macros::native_func;
|
||||
use lazy_static::lazy_static;
|
||||
use talc_lang::{
|
||||
exception::Result,
|
||||
lstring::LString,
|
||||
symbol::{symbol, Symbol, SYM_TYPE_ERROR, SYM_VALUE_ERROR},
|
||||
throw,
|
||||
value::{function::NativeFunc, HashValue, NativeValue, Value},
|
||||
Vm,
|
||||
};
|
||||
use talc_macros::native_func;
|
||||
|
||||
use crate::{unpack_args, SYM_IO_ERROR};
|
||||
|
||||
|
@ -10,7 +26,6 @@ type OpenOptFn = for<'a> fn(&'a mut OpenOptions, bool) -> &'a mut OpenOptions;
|
|||
lazy_static! {
|
||||
static ref SYM_STD_FILE: Symbol = Symbol::get("std.file");
|
||||
static ref SYM_STD_PROCESS: Symbol = Symbol::get("std.process");
|
||||
|
||||
static ref SYM_R: Symbol = Symbol::get("r");
|
||||
static ref SYM_W: Symbol = Symbol::get("w");
|
||||
static ref SYM_A: Symbol = Symbol::get("a");
|
||||
|
@ -18,7 +33,6 @@ lazy_static! {
|
|||
static ref SYM_C: Symbol = Symbol::get("c");
|
||||
static ref SYM_N: Symbol = Symbol::get("n");
|
||||
static ref SYM_U: Symbol = Symbol::get("u");
|
||||
|
||||
static ref OPEN_OPT_MAP: HashMap<Symbol, OpenOptFn> = {
|
||||
let mut map = HashMap::new();
|
||||
map.insert(*SYM_R, OpenOptions::read as OpenOptFn);
|
||||
|
@ -29,21 +43,17 @@ lazy_static! {
|
|||
map.insert(*SYM_N, OpenOptions::create_new);
|
||||
map
|
||||
};
|
||||
|
||||
static ref SYM_STDIN: Symbol = Symbol::get("stdin");
|
||||
static ref SYM_STDOUT: Symbol = Symbol::get("stdout");
|
||||
static ref SYM_STDERR: Symbol = Symbol::get("stderr");
|
||||
static ref SYM_CD: Symbol = Symbol::get("cd");
|
||||
static ref SYM_DELENV: Symbol = Symbol::get("delenv");
|
||||
static ref SYM_ENV: Symbol = Symbol::get("env");
|
||||
|
||||
static ref SYM_PROCESS: Symbol = Symbol::get("process");
|
||||
|
||||
static ref SYM_INHERIT: Symbol = Symbol::get("inherit");
|
||||
static ref SYM_PIPED: Symbol = Symbol::get("piped");
|
||||
static ref SYM_NULL: Symbol = Symbol::get("null");
|
||||
static ref SYM_ALL: Symbol = Symbol::get("all");
|
||||
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
|
@ -69,15 +79,23 @@ thread_local! {
|
|||
|
||||
#[derive(Debug)]
|
||||
enum BufFile {
|
||||
Buffered { r: BufReader<File>, w: BufWriter<File> },
|
||||
Unbuffered { f: File },
|
||||
Buffered {
|
||||
r: BufReader<File>,
|
||||
w: BufWriter<File>,
|
||||
},
|
||||
Unbuffered {
|
||||
f: File,
|
||||
},
|
||||
}
|
||||
|
||||
impl BufFile {
|
||||
fn new(file: File, buffered: bool) -> std::io::Result<Self> {
|
||||
if buffered {
|
||||
let file2 = file.try_clone()?;
|
||||
Ok(Self::Buffered { r: BufReader::new(file), w: BufWriter::new(file2) })
|
||||
Ok(Self::Buffered {
|
||||
r: BufReader::new(file),
|
||||
w: BufWriter::new(file2),
|
||||
})
|
||||
} else {
|
||||
Ok(Self::Unbuffered { f: file })
|
||||
}
|
||||
|
@ -92,15 +110,17 @@ impl BufFile {
|
|||
Self::Buffered { r, .. } => {
|
||||
let file = r.get_ref().try_clone()?;
|
||||
Self::new(file, true)
|
||||
},
|
||||
Self::Unbuffered { f } => Ok(Self::Unbuffered { f: f.try_clone()? })
|
||||
}
|
||||
Self::Unbuffered { f } => Ok(Self::Unbuffered { f: f.try_clone()? }),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_into_raw_fd(self) -> std::result::Result<RawFd, IntoInnerError<BufWriter<File>>> {
|
||||
fn try_into_raw_fd(
|
||||
self,
|
||||
) -> std::result::Result<RawFd, IntoInnerError<BufWriter<File>>> {
|
||||
match self {
|
||||
BufFile::Buffered { r, w } => {
|
||||
w.into_inner()?.into_raw_fd();
|
||||
let _ = w.into_inner()?.into_raw_fd();
|
||||
Ok(r.into_inner().into_raw_fd())
|
||||
}
|
||||
BufFile::Unbuffered { f } => Ok(f.into_raw_fd()),
|
||||
|
@ -152,9 +172,18 @@ impl From<BufFile> for ValueFile {
|
|||
}
|
||||
|
||||
impl NativeValue for ValueFile {
|
||||
fn get_type(&self) -> Symbol { *SYM_STD_FILE }
|
||||
fn as_any(&self) -> &dyn std::any::Any { self }
|
||||
fn to_lstring(&self, w: &mut LString, _repr: bool) -> std::io::Result<()> {
|
||||
fn get_type(&self) -> Symbol {
|
||||
*SYM_STD_FILE
|
||||
}
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn to_lstring(
|
||||
&self,
|
||||
w: &mut LString,
|
||||
_repr: bool,
|
||||
_recur: &mut Vec<*const ()>,
|
||||
) -> std::io::Result<()> {
|
||||
w.push_str("<file>");
|
||||
Ok(())
|
||||
}
|
||||
|
@ -170,9 +199,18 @@ impl From<Child> for ValueProcess {
|
|||
}
|
||||
|
||||
impl NativeValue for ValueProcess {
|
||||
fn get_type(&self) -> Symbol { *SYM_STD_PROCESS }
|
||||
fn as_any(&self) -> &dyn std::any::Any { self }
|
||||
fn to_lstring(&self, w: &mut LString, _repr: bool) -> std::io::Result<()> {
|
||||
fn get_type(&self) -> Symbol {
|
||||
*SYM_STD_PROCESS
|
||||
}
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn to_lstring(
|
||||
&self,
|
||||
w: &mut LString,
|
||||
_repr: bool,
|
||||
_recur: &mut Vec<*const ()>,
|
||||
) -> std::io::Result<()> {
|
||||
let id = self.0.borrow().id();
|
||||
write!(w, "<process {id}>")
|
||||
}
|
||||
|
@ -201,7 +239,6 @@ pub fn load(vm: &mut Vm) {
|
|||
vm.set_global_name("process_id", process_id().into());
|
||||
}
|
||||
|
||||
|
||||
#[native_func(2)]
|
||||
pub fn open(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, path, opts] = unpack_args!(args);
|
||||
|
@ -218,11 +255,12 @@ pub fn open(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
} else {
|
||||
match OPEN_OPT_MAP.get(&s) {
|
||||
Some(f) => f(&mut oo, true),
|
||||
None => throw!(*SYM_TYPE_ERROR, "invalid option for open")
|
||||
None => throw!(*SYM_VALUE_ERROR, "invalid option for open"),
|
||||
};
|
||||
}
|
||||
},
|
||||
Value::List(l) => for s in l.borrow().iter() {
|
||||
}
|
||||
Value::List(l) => {
|
||||
for s in l.borrow().iter() {
|
||||
let Value::Symbol(s) = s else {
|
||||
throw!(*SYM_TYPE_ERROR, "invalid option for open")
|
||||
};
|
||||
|
@ -231,20 +269,21 @@ pub fn open(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
} else {
|
||||
match OPEN_OPT_MAP.get(s) {
|
||||
Some(f) => f(&mut oo, true),
|
||||
None => throw!(*SYM_TYPE_ERROR, "invalid option for open")
|
||||
None => throw!(*SYM_VALUE_ERROR, "invalid option for open"),
|
||||
};
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Value::Nil => {
|
||||
oo.read(true).write(true);
|
||||
},
|
||||
_ => throw!(*SYM_TYPE_ERROR, "invalid option for open")
|
||||
}
|
||||
_ => throw!(*SYM_TYPE_ERROR, "invalid option for open"),
|
||||
}
|
||||
match oo.open(path.to_os_str()) {
|
||||
Ok(f) => match BufFile::new(f, buffered) {
|
||||
Ok(bf) => Ok(ValueFile::from(bf).into()),
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
}
|
||||
},
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
}
|
||||
}
|
||||
|
@ -256,7 +295,10 @@ pub fn read(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "read expected integer, got {nbytes:#}")
|
||||
};
|
||||
let Ok(nbytes) = usize::try_from(nbytes) else {
|
||||
throw!(*SYM_TYPE_ERROR, "number of bytes to read must be nonnegative")
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"number of bytes to read must be nonnegative"
|
||||
)
|
||||
};
|
||||
let Some(file): Option<&ValueFile> = file.downcast_native() else {
|
||||
throw!(*SYM_TYPE_ERROR, "read expected file, got {file:#}")
|
||||
|
@ -310,14 +352,14 @@ pub fn read_until(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "read_until expected string, got {end:#}")
|
||||
};
|
||||
if end.is_empty() {
|
||||
throw!(*SYM_TYPE_ERROR, "read_until: end string must not be empty")
|
||||
throw!(*SYM_VALUE_ERROR, "read_until: end string must not be empty")
|
||||
}
|
||||
let mut file = file.0.borrow_mut();
|
||||
if let BufFile::Buffered { r, .. } = &mut *file {
|
||||
match read_until_impl(r, end.as_bytes()) {
|
||||
Ok(s) if s.is_empty() => Ok(Value::Nil),
|
||||
Ok(s) => Ok(s.into()),
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}")
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
}
|
||||
} else {
|
||||
throw!(*SYM_TYPE_ERROR, "read_until: file must be buffered")
|
||||
|
@ -336,10 +378,10 @@ pub fn read_line(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
match r.read_until(b'\n', &mut buf) {
|
||||
Ok(0) => Ok(Value::Nil),
|
||||
Ok(_) => Ok(LString::from(buf).into()),
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}")
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
}
|
||||
} else {
|
||||
throw!(*SYM_TYPE_ERROR, "read_line: file must be buffered")
|
||||
throw!(*SYM_VALUE_ERROR, "read_line: file must be buffered")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -398,13 +440,13 @@ pub fn tcp_connect(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "tcp_connect expected string, got {addr:#}")
|
||||
};
|
||||
let Ok(addr) = addr.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "address must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "address must be valid UTF-8")
|
||||
};
|
||||
match TcpStream::connect(addr) {
|
||||
Ok(stream) => match BufFile::from_raw_fd(stream.into_raw_fd(), true) {
|
||||
Ok(bf) => Ok(ValueFile::from(bf).into()),
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
}
|
||||
},
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
}
|
||||
}
|
||||
|
@ -413,16 +455,26 @@ pub fn tcp_connect(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
pub fn tcp_connect_timeout(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, addr, timeout] = unpack_args!(args);
|
||||
let Value::String(addr) = addr else {
|
||||
throw!(*SYM_TYPE_ERROR, "tcp_connect_timeout expected string, got {addr:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"tcp_connect_timeout expected string, got {addr:#}"
|
||||
)
|
||||
};
|
||||
let Ok(addr) = addr.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "address must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "address must be valid UTF-8")
|
||||
};
|
||||
let timeout = match timeout {
|
||||
Value::Int(n) if n >= 0 => Duration::from_secs(n as u64),
|
||||
Value::Float(n) if n >= 0.0 && n <= Duration::MAX.as_secs_f64() => Duration::from_secs_f64(n),
|
||||
Value::Int(_) | Value::Float(_) => throw!(*SYM_TYPE_ERROR, "tcp_connect_timeout: invalid timeout"),
|
||||
_ => throw!(*SYM_TYPE_ERROR, "tcp_connect_timeout expected int or float, got {timeout:#}")
|
||||
Value::Float(n) if n >= 0.0 && n <= Duration::MAX.as_secs_f64() => {
|
||||
Duration::from_secs_f64(n)
|
||||
}
|
||||
Value::Int(_) | Value::Float(_) => {
|
||||
throw!(*SYM_VALUE_ERROR, "tcp_connect_timeout: invalid timeout")
|
||||
}
|
||||
_ => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"tcp_connect_timeout expected int or float, got {timeout:#}"
|
||||
),
|
||||
};
|
||||
let mut addrs = match addr.to_socket_addrs() {
|
||||
Ok(addrs) => addrs,
|
||||
|
@ -435,26 +487,25 @@ pub fn tcp_connect_timeout(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
Ok(stream) => match BufFile::from_raw_fd(stream.into_raw_fd(), true) {
|
||||
Ok(bf) => Ok(ValueFile::from(bf).into()),
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
}
|
||||
},
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn tcp_listen_inner(listener: TcpListener) -> Value {
|
||||
let listener = RefCell::new(listener);
|
||||
let func = move |_: &mut Vm, _: Vec<Value>| {
|
||||
match listener.borrow_mut().accept() {
|
||||
let func = move |_: &mut Vm, _: Vec<Value>| match listener.borrow_mut().accept() {
|
||||
Ok((stream, addr)) => match BufFile::from_raw_fd(stream.into_raw_fd(), true) {
|
||||
Ok(bf) => Ok(vec![
|
||||
ValueFile::from(bf).into(),
|
||||
LString::from(addr.to_string()).into()
|
||||
].into()),
|
||||
LString::from(addr.to_string()).into(),
|
||||
]
|
||||
.into()),
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
}
|
||||
},
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
}
|
||||
};
|
||||
NativeFunc::new(Box::new(func), 0).into()
|
||||
NativeFunc::new(Box::new(func), 0, symbol!("inner[tcp_listen]")).into()
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
|
@ -464,18 +515,16 @@ pub fn tcp_listen(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "tcp_connect expected string, got {addr:#}")
|
||||
};
|
||||
let Ok(addr) = addr.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "address must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "address must be valid UTF-8")
|
||||
};
|
||||
match TcpListener::bind(addr) {
|
||||
Ok(listener) => {
|
||||
let addr = listener.local_addr()
|
||||
let addr = listener
|
||||
.local_addr()
|
||||
.map(|a| LString::from(a.to_string()).into())
|
||||
.unwrap_or(Value::Nil);
|
||||
Ok(vec![
|
||||
tcp_listen_inner(listener),
|
||||
addr,
|
||||
].into())
|
||||
},
|
||||
Ok(vec![tcp_listen_inner(listener), addr].into())
|
||||
}
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
}
|
||||
}
|
||||
|
@ -499,24 +548,32 @@ fn spawn_opt_stdio(
|
|||
let f: &ValueFile = opt.downcast_native().unwrap();
|
||||
let bf = match f.0.borrow().try_clone() {
|
||||
Ok(bf) => bf,
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}")
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
};
|
||||
let fd = match bf.try_into_raw_fd() {
|
||||
Ok(fd) => fd,
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}")
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
};
|
||||
let stdio = unsafe { Stdio::from_raw_fd(fd) };
|
||||
func(proc, stdio)
|
||||
},
|
||||
_ => throw!(*SYM_TYPE_ERROR, "{fname} stdio option expected :inherit, :piped, :null, or file, got {opt:#}")
|
||||
}
|
||||
_ => throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"{fname} stdio option expected :inherit, :piped, :null, or file, got {opt:#}"
|
||||
),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_opt_cd(fname: &str, proc: &mut Command, cd: &Value) -> Result<()> {
|
||||
if let Value::Nil = cd { return Ok(()) }
|
||||
if let Value::Nil = cd {
|
||||
return Ok(())
|
||||
}
|
||||
let Value::String(cd) = cd else {
|
||||
throw!(*SYM_TYPE_ERROR, "{fname} cd option expected string, got {cd:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"{fname} cd option expected string, got {cd:#}"
|
||||
)
|
||||
};
|
||||
proc.current_dir(cd.to_os_str());
|
||||
Ok(())
|
||||
|
@ -527,16 +584,22 @@ fn spawn_opt_delenv(fname: &str, proc: &mut Command, delenv: &Value) -> Result<(
|
|||
Value::Nil => (),
|
||||
Value::Symbol(s) if *s == *SYM_ALL => {
|
||||
proc.env_clear();
|
||||
},
|
||||
Value::List(l) => for e in l.borrow().iter() {
|
||||
}
|
||||
Value::List(l) => {
|
||||
for e in l.borrow().iter() {
|
||||
let Value::String(e) = e else {
|
||||
throw!(*SYM_TYPE_ERROR,
|
||||
"{fname} delenv option expected :all or list of strings, got {delenv:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"{fname} delenv option expected :all or list of strings, got {delenv:#}"
|
||||
)
|
||||
};
|
||||
proc.env_remove(e.to_os_str());
|
||||
},
|
||||
_ => throw!(*SYM_TYPE_ERROR,
|
||||
"{fname} delenv option expected :all or list of strings, got {delenv:#}")
|
||||
}
|
||||
}
|
||||
_ => throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"{fname} delenv option expected :all or list of strings, got {delenv:#}"
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -544,15 +607,21 @@ fn spawn_opt_delenv(fname: &str, proc: &mut Command, delenv: &Value) -> Result<(
|
|||
fn spawn_opt_env(fname: &str, proc: &mut Command, env: &Value) -> Result<()> {
|
||||
match env {
|
||||
Value::Nil => (),
|
||||
Value::Table(t) => for (k, v) in t.borrow().iter() {
|
||||
Value::Table(t) => {
|
||||
for (k, v) in t.borrow().iter() {
|
||||
let Value::String(k) = k.inner() else {
|
||||
throw!(*SYM_TYPE_ERROR,
|
||||
"{fname} efromnv option expected table with string keys, got {env:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"{fname} env option expected table with string keys, got {env:#}"
|
||||
)
|
||||
};
|
||||
proc.env(k.to_os_str(), v.str().to_os_str());
|
||||
},
|
||||
_ => throw!(*SYM_TYPE_ERROR,
|
||||
"{fname} env option expected table with string keys, got {env:#}")
|
||||
}
|
||||
}
|
||||
_ => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"{fname} env option expected table with string keys, got {env:#}"
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -560,7 +629,10 @@ fn spawn_opt_env(fname: &str, proc: &mut Command, env: &Value) -> Result<()> {
|
|||
fn spawn_inner(fname: &str, proc: &mut Command, opts: Value) -> Result<Value> {
|
||||
let (i, o, e) = match opts {
|
||||
Value::Native(ref n) if n.get_type() == *SYM_STD_FILE => {
|
||||
throw!(*SYM_TYPE_ERROR, "{fname} options expected :inherit, :piped, :null, or table, got {opts:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"{fname} options expected :inherit, :piped, :null, or table, got {opts:#}"
|
||||
)
|
||||
}
|
||||
Value::Table(t) => {
|
||||
let t = t.borrow();
|
||||
|
@ -577,17 +649,20 @@ fn spawn_inner(fname: &str, proc: &mut Command, opts: Value) -> Result<Value> {
|
|||
spawn_opt_env(fname, proc, env)?;
|
||||
}
|
||||
|
||||
let i = t.get(&HashValue::from(*SYM_STDIN))
|
||||
let i = t
|
||||
.get(&HashValue::from(*SYM_STDIN))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Value::from(*SYM_INHERIT));
|
||||
let o = t.get(&HashValue::from(*SYM_STDOUT))
|
||||
let o = t
|
||||
.get(&HashValue::from(*SYM_STDOUT))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Value::from(*SYM_INHERIT));
|
||||
let e = t.get(&HashValue::from(*SYM_STDERR))
|
||||
let e = t
|
||||
.get(&HashValue::from(*SYM_STDERR))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Value::from(*SYM_INHERIT));
|
||||
(i, o, e)
|
||||
},
|
||||
}
|
||||
v => (v.clone(), v.clone(), v),
|
||||
};
|
||||
|
||||
|
@ -621,7 +696,10 @@ fn spawn_inner(fname: &str, proc: &mut Command, opts: Value) -> Result<Value> {
|
|||
};
|
||||
table.insert(HashValue::from(*SYM_STDERR), ValueFile::from(bf).into());
|
||||
}
|
||||
table.insert(HashValue::from(*SYM_PROCESS), ValueProcess::from(child).into());
|
||||
table.insert(
|
||||
HashValue::from(*SYM_PROCESS),
|
||||
ValueProcess::from(child).into(),
|
||||
);
|
||||
Ok(table.into())
|
||||
}
|
||||
|
||||
|
@ -632,12 +710,18 @@ pub fn spawn(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "spawn expected string, got {cmd:#}")
|
||||
};
|
||||
let Value::List(args) = args else {
|
||||
throw!(*SYM_TYPE_ERROR, "spawn expected list of strings, got {args:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"spawn expected list of strings, got {args:#}"
|
||||
)
|
||||
};
|
||||
let mut proc = Command::new(cmd.to_os_str());
|
||||
for arg in args.borrow().iter() {
|
||||
let Value::String(arg) = arg else {
|
||||
throw!(*SYM_TYPE_ERROR, "spawn expected list of strings, got list containing {arg:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"spawn expected list of strings, got list containing {arg:#}"
|
||||
)
|
||||
};
|
||||
proc.arg(arg.to_os_str());
|
||||
}
|
||||
|
@ -653,12 +737,10 @@ pub fn system(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
let mut proc;
|
||||
if cfg!(target_os = "windows") {
|
||||
proc = Command::new("cmd");
|
||||
proc.arg("/C")
|
||||
.arg(cmd.to_os_str())
|
||||
proc.arg("/C").arg(cmd.to_os_str())
|
||||
} else {
|
||||
proc = Command::new("sh");
|
||||
proc.arg("-c")
|
||||
.arg(cmd.to_os_str())
|
||||
proc.arg("-c").arg(cmd.to_os_str())
|
||||
};
|
||||
spawn_inner("system", &mut proc, opts)
|
||||
}
|
||||
|
@ -671,11 +753,10 @@ pub fn exit_code(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
};
|
||||
let mut proc = proc.0.borrow_mut();
|
||||
match proc.wait() {
|
||||
Ok(code) => {
|
||||
Ok(code.code()
|
||||
Ok(code) => Ok(code
|
||||
.code()
|
||||
.map(|c| Value::Int(c as i64))
|
||||
.unwrap_or_default())
|
||||
},
|
||||
.unwrap_or_default()),
|
||||
Err(e) => throw!(*SYM_IO_ERROR, "{e}"),
|
||||
}
|
||||
}
|
||||
|
@ -689,4 +770,3 @@ pub fn process_id(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
let proc = proc.0.borrow();
|
||||
Ok((proc.id() as i64).into())
|
||||
}
|
||||
|
||||
|
|
507
talc-std/src/format.rs
Normal file
507
talc-std/src/format.rs
Normal file
|
@ -0,0 +1,507 @@
|
|||
use std::io::Write;
|
||||
|
||||
use talc_lang::{
|
||||
exception::{exception, Result},
|
||||
lstring::{LStr, LString},
|
||||
parser::{parse_int, to_lstring_radix},
|
||||
symbol::SYM_TYPE_ERROR,
|
||||
throw,
|
||||
value::{Complex64, Rational64, Value},
|
||||
Vm,
|
||||
};
|
||||
use talc_macros::native_func;
|
||||
|
||||
use crate::{unpack_args, SYM_FORMAT_ERROR};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum FmtIndex {
|
||||
Imm(usize),
|
||||
Arg(usize),
|
||||
NextArg,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum Align {
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
enum FmtType {
|
||||
#[default]
|
||||
Str,
|
||||
Repr,
|
||||
Hex(bool),
|
||||
Oct,
|
||||
Sex,
|
||||
Bin,
|
||||
Exp(bool),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
enum FmtSign {
|
||||
Plus,
|
||||
Minus,
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
// [idx][:[align][sign][width][.prec][ty]]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
struct FmtCode {
|
||||
arg_idx: Option<usize>,
|
||||
align: Option<(Align, char)>,
|
||||
sign: FmtSign,
|
||||
width: Option<FmtIndex>,
|
||||
prec: Option<FmtIndex>,
|
||||
ty: FmtType,
|
||||
}
|
||||
|
||||
struct FmtParser<'p> {
|
||||
code: &'p LStr,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'p> FmtParser<'p> {
|
||||
fn new(code: &'p LStr) -> Self {
|
||||
Self { code, pos: 0 }
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Option<char> {
|
||||
let c = &self.code[self.pos..].chars().next()?;
|
||||
self.pos += c.len_utf8();
|
||||
Some(*c)
|
||||
}
|
||||
|
||||
fn peek(&mut self) -> Option<char> {
|
||||
self.code[self.pos..].chars().next()
|
||||
}
|
||||
|
||||
fn try_next(&mut self, chs: &[char]) -> Option<char> {
|
||||
if self.peek().is_some_and(|c| chs.contains(&c)) {
|
||||
self.next()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn next_number_str(&mut self) -> &LStr {
|
||||
let start = self.pos;
|
||||
while matches!(self.peek(), Some('0'..='9' | '_')) {
|
||||
self.next();
|
||||
}
|
||||
&self.code[start..self.pos]
|
||||
}
|
||||
|
||||
fn next_usize(&mut self) -> Result<Option<usize>> {
|
||||
let s = self.next_number_str();
|
||||
if s.is_empty() {
|
||||
return Ok(None)
|
||||
}
|
||||
let Ok(i) = parse_int(s, 10) else {
|
||||
throw!(*SYM_FORMAT_ERROR, "code {{{}}}: invalid integer", self.code)
|
||||
};
|
||||
if i < 0 {
|
||||
throw!(
|
||||
*SYM_FORMAT_ERROR,
|
||||
"code {{{}}}: integer may not be negative",
|
||||
self.code
|
||||
)
|
||||
}
|
||||
Ok(Some(i as usize))
|
||||
}
|
||||
|
||||
fn next_fmt_index(&mut self) -> Result<Option<FmtIndex>> {
|
||||
let dollar = self.try_next(&['$']).is_some();
|
||||
let num = self.next_usize()?;
|
||||
match (dollar, num) {
|
||||
(true, None) => Ok(Some(FmtIndex::NextArg)),
|
||||
(true, Some(n)) => Ok(Some(FmtIndex::Arg(n))),
|
||||
(false, None) => Ok(None),
|
||||
(false, Some(n)) => Ok(Some(FmtIndex::Imm(n))),
|
||||
}
|
||||
}
|
||||
|
||||
fn next_align(&mut self) -> Result<Option<(Align, char)>> {
|
||||
let align = match self.peek() {
|
||||
Some('<') => Align::Left,
|
||||
Some('^') => Align::Center,
|
||||
Some('>') => Align::Right,
|
||||
_ => return Ok(None),
|
||||
};
|
||||
self.next();
|
||||
let Some(c) = self.next() else {
|
||||
throw!(
|
||||
*SYM_FORMAT_ERROR,
|
||||
"code {{{}}}: alignment without fill character",
|
||||
self.code
|
||||
)
|
||||
};
|
||||
Ok(Some((align, c)))
|
||||
}
|
||||
|
||||
fn next_sign(&mut self) -> FmtSign {
|
||||
match self.try_next(&['+', '-']) {
|
||||
Some('+') => FmtSign::Plus,
|
||||
Some('-') => FmtSign::Minus,
|
||||
_ => FmtSign::None,
|
||||
}
|
||||
}
|
||||
|
||||
fn next_type(&mut self) -> Result<FmtType> {
|
||||
let ty = match self.peek() {
|
||||
None => return Ok(FmtType::Str),
|
||||
Some('?') => FmtType::Repr,
|
||||
Some('x') => FmtType::Hex(false),
|
||||
Some('X') => FmtType::Hex(true),
|
||||
Some('o') => FmtType::Oct,
|
||||
Some('s') => FmtType::Sex,
|
||||
Some('b') => FmtType::Bin,
|
||||
Some('e') => FmtType::Exp(false),
|
||||
Some('E') => FmtType::Exp(true),
|
||||
_ => throw!(
|
||||
*SYM_FORMAT_ERROR,
|
||||
"code {{{}}}: invalid format type",
|
||||
self.code
|
||||
),
|
||||
};
|
||||
self.next();
|
||||
Ok(ty)
|
||||
}
|
||||
|
||||
fn next_code_end(&mut self) -> Result<()> {
|
||||
match self.peek() {
|
||||
Some(c) => throw!(
|
||||
*SYM_FORMAT_ERROR,
|
||||
"code {{{}}}: expected end of code, found character '{}'",
|
||||
self.code,
|
||||
c
|
||||
),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_format(&mut self) -> Result<FmtCode> {
|
||||
let mut code = FmtCode {
|
||||
arg_idx: self.next_usize()?,
|
||||
..FmtCode::default()
|
||||
};
|
||||
if self.try_next(&[':']).is_none() {
|
||||
self.next_code_end()?;
|
||||
return Ok(code)
|
||||
}
|
||||
code.align = self.next_align()?;
|
||||
code.sign = self.next_sign();
|
||||
code.width = self.next_fmt_index()?;
|
||||
if self.try_next(&['.']).is_some() {
|
||||
code.prec = self.next_fmt_index()?;
|
||||
}
|
||||
code.ty = self.next_type()?;
|
||||
self.next_code_end()?;
|
||||
Ok(code)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_arg<'a>(
|
||||
args: &'a [Value],
|
||||
n: Option<usize>,
|
||||
faidx: &mut usize,
|
||||
) -> Result<&'a Value> {
|
||||
let i = match n {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
let i = *faidx;
|
||||
*faidx += 1;
|
||||
i
|
||||
}
|
||||
};
|
||||
if i >= args.len() {
|
||||
throw!(
|
||||
*SYM_FORMAT_ERROR,
|
||||
"missing arguments: expected at least {}",
|
||||
i + 1
|
||||
)
|
||||
}
|
||||
Ok(&args[i])
|
||||
}
|
||||
|
||||
fn get_val(args: &[Value], i: FmtIndex, faidx: &mut usize) -> Result<usize> {
|
||||
let v = match i {
|
||||
FmtIndex::Imm(n) => return Ok(n),
|
||||
FmtIndex::Arg(n) => get_arg(args, Some(n), faidx)?,
|
||||
FmtIndex::NextArg => get_arg(args, None, faidx)?,
|
||||
};
|
||||
let Value::Int(v) = v else {
|
||||
throw!(
|
||||
*SYM_FORMAT_ERROR,
|
||||
"expected positive integer argument, found {v:#}"
|
||||
)
|
||||
};
|
||||
if *v < 0 {
|
||||
throw!(
|
||||
*SYM_FORMAT_ERROR,
|
||||
"expected positive integer argument, found {v}"
|
||||
)
|
||||
}
|
||||
Ok(*v as usize)
|
||||
}
|
||||
|
||||
fn format_float(
|
||||
f: f64,
|
||||
prec: Option<usize>,
|
||||
ty: FmtType,
|
||||
buf: &mut LString,
|
||||
ty_name: &str,
|
||||
) -> Result<()> {
|
||||
let res = match (prec, ty) {
|
||||
(None, FmtType::Str) => write!(buf, "{}", Value::Float(f)),
|
||||
(None, FmtType::Repr) => write!(buf, "{:#}", Value::Float(f)),
|
||||
(None, FmtType::Exp(true)) => write!(buf, "{:E}", f),
|
||||
(None, FmtType::Exp(false)) => write!(buf, "{:e}", f),
|
||||
(Some(p), FmtType::Str) | (Some(p), FmtType::Repr) => {
|
||||
write!(buf, "{0:.1$}", f, p)
|
||||
}
|
||||
(Some(p), FmtType::Exp(true)) => write!(buf, "{0:.1$E}", f, p),
|
||||
(Some(p), FmtType::Exp(false)) => write!(buf, "{0:.1$e}", f, p),
|
||||
_ => throw!(*SYM_FORMAT_ERROR, "invalid format specifier for {ty_name}"),
|
||||
};
|
||||
res.map_err(|e| exception!(*SYM_FORMAT_ERROR, "{e}"))
|
||||
}
|
||||
|
||||
fn format_complex(
|
||||
cx: Complex64,
|
||||
prec: Option<usize>,
|
||||
ty: FmtType,
|
||||
buf: &mut LString,
|
||||
) -> Result<()> {
|
||||
format_float(cx.re, prec, ty, buf, "complex")?;
|
||||
if cx.im < 0.0 {
|
||||
buf.push_char('-')
|
||||
} else {
|
||||
buf.push_char('+')
|
||||
}
|
||||
format_float(cx.im.abs(), prec, ty, buf, "complex")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_int(
|
||||
n: i64,
|
||||
prec: Option<usize>,
|
||||
ty: FmtType,
|
||||
buf: &mut LString,
|
||||
ty_name: &str,
|
||||
) -> Result<()> {
|
||||
if prec.is_some() {
|
||||
throw!(*SYM_FORMAT_ERROR, "invalid format specifier for {ty_name}")
|
||||
}
|
||||
let res = match ty {
|
||||
FmtType::Str => write!(buf, "{}", Value::Int(n)),
|
||||
FmtType::Repr => write!(buf, "{:#}", Value::Int(n)),
|
||||
FmtType::Hex(caps) => write!(buf, "{}", to_lstring_radix(n, 16, caps)),
|
||||
FmtType::Oct => write!(buf, "{}", to_lstring_radix(n, 8, false)),
|
||||
FmtType::Sex => write!(buf, "{}", to_lstring_radix(n, 6, false)),
|
||||
FmtType::Bin => write!(buf, "{}", to_lstring_radix(n, 2, false)),
|
||||
_ => throw!(*SYM_FORMAT_ERROR, "invalid format specifier for {ty_name}"),
|
||||
};
|
||||
res.map_err(|e| exception!(*SYM_FORMAT_ERROR, "{e}"))
|
||||
}
|
||||
|
||||
fn format_ratio(
|
||||
n: Rational64,
|
||||
prec: Option<usize>,
|
||||
ty: FmtType,
|
||||
buf: &mut LString,
|
||||
) -> Result<()> {
|
||||
format_int(*n.numer(), prec, ty, buf, "ratio")?;
|
||||
buf.push_char('/');
|
||||
format_int(*n.denom(), prec, ty, buf, "ratio")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_value(
|
||||
v: &Value,
|
||||
prec: Option<usize>,
|
||||
ty: FmtType,
|
||||
buf: &mut LString,
|
||||
) -> Result<()> {
|
||||
if prec.is_some() {
|
||||
throw!(*SYM_FORMAT_ERROR, "invalid format specifier for value")
|
||||
}
|
||||
let res = match ty {
|
||||
FmtType::Str => write!(buf, "{}", v),
|
||||
FmtType::Repr => write!(buf, "{:#}", v),
|
||||
_ => throw!(*SYM_FORMAT_ERROR, "invalid format specifier for value"),
|
||||
};
|
||||
res.map_err(|e| exception!(*SYM_FORMAT_ERROR, "{e}"))
|
||||
}
|
||||
|
||||
fn format_string(
|
||||
mut s: &LStr,
|
||||
prec: Option<usize>,
|
||||
ty: FmtType,
|
||||
buf: &mut LString,
|
||||
) -> Result<()> {
|
||||
if let Some(prec) = prec {
|
||||
s = &s[..prec]
|
||||
}
|
||||
let res = match ty {
|
||||
FmtType::Str => {
|
||||
buf.push_lstr(s);
|
||||
Ok(())
|
||||
}
|
||||
FmtType::Repr => write!(buf, "{:?}", s),
|
||||
_ => throw!(*SYM_FORMAT_ERROR, "invalid format specifier for string"),
|
||||
};
|
||||
res.map_err(|e| exception!(*SYM_FORMAT_ERROR, "{e}"))
|
||||
}
|
||||
|
||||
fn pad_str(n: usize, c: char, buf: &mut LString) {
|
||||
for _ in 0..n {
|
||||
buf.push_char(c)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_arg(
|
||||
args: &[Value],
|
||||
faidx: &mut usize,
|
||||
code: &LStr,
|
||||
res: &mut LString,
|
||||
) -> Result<()> {
|
||||
if !code.is_utf8() {
|
||||
throw!(
|
||||
*SYM_FORMAT_ERROR,
|
||||
"code {{{}}}: code contains invalid UTF-8",
|
||||
code
|
||||
)
|
||||
}
|
||||
let fmtcode = FmtParser::new(code).read_format()?;
|
||||
|
||||
let mut buf = LString::new();
|
||||
let fmt_arg = get_arg(args, fmtcode.arg_idx, faidx)?;
|
||||
let prec_val = fmtcode.prec.map(|i| get_val(args, i, faidx)).transpose()?;
|
||||
let width_val = fmtcode.width.map(|i| get_val(args, i, faidx)).transpose()?;
|
||||
|
||||
match fmt_arg {
|
||||
Value::Int(n) => format_int(*n, prec_val, fmtcode.ty, &mut buf, "integer")?,
|
||||
Value::Ratio(n) => format_ratio(*n, prec_val, fmtcode.ty, &mut buf)?,
|
||||
Value::Float(f) => format_float(*f, prec_val, fmtcode.ty, &mut buf, "float")?,
|
||||
Value::Complex(n) => format_complex(*n, prec_val, fmtcode.ty, &mut buf)?,
|
||||
Value::String(s) => format_string(s, prec_val, fmtcode.ty, &mut buf)?,
|
||||
v => format_value(v, prec_val, fmtcode.ty, &mut buf)?,
|
||||
}
|
||||
|
||||
let (sign, final_buf) = match fmtcode.sign {
|
||||
FmtSign::Plus => match buf.byte_get(0) {
|
||||
Some(b'+') => ("+", &buf[1..]),
|
||||
Some(b'-') => ("-", &buf[1..]),
|
||||
_ => ("+", &buf[..]),
|
||||
},
|
||||
FmtSign::Minus => match buf.byte_get(0) {
|
||||
Some(b'-') => ("-", &buf[1..]),
|
||||
_ => ("", &buf[..]),
|
||||
},
|
||||
FmtSign::None => ("", &buf[..]),
|
||||
};
|
||||
res.push_str(sign);
|
||||
|
||||
if let Some(w) = width_val {
|
||||
let w = w.saturating_sub(final_buf.len() + sign.len());
|
||||
match fmtcode.align {
|
||||
Some((Align::Left, c)) => {
|
||||
res.push_lstr(final_buf);
|
||||
pad_str(w, c, res);
|
||||
}
|
||||
Some((Align::Center, c)) => {
|
||||
pad_str((w + 1) / 2, c, res);
|
||||
res.push_lstr(final_buf);
|
||||
pad_str(w / 2, c, res);
|
||||
}
|
||||
Some((Align::Right, c)) => {
|
||||
pad_str(w, c, res);
|
||||
res.push_lstr(final_buf);
|
||||
}
|
||||
None => {
|
||||
let c = if matches!(
|
||||
fmt_arg,
|
||||
Value::Int(_) | Value::Float(_) | Value::Ratio(_) | Value::Complex(_)
|
||||
) && fmtcode.sign != FmtSign::None
|
||||
{
|
||||
'0'
|
||||
} else {
|
||||
' '
|
||||
};
|
||||
pad_str(w, c, res);
|
||||
res.push_lstr(final_buf);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res.push_lstr(final_buf);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[native_func(2, "fmt")]
|
||||
pub fn fmt_(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, fstr, fargs] = unpack_args!(args);
|
||||
let (Value::String(fstr), Value::List(fargs)) = (&fstr, &fargs) else {
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"format expected string and list, got {fstr:#} and {fargs:#}"
|
||||
)
|
||||
};
|
||||
let mut res = LString::new();
|
||||
let mut faidx = 0;
|
||||
let mut i = 0;
|
||||
while i < fstr.len() {
|
||||
let b = fstr.byte_at(i);
|
||||
i += 1;
|
||||
if b == b'}' {
|
||||
let Some(b'}') = fstr.byte_get(i) else {
|
||||
throw!(
|
||||
*SYM_FORMAT_ERROR,
|
||||
"unmatched closing brace at byte {}",
|
||||
i - 1
|
||||
)
|
||||
};
|
||||
i += 1;
|
||||
res.push_byte(b);
|
||||
}
|
||||
if b != b'{' {
|
||||
res.push_byte(b);
|
||||
continue
|
||||
}
|
||||
if i >= fstr.len() {
|
||||
throw!(
|
||||
*SYM_FORMAT_ERROR,
|
||||
"unclosed format specifier at byte {}",
|
||||
i - 1
|
||||
)
|
||||
}
|
||||
if let Some(b'{') = fstr.byte_get(i) {
|
||||
i += 1;
|
||||
res.push_byte(b);
|
||||
continue
|
||||
}
|
||||
let start = i;
|
||||
while i < fstr.len() && fstr.byte_at(i) != b'}' {
|
||||
i += 1;
|
||||
}
|
||||
if i == fstr.len() {
|
||||
throw!(
|
||||
*SYM_FORMAT_ERROR,
|
||||
"unclosed format specifier at byte {}",
|
||||
start - 1
|
||||
)
|
||||
}
|
||||
let code = &fstr[start..i];
|
||||
i += 1;
|
||||
format_arg(&fargs.borrow(), &mut faidx, code, &mut res)?;
|
||||
}
|
||||
Ok(res.into())
|
||||
}
|
||||
|
||||
pub fn load(vm: &mut Vm) {
|
||||
vm.set_global_name("fmt", fmt_().into());
|
||||
}
|
|
@ -1,10 +1,23 @@
|
|||
use std::{io::{BufRead, Write}, os::unix::ffi::OsStrExt, time::{SystemTime, UNIX_EPOCH}};
|
||||
use std::{
|
||||
io::{BufRead, Write},
|
||||
os::unix::ffi::OsStrExt,
|
||||
sync::Mutex,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use talc_lang::{exception::{throw, Result}, lstring::LString, symbol::SYM_TYPE_ERROR, value::Value, vmcall, Vm};
|
||||
use talc_lang::{
|
||||
exception::{throw, Result},
|
||||
lstring::LString,
|
||||
symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR},
|
||||
value::Value,
|
||||
vmcall, Vm,
|
||||
};
|
||||
use talc_macros::native_func;
|
||||
|
||||
use crate::{unpack_args, SYM_IO_ERROR};
|
||||
|
||||
static ENV_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn print(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let res = match &args[1] {
|
||||
|
@ -43,14 +56,22 @@ pub fn readln(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
|
|||
|
||||
#[native_func(0)]
|
||||
pub fn time(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
|
||||
let time = SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards");
|
||||
let time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("time went backwards");
|
||||
Ok(time.as_secs_f64().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())
|
||||
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)]
|
||||
|
@ -70,7 +91,10 @@ pub fn env(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "env expected string, got {key:#}")
|
||||
};
|
||||
if key.is_empty() || key.as_bytes().contains(&b'=') || key.as_bytes().contains(&0) {
|
||||
throw!(*SYM_TYPE_ERROR, "environment variable must not be empty or contain an equals sign or null byte")
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"environment variable must not be empty or contain an equals sign or null byte"
|
||||
)
|
||||
}
|
||||
let val = std::env::var_os(key.to_os_str());
|
||||
match val {
|
||||
|
@ -86,13 +110,25 @@ pub fn setenv(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "setenv expected string, got {key:#}")
|
||||
};
|
||||
if key.is_empty() || key.as_bytes().contains(&b'=') || key.as_bytes().contains(&0) {
|
||||
throw!(*SYM_TYPE_ERROR, "environment variable must not be empty or contain an equals sign or null byte")
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"environment variable must not be empty or contain an equals sign or null byte"
|
||||
)
|
||||
}
|
||||
let val = val.str();
|
||||
if val.as_bytes().contains(&0) {
|
||||
throw!(*SYM_TYPE_ERROR, "environment variable value must not contain a null byte")
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"environment variable value must not contain a null byte"
|
||||
)
|
||||
}
|
||||
{
|
||||
let Ok(guard) = ENV_LOCK.lock() else {
|
||||
throw!(*SYM_VALUE_ERROR, "failed to lock environemnt variables")
|
||||
};
|
||||
unsafe { std::env::set_var(key.to_os_str(), val.to_os_str()) };
|
||||
drop(guard);
|
||||
}
|
||||
std::env::set_var(key.to_os_str(), val.to_os_str());
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
|
||||
|
@ -103,13 +139,21 @@ pub fn delenv(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "delenv expected string, got {key:#}")
|
||||
};
|
||||
if key.is_empty() || key.as_bytes().contains(&b'=') || key.as_bytes().contains(&0) {
|
||||
throw!(*SYM_TYPE_ERROR, "environment variable must not be empty or contain an equals sign or null byte")
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"environment variable must not be empty or contain an equals sign or null byte"
|
||||
)
|
||||
}
|
||||
{
|
||||
let Ok(guard) = ENV_LOCK.lock() else {
|
||||
throw!(*SYM_VALUE_ERROR, "failed to lock environemnt variables")
|
||||
};
|
||||
unsafe { std::env::remove_var(key.to_os_str()) };
|
||||
drop(guard);
|
||||
}
|
||||
std::env::remove_var(key.to_os_str());
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
|
||||
|
||||
pub fn load(vm: &mut Vm) {
|
||||
vm.set_global_name("print", print().into());
|
||||
vm.set_global_name("println", println().into());
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use talc_lang::{symbol::SYM_TYPE_ERROR, exception::Result, throw, value::{function::NativeFunc, range::RangeType, HashValue, Value}, vmcall, vmcalliter, Vm};
|
||||
use talc_lang::{
|
||||
exception::Result,
|
||||
symbol::{symbol, SYM_TYPE_ERROR, SYM_VALUE_ERROR},
|
||||
throw,
|
||||
value::{function::NativeFunc, ops::RatioExt, range::RangeType, HashValue, Value},
|
||||
vmcall, vmcalliter, Vm,
|
||||
};
|
||||
use talc_macros::native_func;
|
||||
|
||||
use crate::{unpack_args, unpack_varargs};
|
||||
use crate::unpack_args;
|
||||
|
||||
pub fn load(vm: &mut Vm) {
|
||||
// begin
|
||||
|
@ -26,7 +32,9 @@ pub fn load(vm: &mut Vm) {
|
|||
|
||||
// join
|
||||
vm.set_global_name("zip", zip().into());
|
||||
vm.set_global_name("zipn", zipn().into());
|
||||
vm.set_global_name("alternate", alternate().into());
|
||||
vm.set_global_name("alternaten", alternaten().into());
|
||||
vm.set_global_name("intersperse", intersperse().into());
|
||||
vm.set_global_name("cartprod", cartprod().into());
|
||||
vm.set_global_name("chain", chain().into());
|
||||
|
@ -48,6 +56,12 @@ pub fn load(vm: &mut Vm) {
|
|||
vm.set_global_name("index_if", index_if().into());
|
||||
vm.set_global_name("find", find().into());
|
||||
vm.set_global_name("count", count().into());
|
||||
vm.set_global_name("mean", mean().into());
|
||||
vm.set_global_name("variance", variance().into());
|
||||
vm.set_global_name("stdev", stdev().into());
|
||||
vm.set_global_name("pvariance", pvariance().into());
|
||||
vm.set_global_name("pstdev", pstdev().into());
|
||||
vm.set_global_name("median", median().into());
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -79,7 +93,7 @@ pub fn pairs(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
Ok(Value::iter_pack(None))
|
||||
}
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[pairs]")).into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
|
@ -87,40 +101,32 @@ 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, _| {
|
||||
Ok(Value::iter_pack(v.borrow_mut().take()))
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
let f = move |_: &mut Vm, _| Ok(Value::iter_pack(v.borrow_mut().take()));
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[once]")).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())
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
let f = move |_: &mut Vm, _| Ok(val.clone());
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[forever]")).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())? {
|
||||
let f = move |vm: &mut Vm, _| match vmcalliter!(vm; iter.clone())? {
|
||||
Some(val) => Ok(vmcall!(vm; map.clone(), val)?),
|
||||
None => Ok(Value::iter_pack(None)),
|
||||
}
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[map]")).into())
|
||||
}
|
||||
|
||||
#[native_func(2)]
|
||||
|
@ -128,16 +134,14 @@ 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())? {
|
||||
let f = move |vm: &mut Vm, _| match vmcalliter!(vm; iter.clone())? {
|
||||
Some(val) => {
|
||||
vmcall!(vm; tee.clone(), val.clone())?;
|
||||
Ok(val)
|
||||
}
|
||||
None => Ok(Value::iter_pack(None)),
|
||||
}
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[tee]")).into())
|
||||
}
|
||||
|
||||
#[native_func(3)]
|
||||
|
@ -155,7 +159,7 @@ pub fn scan(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
*result.borrow_mut() = r.clone();
|
||||
Ok(r)
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[scan]")).into())
|
||||
}
|
||||
|
||||
#[native_func(2)]
|
||||
|
@ -163,8 +167,7 @@ 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 f = move |vm: &mut Vm, _| loop {
|
||||
let Some(next) = vmcalliter!(vm; iter.clone())? else {
|
||||
return Ok(Value::iter_pack(None))
|
||||
};
|
||||
|
@ -172,19 +175,21 @@ pub fn filter(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
if res.truthy() {
|
||||
return Ok(next)
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[filter]")).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")
|
||||
throw!(*SYM_TYPE_ERROR, "take expected integer, got {count:#}")
|
||||
};
|
||||
let Ok(count) = count.try_into() else {
|
||||
throw!(*SYM_TYPE_ERROR, "take expected nonnegative integer")
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"take expected nonnegative integer, got {count:#}"
|
||||
)
|
||||
};
|
||||
let iter = iter.to_iter_function()?;
|
||||
|
||||
|
@ -200,17 +205,20 @@ pub fn take(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
*taken.borrow_mut() += 1;
|
||||
Ok(next)
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[take]")).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")
|
||||
throw!(*SYM_TYPE_ERROR, "count expected integer, got {count:#}")
|
||||
};
|
||||
let Ok(count) = count.try_into() else {
|
||||
throw!(*SYM_TYPE_ERROR, "count expected nonnegative integer")
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"count expected nonnegative integer, got {count:#}"
|
||||
)
|
||||
};
|
||||
let iter = iter.to_iter_function()?;
|
||||
|
||||
|
@ -225,7 +233,7 @@ pub fn skip(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
}
|
||||
vmcall!(vm; iter.clone())
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[skip]")).into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
|
@ -244,10 +252,9 @@ pub fn enumerate(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
Ok(Value::from(vec![n.into(), next]))
|
||||
};
|
||||
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[enumerate]")).into())
|
||||
}
|
||||
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn cycle(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, iter] = unpack_args!(args);
|
||||
|
@ -279,12 +286,15 @@ pub fn cycle(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
}
|
||||
};
|
||||
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[cycle]")).into())
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
enum Step {
|
||||
First, Going, #[default] End,
|
||||
First,
|
||||
Going,
|
||||
#[default]
|
||||
End,
|
||||
}
|
||||
|
||||
#[native_func(2)]
|
||||
|
@ -292,10 +302,13 @@ 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")
|
||||
throw!(*SYM_TYPE_ERROR, "step expected integer, got {by:#}")
|
||||
};
|
||||
if by <= 0 {
|
||||
throw!(*SYM_TYPE_ERROR, "step expected positive integer")
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"step expected positive integer, got {by:#}"
|
||||
)
|
||||
}
|
||||
|
||||
let state = RefCell::new(Step::First);
|
||||
|
@ -306,9 +319,9 @@ pub fn step(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
*state.borrow_mut() = Step::Going;
|
||||
}
|
||||
Ok(Value::iter_pack(res))
|
||||
},
|
||||
}
|
||||
Step::Going => {
|
||||
for _ in 0..(by-1) {
|
||||
for _ in 0..(by - 1) {
|
||||
if vmcall!(vm; iter.clone())? == Value::Nil {
|
||||
return Ok(Value::iter_pack(None))
|
||||
}
|
||||
|
@ -318,10 +331,10 @@ pub fn step(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
};
|
||||
*state.borrow_mut() = Step::Going;
|
||||
Ok(x)
|
||||
},
|
||||
}
|
||||
Step::End => Ok(Value::Nil),
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[step]")).into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
|
@ -339,24 +352,47 @@ pub fn rev(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
*lst.borrow_mut() = Some(l);
|
||||
}
|
||||
Ok(Value::iter_pack(lst.borrow_mut().as_mut().unwrap().pop()))
|
||||
|
||||
};
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[rev]")).into())
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// join
|
||||
//
|
||||
|
||||
|
||||
#[native_func(2..)]
|
||||
#[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 [_, i1, i2] = unpack_args!(args);
|
||||
let i1 = i1.to_iter_function()?;
|
||||
let i2 = i2.to_iter_function()?;
|
||||
|
||||
let f = move |vm: &mut Vm, _| {
|
||||
let mut res = Vec::with_capacity(2);
|
||||
match vmcalliter!(vm; i1.clone())? {
|
||||
Some(v) => res.push(v),
|
||||
None => return Ok(Value::iter_pack(None)),
|
||||
};
|
||||
match vmcalliter!(vm; i2.clone())? {
|
||||
Some(v) => res.push(v),
|
||||
None => return Ok(Value::iter_pack(None)),
|
||||
};
|
||||
Ok(Value::from(res))
|
||||
};
|
||||
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[zip]")).into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn zipn(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, args] = unpack_args!(args);
|
||||
let Value::List(args) = args else {
|
||||
throw!(*SYM_TYPE_ERROR, "zipn expected list, got {args:#}")
|
||||
};
|
||||
let iters = args
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|i| i.clone().to_iter_function())
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let f = move |vm: &mut Vm, _| {
|
||||
let mut res = Vec::with_capacity(iters.len());
|
||||
|
@ -369,22 +405,53 @@ pub fn zip(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
Ok(Value::from(res))
|
||||
};
|
||||
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[zipn]")).into())
|
||||
}
|
||||
|
||||
#[native_func(2..)]
|
||||
#[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 [_, i1, i2] = unpack_args!(args);
|
||||
let i1 = i1.to_iter_function()?;
|
||||
let i2 = i2.to_iter_function()?;
|
||||
|
||||
let state = RefCell::new((false, false));
|
||||
let f = move |vm: &mut Vm, _| {
|
||||
let mut s = state.borrow_mut();
|
||||
if s.1 {
|
||||
return Ok(Value::iter_pack(None))
|
||||
}
|
||||
let n = s.0;
|
||||
s.0 = !s.0;
|
||||
drop(s);
|
||||
let iter = if n { i1.clone() } else { i2.clone() };
|
||||
if let Some(v) = vmcalliter!(vm; iter)? {
|
||||
Ok(v)
|
||||
} else {
|
||||
state.borrow_mut().1 = true;
|
||||
Ok(Value::iter_pack(None))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[alternate]")).into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn alternaten(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, args] = unpack_args!(args);
|
||||
let Value::List(args) = args else {
|
||||
throw!(*SYM_TYPE_ERROR, "alternaten expected list, got {args:#}")
|
||||
};
|
||||
let iters = args
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|i| i.clone().to_iter_function())
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let state = RefCell::new((0, false));
|
||||
let f = move |vm: &mut Vm, _| {
|
||||
let mut s = state.borrow_mut();
|
||||
if s.1 {
|
||||
return Ok(Value::iter_pack(None));
|
||||
return Ok(Value::iter_pack(None))
|
||||
}
|
||||
let n = s.0;
|
||||
s.0 = (s.0 + 1) % iters.len();
|
||||
|
@ -397,12 +464,16 @@ pub fn alternate(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
}
|
||||
};
|
||||
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[alternaten]")).into())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
enum Intersperse {
|
||||
Init, Waiting, HasNext(Value), #[default] End
|
||||
Init,
|
||||
Waiting,
|
||||
HasNext(Value),
|
||||
#[default]
|
||||
End,
|
||||
}
|
||||
|
||||
#[native_func(2)]
|
||||
|
@ -411,8 +482,7 @@ pub fn intersperse(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
let iter = iter.to_iter_function()?;
|
||||
|
||||
let state = RefCell::new(Intersperse::Init);
|
||||
let f = move |vm: &mut Vm, _| {
|
||||
match state.take() {
|
||||
let f = move |vm: &mut Vm, _| match state.take() {
|
||||
Intersperse::Init => {
|
||||
if let Some(v) = vmcalliter!(vm; iter.clone())? {
|
||||
*state.borrow_mut() = Intersperse::Waiting;
|
||||
|
@ -421,7 +491,7 @@ pub fn intersperse(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
*state.borrow_mut() = Intersperse::End;
|
||||
Ok(Value::iter_pack(None))
|
||||
}
|
||||
},
|
||||
}
|
||||
Intersperse::Waiting => {
|
||||
if let Some(v) = vmcalliter!(vm; iter.clone())? {
|
||||
*state.borrow_mut() = Intersperse::HasNext(v);
|
||||
|
@ -430,19 +500,17 @@ pub fn intersperse(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
*state.borrow_mut() = Intersperse::End;
|
||||
Ok(Value::iter_pack(None))
|
||||
}
|
||||
},
|
||||
}
|
||||
Intersperse::HasNext(v) => {
|
||||
*state.borrow_mut() = Intersperse::Waiting;
|
||||
Ok(v)
|
||||
},
|
||||
Intersperse::End => Ok(Value::iter_pack(None)),
|
||||
}
|
||||
Intersperse::End => Ok(Value::iter_pack(None)),
|
||||
};
|
||||
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[intersperse]")).into())
|
||||
}
|
||||
|
||||
|
||||
#[native_func(2)]
|
||||
pub fn chain(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, iter1, iter2] = unpack_args!(args);
|
||||
|
@ -462,7 +530,7 @@ pub fn chain(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
}
|
||||
};
|
||||
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[chain]")).into())
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
|
@ -527,17 +595,13 @@ pub fn cartprod(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
Ok(Value::from(vec![a_res, b_res]))
|
||||
};
|
||||
|
||||
Ok(NativeFunc::new(Box::new(f), 0).into())
|
||||
Ok(NativeFunc::new(Box::new(f), 0, symbol!("iter[cart_prod]")).into())
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// end iteration
|
||||
//
|
||||
|
||||
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn list(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, iter] = unpack_args!(args);
|
||||
|
@ -545,7 +609,7 @@ pub fn list(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
let mut result = Vec::new();
|
||||
while let Some(value) = vmcalliter!(vm; iter.clone())? {
|
||||
result.push(value);
|
||||
};
|
||||
}
|
||||
Ok(result.into())
|
||||
}
|
||||
|
||||
|
@ -556,14 +620,23 @@ pub fn table(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
let mut result = HashMap::new();
|
||||
while let Some(value) = vmcalliter!(vm; iter.clone())? {
|
||||
let Value::List(l) = value else {
|
||||
throw!(*SYM_TYPE_ERROR, "table expected iterator to yield list")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"table expected iterator to yield list, got {value:#}"
|
||||
)
|
||||
};
|
||||
let l = Rc::unwrap_or_clone(l).take();
|
||||
let Ok([k, v]): std::result::Result<[Value; 2], Vec<Value>> = l.try_into() else {
|
||||
throw!(*SYM_TYPE_ERROR, "table expected iterator to yield list of length 2")
|
||||
let mut l = Rc::unwrap_or_clone(l).take();
|
||||
if l.len() != 2 {
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"table: iterator yielded list of length {} (expected 2)",
|
||||
l.len()
|
||||
)
|
||||
};
|
||||
let v = l.pop().unwrap();
|
||||
let k = l.pop().unwrap();
|
||||
result.insert(k.try_into()?, v);
|
||||
};
|
||||
}
|
||||
Ok(result.into())
|
||||
}
|
||||
|
||||
|
@ -574,15 +647,16 @@ pub fn len(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
Value::String(s) => return Ok((s.chars().count() 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()),
|
||||
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())
|
||||
}
|
||||
|
||||
|
@ -701,11 +775,13 @@ pub fn contains(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
for _ in 0.. {
|
||||
match vmcalliter!(vm; iter.clone())? {
|
||||
None => return Ok(Value::Nil),
|
||||
Some(v) => if v == val {
|
||||
Some(v) => {
|
||||
if v == val {
|
||||
return Ok(true.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false.into())
|
||||
}
|
||||
|
||||
|
@ -717,11 +793,13 @@ pub fn index_of(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
for i in 0.. {
|
||||
match vmcalliter!(vm; iter.clone())? {
|
||||
None => return Ok(Value::Nil),
|
||||
Some(v) => if v == val {
|
||||
Some(v) => {
|
||||
if v == val {
|
||||
return Ok(i.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
|
||||
|
@ -733,11 +811,13 @@ pub fn index_if(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
for i in 0.. {
|
||||
match vmcalliter!(vm; iter.clone())? {
|
||||
None => return Ok(Value::Nil),
|
||||
Some(v) => if vmcall!(vm; func.clone(), v)?.truthy() {
|
||||
Some(v) => {
|
||||
if vmcall!(vm; func.clone(), v)?.truthy() {
|
||||
return Ok(i.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
|
||||
|
@ -775,3 +855,111 @@ pub fn count(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
Ok(map.into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn mean(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, iter] = unpack_args!(args);
|
||||
let iter = iter.to_iter_function()?;
|
||||
|
||||
let mut sum = Value::Float(0.0);
|
||||
let mut count = Value::Int(0);
|
||||
while let Some(value) = vmcalliter!(vm; iter.clone())? {
|
||||
sum = (sum + value)?;
|
||||
count = (count + Value::from(1))?;
|
||||
}
|
||||
sum / count
|
||||
}
|
||||
|
||||
fn variance_inner(vm: &mut Vm, iter: Value, pop: bool) -> Result<Value> {
|
||||
let mut m = Value::Float(0.0);
|
||||
let mut s = Value::Float(0.0);
|
||||
let mut k = 1;
|
||||
while let Some(value) = vmcalliter!(vm; iter.clone())? {
|
||||
let old_m = m.clone();
|
||||
m = (m.clone() + ((value.clone() - m.clone())? / Value::Int(k))?)?;
|
||||
s = (s + ((value.clone() - m.clone())? * (value - old_m)?)?)?;
|
||||
k += 1;
|
||||
}
|
||||
s / Value::Int(k - if pop { 1 } else { 2 })
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn variance(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, iter] = unpack_args!(args);
|
||||
let iter = iter.to_iter_function()?;
|
||||
|
||||
variance_inner(vm, iter, false)
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn stdev(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, iter] = unpack_args!(args);
|
||||
let iter = iter.to_iter_function()?;
|
||||
|
||||
let v = variance_inner(vm, iter, false)?;
|
||||
Ok(match v {
|
||||
Value::Int(n) => Value::Float((n as f64).sqrt()),
|
||||
Value::Float(f) => Value::Float(f.sqrt()),
|
||||
Value::Ratio(r) => Value::Float(r.to_f64().sqrt()),
|
||||
Value::Complex(c) => Value::Complex(c.sqrt()),
|
||||
v => throw!(*SYM_TYPE_ERROR, "stdev: cannot square root {v:#}"),
|
||||
})
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn pvariance(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, iter] = unpack_args!(args);
|
||||
let iter = iter.to_iter_function()?;
|
||||
|
||||
variance_inner(vm, iter, true)
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn pstdev(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, iter] = unpack_args!(args);
|
||||
let iter = iter.to_iter_function()?;
|
||||
|
||||
let v = variance_inner(vm, iter, true)?;
|
||||
Ok(match v {
|
||||
Value::Int(n) => Value::Float((n as f64).sqrt()),
|
||||
Value::Float(f) => Value::Float(f.sqrt()),
|
||||
Value::Ratio(r) => Value::Float(r.to_f64().sqrt()),
|
||||
Value::Complex(c) => Value::Complex(c.sqrt()),
|
||||
v => throw!(*SYM_TYPE_ERROR, "stdev: cannot square root {v:#}"),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(PartialEq, PartialOrd)]
|
||||
struct OrdValue(Value);
|
||||
impl std::cmp::Eq for OrdValue {}
|
||||
|
||||
#[allow(clippy::derive_ord_xor_partial_ord)]
|
||||
impl std::cmp::Ord for OrdValue {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.partial_cmp(other).unwrap_or(std::cmp::Ordering::Less)
|
||||
}
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn median(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, iter] = unpack_args!(args);
|
||||
let iter = iter.to_iter_function()?;
|
||||
|
||||
let mut vals = Vec::new();
|
||||
while let Some(value) = vmcalliter!(vm; iter.clone())? {
|
||||
vals.push(OrdValue(value));
|
||||
}
|
||||
let count = vals.len();
|
||||
if count == 0 {
|
||||
Ok(Value::Nil)
|
||||
} else if count % 2 == 0 {
|
||||
let (_, _, hi) = vals.select_nth_unstable(count / 2 - 1);
|
||||
let (_, _, _) = hi.select_nth_unstable(0);
|
||||
let m2 = vals.swap_remove(count / 2);
|
||||
let m1 = vals.swap_remove(count / 2 - 1);
|
||||
(m1.0 + m2.0)? / Value::Int(2)
|
||||
} else {
|
||||
let (_, _, _) = vals.select_nth_unstable(count / 2);
|
||||
let m = vals.swap_remove(count / 2);
|
||||
m.0 / Value::Int(1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
use talc_lang::{symbol::{symbol, Symbol}, Vm};
|
||||
#![allow(clippy::mutable_key_type)]
|
||||
|
||||
pub mod value;
|
||||
use talc_lang::{
|
||||
symbol::{symbol, Symbol},
|
||||
Vm,
|
||||
};
|
||||
|
||||
pub mod collection;
|
||||
pub mod exception;
|
||||
pub mod file;
|
||||
pub mod format;
|
||||
pub mod io;
|
||||
pub mod iter;
|
||||
pub mod exception;
|
||||
pub mod num;
|
||||
pub mod collection;
|
||||
pub mod string;
|
||||
pub mod file;
|
||||
pub mod regex;
|
||||
#[cfg(feature="random")]
|
||||
#[cfg(feature = "random")]
|
||||
pub mod random;
|
||||
pub mod regex;
|
||||
pub mod string;
|
||||
pub mod value;
|
||||
|
||||
pub fn load_all(vm: &mut Vm) {
|
||||
value::load(vm);
|
||||
|
@ -20,17 +26,18 @@ pub fn load_all(vm: &mut Vm) {
|
|||
num::load(vm);
|
||||
io::load(vm);
|
||||
string::load(vm);
|
||||
format::load(vm);
|
||||
regex::load(vm);
|
||||
file::load(vm);
|
||||
#[cfg(feature="random")]
|
||||
#[cfg(feature = "random")]
|
||||
random::load(vm);
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref SYM_IO_ERROR: Symbol = symbol!(io_error);
|
||||
pub static ref SYM_FORMAT_ERROR: Symbol = symbol!(format_error);
|
||||
}
|
||||
|
||||
|
||||
macro_rules! unpack_args {
|
||||
($e:expr) => {
|
||||
($e).try_into().expect("bypassed arity check")
|
||||
|
@ -38,16 +45,3 @@ macro_rules! unpack_args {
|
|||
}
|
||||
|
||||
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,10 +1,17 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use talc_lang::{exception::Result, lstring::LString, parse_int, symbol::{Symbol, SYM_TYPE_ERROR}, throw, value::{ops::RatioExt, Complex64, Value}, Vm};
|
||||
use talc_lang::{
|
||||
exception::Result,
|
||||
parser::{parse_int, to_lstring_radix},
|
||||
symbol::{Symbol, SYM_TYPE_ERROR, SYM_VALUE_ERROR},
|
||||
throw,
|
||||
value::{ops::RatioExt, Complex64, Value},
|
||||
vmcalliter, Vm,
|
||||
};
|
||||
use talc_macros::native_func;
|
||||
|
||||
use crate::{unpack_args, unpack_varargs};
|
||||
use crate::unpack_args;
|
||||
|
||||
lazy_static! {
|
||||
static ref SYM_NAN: Symbol = Symbol::get("nan");
|
||||
|
@ -49,8 +56,8 @@ pub fn load(vm: &mut Vm) {
|
|||
|
||||
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("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());
|
||||
|
@ -61,10 +68,13 @@ pub fn load(vm: &mut Vm) {
|
|||
vm.set_global_name("from_radix", from_radix().into());
|
||||
|
||||
vm.set_global_name("gcd", gcd().into());
|
||||
vm.set_global_name("gcdn", gcdn().into());
|
||||
vm.set_global_name("lcm", lcm().into());
|
||||
vm.set_global_name("lcmn", lcmn().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("totient", totient().into());
|
||||
|
||||
vm.set_global_name("min", min().into());
|
||||
vm.set_global_name("max", max().into());
|
||||
|
@ -96,6 +106,7 @@ pub fn load(vm: &mut Vm) {
|
|||
vm.set_global_name("cbrt", cbrt().into());
|
||||
vm.set_global_name("ln", ln().into());
|
||||
vm.set_global_name("log2", log2().into());
|
||||
vm.set_global_name("log10", log10().into());
|
||||
vm.set_global_name("exp", exp().into());
|
||||
vm.set_global_name("exp2", exp2().into());
|
||||
|
||||
|
@ -119,42 +130,13 @@ pub fn load(vm: &mut Vm) {
|
|||
// base conversions
|
||||
//
|
||||
|
||||
fn to_radix_inner(n: i64, radix: u32, upper: bool) -> LString {
|
||||
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;
|
||||
|
||||
let mut c = char::from_digit(m as u32, radix).unwrap();
|
||||
if upper { c.make_ascii_uppercase(); }
|
||||
result.push(c as u8);
|
||||
if x == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result[begin..].reverse();
|
||||
LString::from(result)
|
||||
}
|
||||
|
||||
|
||||
#[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, false).into())
|
||||
Ok(to_lstring_radix(x, 2, false).into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
|
@ -163,7 +145,7 @@ pub fn sex(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
let Value::Int(x) = x else {
|
||||
throw!(*SYM_TYPE_ERROR, "sex expected integer argument, got {x:#}")
|
||||
};
|
||||
Ok(to_radix_inner(x, 6, false).into())
|
||||
Ok(to_lstring_radix(x, 6, false).into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
|
@ -172,7 +154,7 @@ pub fn oct(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
let Value::Int(x) = x else {
|
||||
throw!(*SYM_TYPE_ERROR, "oct expected integer argument, got {x:#}")
|
||||
};
|
||||
Ok(to_radix_inner(x, 8, false).into())
|
||||
Ok(to_lstring_radix(x, 8, false).into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
|
@ -181,45 +163,63 @@ pub fn hex(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
let Value::Int(x) = x else {
|
||||
throw!(*SYM_TYPE_ERROR, "hex expected integer argument, got {x:#}")
|
||||
};
|
||||
Ok(to_radix_inner(x, 16, false).into())
|
||||
Ok(to_lstring_radix(x, 16, false).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:#}")
|
||||
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")
|
||||
throw!(*SYM_VALUE_ERROR, "to_radix expected radix in range 0..=36")
|
||||
}
|
||||
Ok(to_radix_inner(*x, *radix as u32, false).into())
|
||||
Ok(to_lstring_radix(*x, *radix as u32, false).into())
|
||||
}
|
||||
|
||||
#[native_func(2)]
|
||||
pub fn to_radix_upper(_: &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_upper expected integer arguments, got {x:#} and {radix:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"to_radix_upper expected integer arguments, got {x:#} and {radix:#}"
|
||||
)
|
||||
};
|
||||
if *radix < 2 || *radix > 36 {
|
||||
throw!(*SYM_TYPE_ERROR, "to_radix_upper expected radix in range 0..=36")
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"to_radix_upper expected radix in range 0..=36"
|
||||
)
|
||||
}
|
||||
Ok(to_radix_inner(*x, *radix as u32, true).into())
|
||||
Ok(to_lstring_radix(*x, *radix as u32, true).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:#}")
|
||||
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")
|
||||
throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"from_radix expected radix in range 0..=36"
|
||||
)
|
||||
}
|
||||
match parse_int(s.as_ref(), *radix as u32) {
|
||||
Ok(v) => Ok(v.into()),
|
||||
Err(_) => throw!(*SYM_TYPE_ERROR, "string was not a valid integer in given radix"),
|
||||
Err(_) => throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"string was not a valid integer in given radix"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,7 +229,9 @@ pub fn from_radix(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
|
||||
fn isqrt_inner(mut n: i64) -> i64 {
|
||||
assert!(n >= 0, "isqrt input should be nonnegative");
|
||||
if n < 2 { return n }
|
||||
if n < 2 {
|
||||
return n
|
||||
}
|
||||
|
||||
let mut c = 0;
|
||||
let mut d = 1 << 62;
|
||||
|
@ -242,8 +244,7 @@ fn isqrt_inner(mut n: i64) -> i64 {
|
|||
if n >= c + d {
|
||||
n -= c + d;
|
||||
c = (c >> 1) + d;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
c >>= 1;
|
||||
}
|
||||
d >>= 2;
|
||||
|
@ -272,31 +273,35 @@ pub fn gcd_inner(a: i64, b: i64) -> i64 {
|
|||
}
|
||||
b -= a;
|
||||
if b == 0 {
|
||||
return a << z;
|
||||
return a << z
|
||||
}
|
||||
b >>= b.trailing_zeros();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[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:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"isqrt expected integer argument, got {x:#}"
|
||||
)
|
||||
};
|
||||
if x < 0 {
|
||||
throw!(*SYM_TYPE_ERROR, "isqrt: argument must be positive")
|
||||
throw!(*SYM_VALUE_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:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"isprime expected integer argument, got {x:#}"
|
||||
)
|
||||
};
|
||||
if x < 2 {
|
||||
return Ok(false.into())
|
||||
|
@ -311,59 +316,97 @@ pub fn isprime(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
}
|
||||
let lim = isqrt_inner(x);
|
||||
let mut i = 12;
|
||||
while i <= lim+1 {
|
||||
if x % (i - 1) == 0 { return Ok(false.into()) }
|
||||
if x % (i + 1) == 0 { return Ok(false.into()) }
|
||||
while i <= lim + 1 {
|
||||
if x % (i - 1) == 0 {
|
||||
return Ok(false.into())
|
||||
}
|
||||
if x % (i + 1) == 0 {
|
||||
return Ok(false.into())
|
||||
}
|
||||
i += 6;
|
||||
}
|
||||
Ok(true.into())
|
||||
}
|
||||
|
||||
#[native_func(2..)]
|
||||
#[native_func(2)]
|
||||
pub fn gcd(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let ([_, x, y], rest) = unpack_varargs!(args);
|
||||
let [_, x, y] = unpack_args!(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:#}")
|
||||
throw!(*SYM_TYPE_ERROR, "gcd expected integer argument, got {y:#}")
|
||||
};
|
||||
let mut g = gcd_inner(x, y);
|
||||
for a in rest {
|
||||
Ok(gcd_inner(x, y).into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn gcdn(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, args] = unpack_args!(args);
|
||||
let args = args.to_iter_function()?;
|
||||
|
||||
let mut g = 0;
|
||||
|
||||
while let Some(a) = vmcalliter!(vm; args.clone())? {
|
||||
let Value::Int(a) = a else {
|
||||
throw!(*SYM_TYPE_ERROR, "gcd expected integer argument, got {a:#}")
|
||||
throw!(*SYM_TYPE_ERROR, "gcdn: cannot take gcd with {a:#}")
|
||||
};
|
||||
g = gcd_inner(g, a);
|
||||
}
|
||||
|
||||
Ok(g.into())
|
||||
}
|
||||
|
||||
#[native_func(2..)]
|
||||
#[native_func(2)]
|
||||
pub fn lcm(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let ([_, x, y], rest) = unpack_varargs!(args);
|
||||
let [_, x, y] = unpack_args!(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:#}")
|
||||
throw!(*SYM_TYPE_ERROR, "lcm expected integer argument, got {y:#}")
|
||||
};
|
||||
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);
|
||||
let g = gcd_inner(x, y);
|
||||
if g == 0 {
|
||||
Ok(Value::from(0))
|
||||
} else {
|
||||
(Value::from(x) / Value::from(g))? * Value::from(y)
|
||||
}
|
||||
Ok((x/g * prod).into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn lcmn(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, args] = unpack_args!(args);
|
||||
let args = args.to_iter_function()?;
|
||||
|
||||
let mut l = 1;
|
||||
|
||||
while let Some(a) = vmcalliter!(vm; args.clone())? {
|
||||
let Value::Int(a) = a else {
|
||||
throw!(*SYM_TYPE_ERROR, "lcmn: cannot take lcm with {a:#}")
|
||||
};
|
||||
let g = gcd_inner(l, a);
|
||||
if g == 0 {
|
||||
return Ok(Value::from(0))
|
||||
};
|
||||
let new_l = (Value::from(l).int_div(Value::from(g))? * Value::from(a))?;
|
||||
let Value::Int(new_l) = new_l else {
|
||||
unreachable!("int//int * int != int")
|
||||
};
|
||||
l = new_l;
|
||||
}
|
||||
|
||||
Ok(Value::from(l))
|
||||
}
|
||||
|
||||
#[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:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"factors expected integer argument, got {x:#}"
|
||||
)
|
||||
};
|
||||
let mut factors = Vec::new();
|
||||
if x <= 1 {
|
||||
|
@ -378,7 +421,7 @@ pub fn factors(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
factors.push(Value::Int(3));
|
||||
}
|
||||
let mut i = 5;
|
||||
while x >= i*i {
|
||||
while x >= i * i {
|
||||
while x % i == 0 {
|
||||
x /= i;
|
||||
factors.push(Value::Int(i));
|
||||
|
@ -396,31 +439,76 @@ pub fn factors(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
Ok(factors.into())
|
||||
}
|
||||
|
||||
fn totient_prime(n: &mut u64, p: u64) -> u64 {
|
||||
if *n % p != 0 {
|
||||
return 1
|
||||
}
|
||||
*n /= p;
|
||||
let mut v = p - 1;
|
||||
while *n % p == 0 {
|
||||
*n /= p;
|
||||
v *= p;
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn totient(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, x] = unpack_args!(args);
|
||||
let Value::Int(x) = x else {
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"totient expected integer argument, got {x:#}"
|
||||
)
|
||||
};
|
||||
if x <= 1 {
|
||||
return Ok(1.into())
|
||||
}
|
||||
let mut x = x as u64;
|
||||
let mut totient = 1;
|
||||
if x & 1 == 0 {
|
||||
x >>= 1;
|
||||
}
|
||||
while x & 1 == 0 {
|
||||
x >>= 1;
|
||||
totient <<= 1;
|
||||
}
|
||||
totient *= totient_prime(&mut x, 3);
|
||||
let mut i = 5;
|
||||
while x >= i * i {
|
||||
totient *= totient_prime(&mut x, i);
|
||||
i += 2;
|
||||
totient *= totient_prime(&mut x, i);
|
||||
i += 4;
|
||||
}
|
||||
if x > 1 {
|
||||
totient *= x - 1;
|
||||
}
|
||||
Ok((totient as i64).into())
|
||||
}
|
||||
|
||||
//
|
||||
// numeric operations
|
||||
//
|
||||
|
||||
#[native_func(1..)]
|
||||
#[native_func(2)]
|
||||
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;
|
||||
}
|
||||
}
|
||||
let [_, x, y] = unpack_args!(args);
|
||||
if y < x {
|
||||
Ok(y)
|
||||
} else {
|
||||
Ok(x)
|
||||
}
|
||||
}
|
||||
|
||||
#[native_func(1..)]
|
||||
#[native_func(2)]
|
||||
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;
|
||||
}
|
||||
}
|
||||
let [_, x, y] = unpack_args!(args);
|
||||
if y > x {
|
||||
Ok(y)
|
||||
} else {
|
||||
Ok(x)
|
||||
}
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
|
@ -488,7 +576,7 @@ pub fn sign(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
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:#}"),
|
||||
}
|
||||
}
|
||||
|
@ -497,7 +585,6 @@ pub fn sign(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
// floating-point operations
|
||||
//
|
||||
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn signum(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, x] = unpack_args!(args);
|
||||
|
@ -512,7 +599,10 @@ 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:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"classify expected real argument, got {x:#}"
|
||||
)
|
||||
};
|
||||
Ok(match x.classify() {
|
||||
std::num::FpCategory::Nan => *SYM_NAN,
|
||||
|
@ -520,7 +610,8 @@ pub fn classify(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
std::num::FpCategory::Zero => *SYM_ZERO,
|
||||
std::num::FpCategory::Subnormal => *SYM_SUBNORMAL,
|
||||
std::num::FpCategory::Normal => *SYM_NORMAL,
|
||||
}.into())
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
|
@ -530,7 +621,10 @@ pub fn isnan(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
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:#}"),
|
||||
v => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"isnan expected numeric argument, got {v:#}"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -541,7 +635,10 @@ pub fn isfinite(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
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:#}"),
|
||||
v => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"isfinite expected numeric argument, got {v:#}"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -552,7 +649,10 @@ pub fn isinfinite(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
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:#}"),
|
||||
v => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"isinfinite expected numeric argument, got {v:#}"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -560,7 +660,10 @@ pub fn isinfinite(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
pub fn float_to_bits(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, val] = unpack_args!(args);
|
||||
let Value::Float(f) = val else {
|
||||
throw!(*SYM_TYPE_ERROR, "float_to_bits expected float argument, got {val:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"float_to_bits expected float argument, got {val:#}"
|
||||
)
|
||||
};
|
||||
Ok(Value::Int(f.to_bits() as i64))
|
||||
}
|
||||
|
@ -569,25 +672,28 @@ pub fn float_to_bits(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
pub fn float_of_bits(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, val] = unpack_args!(args);
|
||||
let Value::Int(i) = val else {
|
||||
throw!(*SYM_TYPE_ERROR, "float_of_bits expected integer argument, got {val:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"float_of_bits expected integer argument, got {val:#}"
|
||||
)
|
||||
};
|
||||
Ok(Value::Float(f64::from_bits(i as u64)))
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// 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:#}"),
|
||||
v => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"numer expected rational argument, got {v:#}"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -597,7 +703,10 @@ pub fn denom(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
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:#}"),
|
||||
v => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"denom expected rational argument, got {v:#}"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -605,8 +714,6 @@ pub fn denom(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
// complex operations
|
||||
//
|
||||
|
||||
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn re(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, v] = unpack_args!(args);
|
||||
|
@ -643,34 +750,28 @@ pub fn arg(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
#[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:#}"),
|
||||
}
|
||||
x.abs()
|
||||
}
|
||||
|
||||
#[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::Int(_) => x.clone() * x,
|
||||
Value::Ratio(_) => x.clone() * x,
|
||||
Value::Float(_) => x.clone() * x,
|
||||
Value::Complex(x) => Ok(Value::Float(x.norm_sqr())),
|
||||
x => throw!(*SYM_TYPE_ERROR, "abs_sq expected numeric argument, got {x:#}"),
|
||||
x => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"abs_sq expected numeric argument, got {x:#}"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// continuous operations
|
||||
//
|
||||
|
||||
|
||||
macro_rules! float_func {
|
||||
($name:ident) => {
|
||||
#[native_func(1)]
|
||||
|
@ -679,8 +780,11 @@ macro_rules! float_func {
|
|||
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)),
|
||||
v => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"{} expected numeric argument, got {v:#}",
|
||||
stringify!($name)
|
||||
),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -690,6 +794,7 @@ float_func!(sqrt);
|
|||
float_func!(cbrt);
|
||||
float_func!(ln);
|
||||
float_func!(log2);
|
||||
float_func!(log10);
|
||||
float_func!(exp);
|
||||
float_func!(exp2);
|
||||
|
||||
|
@ -710,10 +815,10 @@ float_func!(atanh);
|
|||
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:#}"),
|
||||
(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:#}"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
use rand::{seq::SliceRandom, Rng};
|
||||
use talc_lang::{symbol::SYM_TYPE_ERROR, throw, value::{range::RangeType, Value}, vmcalliter, Vm, exception::Result};
|
||||
use talc_lang::{
|
||||
exception::Result,
|
||||
symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR},
|
||||
throw,
|
||||
value::{range::RangeType, Value},
|
||||
vmcalliter, Vm,
|
||||
};
|
||||
use talc_macros::native_func;
|
||||
|
||||
use crate::unpack_args;
|
||||
|
@ -22,29 +28,31 @@ pub fn rand_in(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
Value::List(l) => {
|
||||
let l = l.borrow();
|
||||
let Some(v) = l.choose(&mut rand::thread_rng()) else {
|
||||
throw!(*SYM_TYPE_ERROR, "rand_in: empty list")
|
||||
throw!(*SYM_VALUE_ERROR, "rand_in: empty list")
|
||||
};
|
||||
Ok(v.clone())
|
||||
},
|
||||
}
|
||||
Value::Table(t) => {
|
||||
let t = t.borrow();
|
||||
if t.is_empty() {
|
||||
throw!(*SYM_TYPE_ERROR, "rand_in: empty table")
|
||||
throw!(*SYM_VALUE_ERROR, "rand_in: empty table")
|
||||
};
|
||||
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) => {
|
||||
if r.is_empty() {
|
||||
throw!(*SYM_TYPE_ERROR, "rand_in: empty range")
|
||||
throw!(*SYM_VALUE_ERROR, "rand_in: empty range")
|
||||
}
|
||||
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, "rand_in: endless range"),
|
||||
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_VALUE_ERROR, "rand_in: endless range"),
|
||||
}
|
||||
}
|
||||
col => {
|
||||
|
@ -54,7 +62,7 @@ pub fn rand_in(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
values.push(v);
|
||||
}
|
||||
if values.is_empty() {
|
||||
throw!(*SYM_TYPE_ERROR, "rand_in: empty iterator")
|
||||
throw!(*SYM_VALUE_ERROR, "rand_in: empty iterator")
|
||||
}
|
||||
let i = rand::thread_rng().gen_range(0..values.len());
|
||||
let v = values.swap_remove(i);
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use talc_lang::{exception::{exception, Result}, lstring::LString, symbol::{Symbol, SYM_TYPE_ERROR}, throw, value::{NativeValue, Value}, Vm};
|
||||
use talc_macros::native_func;
|
||||
use regex::{Captures, Match, Regex};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::{Captures, Match, Regex};
|
||||
use talc_lang::{
|
||||
exception::{exception, Result},
|
||||
lstring::LString,
|
||||
symbol::{Symbol, SYM_TYPE_ERROR, SYM_VALUE_ERROR},
|
||||
throw,
|
||||
value::{NativeValue, Value},
|
||||
Vm,
|
||||
};
|
||||
use talc_macros::native_func;
|
||||
|
||||
use crate::unpack_args;
|
||||
|
||||
|
@ -18,17 +25,30 @@ lazy_static! {
|
|||
pub struct ValueRegex(Regex);
|
||||
|
||||
impl From<Regex> for ValueRegex {
|
||||
fn from(value: Regex) -> Self { Self(value) }
|
||||
fn from(value: Regex) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ValueRegex> for Regex {
|
||||
fn from(value: ValueRegex) -> Self { value.0 }
|
||||
fn from(value: ValueRegex) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeValue for ValueRegex {
|
||||
fn get_type(&self) -> Symbol { *SYM_STD_REGEX }
|
||||
fn as_any(&self) -> &dyn std::any::Any { self }
|
||||
fn to_lstring(&self, w: &mut LString, repr: bool) -> std::io::Result<()> {
|
||||
fn get_type(&self) -> Symbol {
|
||||
*SYM_STD_REGEX
|
||||
}
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn to_lstring(
|
||||
&self,
|
||||
w: &mut LString,
|
||||
repr: bool,
|
||||
_recur: &mut Vec<*const ()>,
|
||||
) -> std::io::Result<()> {
|
||||
use std::io::Write;
|
||||
if repr {
|
||||
write!(w, "/{}/", self.0)
|
||||
|
@ -58,7 +78,10 @@ fn match_to_value(m: Match) -> Value {
|
|||
Value::new_table(|t| {
|
||||
t.insert((*SYM_START).into(), (m.start() as i64).into());
|
||||
t.insert((*SYM_END).into(), (m.end() as i64).into());
|
||||
t.insert((*SYM_STR).into(), LString::from(m.as_str().to_string()).into());
|
||||
t.insert(
|
||||
(*SYM_STR).into(),
|
||||
LString::from(m.as_str().to_string()).into(),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -73,27 +96,33 @@ fn regex_from<'a>(v: &'a Value, name: &str) -> Result<Cow<'a, Regex>> {
|
|||
match v {
|
||||
Value::String(s) => {
|
||||
let Ok(s) = s.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "regex must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "regex must be valid UTF-8")
|
||||
};
|
||||
Regex::new(s)
|
||||
.map(Cow::Owned)
|
||||
.map_err(|e| exception!(*SYM_TYPE_ERROR, "invalid regex: {e}"))
|
||||
},
|
||||
Value::Native(n) if n.get_type() == *SYM_STD_REGEX => {
|
||||
n.as_any().downcast_ref::<ValueRegex>()
|
||||
.map_err(|e| exception!(*SYM_VALUE_ERROR, "invalid regex: {e}"))
|
||||
}
|
||||
Value::Native(n) if n.get_type() == *SYM_STD_REGEX => n
|
||||
.as_any()
|
||||
.downcast_ref::<ValueRegex>()
|
||||
.map(|vr| Cow::Borrowed(&vr.0))
|
||||
.ok_or_else(|| exception!(
|
||||
*SYM_TYPE_ERROR, "BEES {name} expected string or regex, got {v:#}"))
|
||||
},
|
||||
_ => throw!(*SYM_TYPE_ERROR, "{name} expected string or regex, got {v:#}")
|
||||
.ok_or_else(|| {
|
||||
exception!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"{name} expected string or regex, got {v:#}"
|
||||
)
|
||||
}),
|
||||
_ => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"{name} expected string or regex, got {v:#}"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn _regex(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, re] = unpack_args!(args);
|
||||
regex_from(&re, "regex")
|
||||
.map(|re| ValueRegex(re.into_owned()).into())
|
||||
regex_from(&re, "regex").map(|re| ValueRegex(re.into_owned()).into())
|
||||
}
|
||||
|
||||
#[native_func(2)]
|
||||
|
@ -103,7 +132,7 @@ pub fn matches(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "matches expected string, got {s:#}")
|
||||
};
|
||||
let Ok(s) = s.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "search string must be valid UTF-8")
|
||||
};
|
||||
let re = regex_from(&re, "matches")?;
|
||||
Ok(re.is_match(s).into())
|
||||
|
@ -116,7 +145,7 @@ pub fn match_once(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "match_once expected string, got {s:#}")
|
||||
};
|
||||
let Ok(s) = s.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "search string must be valid UTF-8")
|
||||
};
|
||||
let re = regex_from(&re, "match_once")?;
|
||||
Ok(re.find(s).map_or(Value::Nil, match_to_value))
|
||||
|
@ -129,10 +158,14 @@ pub fn _match(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "match expected string, got {s:#}")
|
||||
};
|
||||
let Ok(s) = s.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "search string must be valid UTF-8")
|
||||
};
|
||||
let re = regex_from(&re, "match")?;
|
||||
Ok(re.find_iter(s).map(match_to_value).collect::<Vec<Value>>().into())
|
||||
Ok(re
|
||||
.find_iter(s)
|
||||
.map(match_to_value)
|
||||
.collect::<Vec<Value>>()
|
||||
.into())
|
||||
}
|
||||
|
||||
#[native_func(2)]
|
||||
|
@ -142,7 +175,7 @@ pub fn captures_once(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "captures_once expected string, got {s:#}")
|
||||
};
|
||||
let Ok(s) = s.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "search string must be valid UTF-8")
|
||||
};
|
||||
let re = regex_from(&re, "captures_once")?;
|
||||
Ok(re.captures(s).map_or(Value::Nil, captures_to_value))
|
||||
|
@ -155,10 +188,14 @@ pub fn captures(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "captures expected string, got {s:#}")
|
||||
};
|
||||
let Ok(s) = s.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "search string must be valid UTF-8")
|
||||
};
|
||||
let re = regex_from(&re, "captures")?;
|
||||
Ok(re.captures_iter(s).map(captures_to_value).collect::<Vec<Value>>().into())
|
||||
Ok(re
|
||||
.captures_iter(s)
|
||||
.map(captures_to_value)
|
||||
.collect::<Vec<Value>>()
|
||||
.into())
|
||||
}
|
||||
|
||||
#[native_func(3)]
|
||||
|
@ -168,13 +205,16 @@ pub fn replace_once(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "replace_once expected string, got {s:#}")
|
||||
};
|
||||
let Value::String(rep) = rep else {
|
||||
throw!(*SYM_TYPE_ERROR, "replace_once expected string or function, got {rep:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"replace_once expected string or function, got {rep:#}"
|
||||
)
|
||||
};
|
||||
let Ok(s) = s.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "search string must be valid UTF-8")
|
||||
};
|
||||
let Ok(rep) = rep.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "replacement string must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "replacement string must be valid UTF-8")
|
||||
};
|
||||
let re = regex_from(&re, "replace_once")?;
|
||||
Ok(LString::from(re.replace(s, rep)).into())
|
||||
|
@ -187,13 +227,16 @@ pub fn replace(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "replace expected string, got {s:#}")
|
||||
};
|
||||
let Value::String(rep) = rep else {
|
||||
throw!(*SYM_TYPE_ERROR, "replace expected string or function, got {rep:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"replace expected string or function, got {rep:#}"
|
||||
)
|
||||
};
|
||||
let Ok(s) = s.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "search string must be valid UTF-8")
|
||||
};
|
||||
let Ok(rep) = rep.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "replacement string must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "replacement string must be valid UTF-8")
|
||||
};
|
||||
let re = regex_from(&re, "replace")?;
|
||||
Ok(LString::from(re.replace_all(s, rep)).into())
|
||||
|
@ -206,13 +249,13 @@ pub fn split_once(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "split_once expected string, got {s:#}")
|
||||
};
|
||||
let Ok(s) = s.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "string to split must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "string to split must be valid UTF-8")
|
||||
};
|
||||
let re = regex_from(&re, "split_once")?;
|
||||
let mut parts = re.splitn(s, 2);
|
||||
let (part1, part2) = (
|
||||
LString::from(parts.next().unwrap_or_default()).into(),
|
||||
LString::from(parts.next().unwrap_or_default()).into()
|
||||
LString::from(parts.next().unwrap_or_default()).into(),
|
||||
);
|
||||
Ok(vec![part1, part2].into())
|
||||
}
|
||||
|
@ -224,12 +267,9 @@ pub fn split(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "split expected string, got {s:#}")
|
||||
};
|
||||
let Ok(s) = s.to_str() else {
|
||||
throw!(*SYM_TYPE_ERROR, "string to split must be valid UTF-8")
|
||||
throw!(*SYM_VALUE_ERROR, "string to split must be valid UTF-8")
|
||||
};
|
||||
let re = regex_from(&re, "split")?;
|
||||
let parts: Vec<Value> = re.split(s)
|
||||
.map(|s| LString::from(s).into())
|
||||
.collect();
|
||||
let parts: Vec<Value> = re.split(s).map(|s| LString::from(s).into()).collect();
|
||||
Ok(parts.into())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
use talc_lang::{exception::Result, lformat, lstring::LString, symbol::SYM_TYPE_ERROR, throw, value::Value, Vm};
|
||||
use talc_lang::{
|
||||
exception::Result,
|
||||
lstring::LString,
|
||||
symbol::{SYM_TYPE_ERROR, SYM_VALUE_ERROR},
|
||||
throw,
|
||||
value::Value,
|
||||
Vm,
|
||||
};
|
||||
use talc_macros::native_func;
|
||||
|
||||
use crate::unpack_args;
|
||||
|
@ -17,7 +24,6 @@ pub fn load(vm: &mut Vm) {
|
|||
vm.set_global_name("to_utf8_lossy", to_utf8_lossy().into());
|
||||
vm.set_global_name("str_to_bytes", str_to_bytes().into());
|
||||
vm.set_global_name("str_of_bytes", str_of_bytes().into());
|
||||
vm.set_global_name("format", _format().into());
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
|
@ -28,10 +34,10 @@ pub fn ord(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
};
|
||||
let mut chars = s.chars();
|
||||
let Some(c) = chars.next() else {
|
||||
throw!(*SYM_TYPE_ERROR, "argument to ord must have length 1")
|
||||
throw!(*SYM_VALUE_ERROR, "argument to ord must have length 1")
|
||||
};
|
||||
if chars.next().is_some() {
|
||||
throw!(*SYM_TYPE_ERROR, "argument to ord must have length 1")
|
||||
throw!(*SYM_VALUE_ERROR, "argument to ord must have length 1")
|
||||
};
|
||||
Ok(Value::Int(c as u32 as i64))
|
||||
}
|
||||
|
@ -43,10 +49,10 @@ pub fn chr(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
throw!(*SYM_TYPE_ERROR, "chr expected integer argument, got {i:#}")
|
||||
};
|
||||
let Ok(i) = u32::try_from(i) else {
|
||||
throw!(*SYM_TYPE_ERROR, "argument to chr is not a valid codepoint")
|
||||
throw!(*SYM_VALUE_ERROR, "argument to chr is not a valid codepoint")
|
||||
};
|
||||
let Some(c) = char::from_u32(i) else {
|
||||
throw!(*SYM_TYPE_ERROR, "argument to chr is not a valid codepoint")
|
||||
throw!(*SYM_VALUE_ERROR, "argument to chr is not a valid codepoint")
|
||||
};
|
||||
Ok(Value::String(LString::from(c).into()))
|
||||
}
|
||||
|
@ -55,7 +61,10 @@ pub fn chr(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
pub fn len_bytes(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, s] = unpack_args!(args);
|
||||
let Value::String(s) = s else {
|
||||
throw!(*SYM_TYPE_ERROR, "len_bytes expected string argument, got {s:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"len_bytes expected string argument, got {s:#}"
|
||||
)
|
||||
};
|
||||
Ok(Value::Int(s.len() as i64))
|
||||
}
|
||||
|
@ -93,8 +102,10 @@ pub fn starts_with(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
let res = match (d, pre) {
|
||||
(Value::List(d), Value::List(pre)) => d.borrow().starts_with(&pre.borrow()),
|
||||
(Value::String(d), Value::String(pre)) => d.starts_with(&pre),
|
||||
(d, pre) => throw!(*SYM_TYPE_ERROR,
|
||||
"starts_with expected two lists or strings, got {d:#} and {pre:#}")
|
||||
(d, pre) => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"starts_with expected two lists or strings, got {d:#} and {pre:#}"
|
||||
),
|
||||
};
|
||||
Ok(res.into())
|
||||
}
|
||||
|
@ -105,8 +116,10 @@ pub fn ends_with(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
let res = match (d, suf) {
|
||||
(Value::List(d), Value::List(suf)) => d.borrow().ends_with(&suf.borrow()),
|
||||
(Value::String(d), Value::String(suf)) => d.ends_with(&suf),
|
||||
(d, suf) => throw!(*SYM_TYPE_ERROR,
|
||||
"ends_with expected two lists or strings, got {d:#} and {suf:#}")
|
||||
(d, suf) => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"ends_with expected two lists or strings, got {d:#} and {suf:#}"
|
||||
),
|
||||
};
|
||||
Ok(res.into())
|
||||
}
|
||||
|
@ -115,7 +128,10 @@ pub fn ends_with(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
pub fn is_utf8(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, s] = unpack_args!(args);
|
||||
let Value::String(s) = s else {
|
||||
throw!(*SYM_TYPE_ERROR, "is_utf8 expected string argument, got {s:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"is_utf8 expected string argument, got {s:#}"
|
||||
)
|
||||
};
|
||||
Ok(s.is_utf8().into())
|
||||
}
|
||||
|
@ -124,7 +140,10 @@ pub fn is_utf8(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
pub fn to_utf8(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, s] = unpack_args!(args);
|
||||
let Value::String(s) = s else {
|
||||
throw!(*SYM_TYPE_ERROR, "is_utf8 expected string argument, got {s:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"is_utf8 expected string argument, got {s:#}"
|
||||
)
|
||||
};
|
||||
if s.is_utf8() {
|
||||
Ok(s.into())
|
||||
|
@ -137,7 +156,10 @@ pub fn to_utf8(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
pub fn to_utf8_lossy(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, s] = unpack_args!(args);
|
||||
let Value::String(s) = s else {
|
||||
throw!(*SYM_TYPE_ERROR, "is_utf8 expected string argument, got {s:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"is_utf8 expected string argument, got {s:#}"
|
||||
)
|
||||
};
|
||||
Ok(s.to_utf8_lossy().into())
|
||||
}
|
||||
|
@ -146,7 +168,10 @@ pub fn to_utf8_lossy(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
pub fn str_to_bytes(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, s] = unpack_args!(args);
|
||||
let Value::String(s) = s else {
|
||||
throw!(*SYM_TYPE_ERROR, "str_to_bytes expected string argument, got {s:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"str_to_bytes expected string argument, got {s:#}"
|
||||
)
|
||||
};
|
||||
Ok(s.as_bytes()
|
||||
.iter()
|
||||
|
@ -159,47 +184,21 @@ pub fn str_to_bytes(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
pub fn str_of_bytes(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, b] = unpack_args!(args);
|
||||
let Value::List(b) = b else {
|
||||
throw!(*SYM_TYPE_ERROR, "str_of_bytes expected list argument, got {b:#}")
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"str_of_bytes expected list argument, got {b:#}"
|
||||
)
|
||||
};
|
||||
let bytes: Vec<u8> = b.borrow().iter()
|
||||
let bytes: Vec<u8> = b
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|v| match v {
|
||||
Value::Int(i) if (0..=255).contains(i) => Ok(*i as u8),
|
||||
_ => throw!(*SYM_TYPE_ERROR, "str_of_bytes expected list of integers in 0..=255"),
|
||||
}).collect::<Result<Vec<u8>>>()?;
|
||||
_ => throw!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"str_of_bytes expected list of integers in 0..=255"
|
||||
),
|
||||
})
|
||||
.collect::<Result<Vec<u8>>>()?;
|
||||
Ok(LString::from(bytes).into())
|
||||
}
|
||||
|
||||
#[native_func(2)]
|
||||
pub fn _format(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, fstr, fargs] = unpack_args!(args);
|
||||
let (Value::String(fstr), Value::List(fargs)) = (&fstr, &fargs) else {
|
||||
throw!(*SYM_TYPE_ERROR, "format expected string and list, got {fstr:#} and {fargs:#}")
|
||||
};
|
||||
let mut res = LString::new();
|
||||
let mut bytes = fstr.bytes();
|
||||
let mut faidx = 0;
|
||||
while let Some(b) = bytes.next() {
|
||||
if b == b'%' {
|
||||
match bytes.next() {
|
||||
Some(b'%') => res.push_byte(b'%'),
|
||||
Some(b @ (b'#' | b'?')) => {
|
||||
let fargs = fargs.borrow();
|
||||
let Some(a) = fargs.get(faidx) else {
|
||||
throw!(*SYM_TYPE_ERROR, "not enough args for format string")
|
||||
};
|
||||
faidx += 1;
|
||||
if b == b'?' {
|
||||
res += &lformat!("{a:#}");
|
||||
} else {
|
||||
res += &lformat!("{a}");
|
||||
}
|
||||
|
||||
},
|
||||
_ => throw!(*SYM_TYPE_ERROR, "invalid format code")
|
||||
}
|
||||
} else {
|
||||
res.push_byte(b);
|
||||
}
|
||||
}
|
||||
Ok(res.into())
|
||||
}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use talc_lang::{exception::{exception, Result}, lformat, parse_float, parse_int, symbol::SYM_TYPE_ERROR, throw, value::{ops::RatioExt, HashValue, Rational64, Value}, Vm};
|
||||
use talc_lang::{
|
||||
exception::{exception, Result},
|
||||
lformat,
|
||||
parser::{parse_float, parse_int},
|
||||
symbol::{symbol, Symbol, SYM_TYPE_ERROR, SYM_VALUE_ERROR},
|
||||
throw,
|
||||
value::{ops::RatioExt, HashValue, Rational64, Value},
|
||||
Vm,
|
||||
};
|
||||
use talc_macros::native_func;
|
||||
|
||||
use crate::unpack_args;
|
||||
|
@ -14,18 +22,27 @@ pub fn load(vm: &mut Vm) {
|
|||
vm.set_global_name("str", str_().into());
|
||||
vm.set_global_name("repr", repr().into());
|
||||
|
||||
vm.set_global_name("symbol_name", symbol_name().into());
|
||||
vm.set_global_name("symbol_of", symbol_of().into());
|
||||
vm.set_global_name("symbol_exists", symbol_exists().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());
|
||||
vm.set_global_name("cell_take", cell_take().into());
|
||||
|
||||
vm.set_global_name("func_state", func_state().into());
|
||||
vm.set_global_name("func_arity", func_arity().into());
|
||||
vm.set_global_name("func_name", func_name().into());
|
||||
vm.set_global_name("apply", apply().into());
|
||||
vm.set_global_name("compile", compile().into());
|
||||
}
|
||||
|
||||
//
|
||||
// types
|
||||
//
|
||||
|
||||
|
||||
#[native_func(1)]
|
||||
#[native_func(1, "type")]
|
||||
pub fn type_(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, val] = unpack_args!(args);
|
||||
Ok(val.get_type().into())
|
||||
|
@ -40,7 +57,7 @@ pub fn is(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
Ok((val.get_type() == ty).into())
|
||||
}
|
||||
|
||||
#[native_func(2)]
|
||||
#[native_func(2, "as")]
|
||||
pub fn as_(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, val, ty] = unpack_args!(args);
|
||||
let Value::Symbol(ty) = ty else {
|
||||
|
@ -64,59 +81,75 @@ pub fn as_(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
(Value::Ratio(x), b"complex") => Ok(Value::Complex(x.to_f64().into())),
|
||||
(Value::Float(x), b"int") => Ok(Value::Int(x as i64)),
|
||||
(Value::Float(x), b"ratio") => {
|
||||
let r = Rational64::approximate_float(x)
|
||||
.ok_or_else(|| exception!(*SYM_TYPE_ERROR, "float {x:?} could not be converted to ratio"))?;
|
||||
let r = Rational64::approximate_float(x).ok_or_else(|| {
|
||||
exception!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"float {x:?} could not be converted to ratio"
|
||||
)
|
||||
})?;
|
||||
Ok(Value::Ratio(r))
|
||||
}
|
||||
(Value::Float(x), b"complex") => Ok(Value::Complex(x.into())),
|
||||
|
||||
(Value::String(s), b"int")
|
||||
=> parse_int(s.as_ref(), 10)
|
||||
(Value::String(s), b"int") => parse_int(s.as_ref(), 10)
|
||||
.map(i64::into)
|
||||
.map_err(|_| exception!(*SYM_TYPE_ERROR, "could not parse {s:#} as integer")),
|
||||
.map_err(|_| exception!(*SYM_VALUE_ERROR, "could not parse {s:#} as integer")),
|
||||
|
||||
(Value::String(s), b"float")
|
||||
=> parse_float(s.as_ref())
|
||||
(Value::String(s), b"float") => parse_float(s.as_ref())
|
||||
.map(f64::into)
|
||||
.map_err(|_| exception!(*SYM_TYPE_ERROR, "could not parse {s:#} as float")),
|
||||
.map_err(|_| exception!(*SYM_VALUE_ERROR, "could not parse {s:#} as float")),
|
||||
|
||||
(v, _) => throw!(*SYM_TYPE_ERROR,
|
||||
"cannot convert value of type {} to type {}", v.get_type().name(), ty.name())
|
||||
(v, _) => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"cannot convert value of type {} to type {}",
|
||||
v.get_type().name(),
|
||||
ty.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::Nil
|
||||
| Value::Bool(_)
|
||||
| Value::Symbol(_)
|
||||
| Value::Int(_)
|
||||
| Value::Ratio(_)
|
||||
| Value::Float(_)
|
||||
| Value::Complex(_)
|
||||
| Value::Range(_)
|
||||
| Value::String(_) => Ok(value),
|
||||
Value::Cell(c) => {
|
||||
let c = Rc::unwrap_or_clone(c).take();
|
||||
let c = copy_inner(c)?;
|
||||
Ok(RefCell::new(c).into())
|
||||
},
|
||||
}
|
||||
Value::List(l) => {
|
||||
let l = Rc::unwrap_or_clone(l).take();
|
||||
let v: Result<Vec<Value>> = l.into_iter()
|
||||
.map(copy_inner)
|
||||
.collect();
|
||||
let v: Result<Vec<Value>> = l.into_iter().map(copy_inner).collect();
|
||||
Ok(v?.into())
|
||||
},
|
||||
}
|
||||
Value::Table(t) => {
|
||||
let t = Rc::unwrap_or_clone(t).take();
|
||||
let v: Result<HashMap<HashValue, Value>> = t.into_iter()
|
||||
let v: Result<HashMap<HashValue, Value>> = t
|
||||
.into_iter()
|
||||
.map(|(k, v)| copy_inner(v).map(|v| (k, v)))
|
||||
.collect();
|
||||
Ok(v?.into())
|
||||
},
|
||||
}
|
||||
Value::Native(ref n) => match n.copy_value()? {
|
||||
Some(x) => Ok(x),
|
||||
None => throw!(*SYM_TYPE_ERROR,
|
||||
"cannot copy value of type {}", value.get_type().name())
|
||||
}
|
||||
_ => throw!(*SYM_TYPE_ERROR,
|
||||
"cannot copy value of type {}", value.get_type().name())
|
||||
None => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"cannot copy value of type {}",
|
||||
value.get_type().name()
|
||||
),
|
||||
},
|
||||
_ => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"cannot copy value of type {}",
|
||||
value.get_type().name()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,8 +163,7 @@ pub fn copy(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
// strings
|
||||
//
|
||||
|
||||
|
||||
#[native_func(1)]
|
||||
#[native_func(1, "str")]
|
||||
pub fn str_(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, val] = unpack_args!(args);
|
||||
Ok(lformat!("{val}").into())
|
||||
|
@ -144,9 +176,42 @@ pub fn repr(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
}
|
||||
|
||||
//
|
||||
// cells
|
||||
// symbols
|
||||
//
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn symbol_name(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, val] = unpack_args!(args);
|
||||
let Value::Symbol(s) = val else {
|
||||
throw!(*SYM_TYPE_ERROR, "symbol_name: expected symbol")
|
||||
};
|
||||
Ok(s.name().into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn symbol_of(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, val] = unpack_args!(args);
|
||||
let Value::String(s) = val else {
|
||||
throw!(*SYM_TYPE_ERROR, "symbol_of: expected string")
|
||||
};
|
||||
Ok(Symbol::get(s.as_ref()).into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn symbol_exists(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, val] = unpack_args!(args);
|
||||
let Value::String(s) = val else {
|
||||
throw!(*SYM_TYPE_ERROR, "symbol_of: expected string")
|
||||
};
|
||||
match Symbol::try_get(s.as_ref()) {
|
||||
Some(s) => Ok(s.into()),
|
||||
None => Ok(Value::Nil),
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// cells
|
||||
//
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn cell(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
|
@ -158,7 +223,7 @@ pub fn cell(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
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")
|
||||
throw!(*SYM_TYPE_ERROR, "uncell: value is not a cell")
|
||||
};
|
||||
Ok(Rc::unwrap_or_clone(cell).into_inner())
|
||||
}
|
||||
|
@ -167,7 +232,7 @@ pub fn uncell(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
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")
|
||||
throw!(*SYM_TYPE_ERROR, "cell_replace: value is not a cell")
|
||||
};
|
||||
Ok(cell.replace(value))
|
||||
}
|
||||
|
@ -176,8 +241,90 @@ pub fn cell_replace(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
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")
|
||||
throw!(*SYM_TYPE_ERROR, "cell_take: value is not a cell")
|
||||
};
|
||||
Ok(cell.replace(Value::Nil))
|
||||
}
|
||||
|
||||
//
|
||||
// functions
|
||||
//
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn func_state(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, func] = unpack_args!(args);
|
||||
match func {
|
||||
Value::NativeFunc(_) => Ok(Value::Nil),
|
||||
Value::Function(f) => {
|
||||
let l: Vec<Value> = f.state.iter().map(|v| Value::Cell(v.clone())).collect();
|
||||
Ok(l.into())
|
||||
}
|
||||
_ => throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"closure_state: {func:#} is not a talc function"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn func_arity(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, func] = unpack_args!(args);
|
||||
let Some(attrs) = func.func_attrs() else {
|
||||
throw!(*SYM_TYPE_ERROR, "closure_state: {func:#} is not a function")
|
||||
};
|
||||
Ok((attrs.arity as i64).into())
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn func_name(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, func] = unpack_args!(args);
|
||||
let Some(attrs) = func.func_attrs() else {
|
||||
throw!(*SYM_TYPE_ERROR, "closure_state: {func:#} is not a function")
|
||||
};
|
||||
if let Some(name) = attrs.name {
|
||||
Ok(name.into())
|
||||
} else {
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
}
|
||||
|
||||
#[native_func(2)]
|
||||
pub fn apply(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, func, lst] = unpack_args!(args);
|
||||
if func.func_attrs().is_none() {
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"apply: first argument must be a function, found {func:#}"
|
||||
)
|
||||
}
|
||||
let Value::List(l) = lst else {
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"apply: second argument must be a list, found {lst:#}"
|
||||
)
|
||||
};
|
||||
let mut args = l.borrow().clone();
|
||||
args.insert(0, func.clone());
|
||||
vm.call_value(func, args)
|
||||
}
|
||||
|
||||
#[native_func(1)]
|
||||
pub fn compile(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
let [_, src] = unpack_args!(args);
|
||||
let Value::String(src) = src else {
|
||||
throw!(
|
||||
*SYM_TYPE_ERROR,
|
||||
"compile: argument must be a string, found {src:#}"
|
||||
)
|
||||
};
|
||||
let src = src.to_str().map_err(|e| {
|
||||
exception!(
|
||||
*SYM_VALUE_ERROR,
|
||||
"compile: argument must be valid unicode ({e})"
|
||||
)
|
||||
})?;
|
||||
let ast = talc_lang::parser::parse(src)
|
||||
.map_err(|e| exception!(symbol!("parse_error"), "{e}"))?;
|
||||
let func = talc_lang::compiler::compile(&ast, None);
|
||||
Ok(func.into())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue