├── test.txt ├── .gitignore ├── assemble.spl ├── echo.spl ├── rust-test.spl ├── net.spl ├── Cargo.toml ├── spl.vim ├── test.sasm ├── install.spl ├── src ├── stdlib.rs ├── mutex.rs ├── main.rs ├── oxidizer │ ├── splrs.rs │ └── mod.rs ├── lib.rs ├── dyn_fns.rs ├── stream.rs ├── lexer.rs ├── sasm.rs ├── std_fns.rs └── runtime.rs ├── repl.spl ├── Cargo.lock ├── messaging.spl ├── stream.spl ├── http.spl ├── test.spl ├── iter.spl ├── README.md └── std.spl /test.txt: -------------------------------------------------------------------------------- 1 | hi 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /assemble.spl: -------------------------------------------------------------------------------- 1 | 2 | func main { mega | with args ; 3 | 2 args:get write-file-sasm println 4 | 0 5 | } 6 | -------------------------------------------------------------------------------- /echo.spl: -------------------------------------------------------------------------------- 1 | func main { mega | with args ; 2 | 2 args:iter:skip =args 3 | { | with item ; 4 | item print 5 | " " print 6 | } args:foreach 7 | "\n" print 8 | 0 exit 9 | } 10 | -------------------------------------------------------------------------------- /rust-test.spl: -------------------------------------------------------------------------------- 1 | func main { | 2 | 1 rusty-test _str println 3 | 0 4 | } 5 | func sply-test { mega | 6 | 1 7 | } 8 | func rusty-test @rust !{ 9 | println!("hii"); 10 | let v = #pop:Mega#; 11 | #call(sply-test)# 12 | let v2 = #pop:Mega#; 13 | #push(v + v2)# 14 | } 15 | 16 | -------------------------------------------------------------------------------- /net.spl: -------------------------------------------------------------------------------- 1 | 2 | "the net namespace allows any other constructs and namespaces in it. They can be added"; 3 | "using \"Name\" net:register after which net:Name is available to become a construct"; 4 | construct net namespace { 5 | ; 6 | register { | with name this ; 7 | name "net" this register-field 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spl" 3 | version = "0.1.8" 4 | edition = "2021" 5 | description = "Stack Pogramming Language: A simple, concise scripting language." 6 | license = "MIT" 7 | repository = "https://github.com/tudbut/spl" 8 | authors = ["TudbuT"] 9 | 10 | [dependencies] 11 | readformat = "0.1" 12 | once_cell = "1.17" 13 | multicall = "0.1.4" 14 | -------------------------------------------------------------------------------- /spl.vim: -------------------------------------------------------------------------------- 1 | 2 | if exists("b:current_syntax") 3 | finish 4 | endif 5 | 6 | syn match Comment /".*?";/ 7 | syn match Number /\<[0-9._]*\>/ 8 | syn match Function /\/ 13 | syn match Identifier /[a-zA-Z0-9_\-]\+:\|\/ 14 | syn match String /"[^"]*"/ 15 | syn match Typedef /\ " print readln dyn-read exec2 "\n" print 10 | } 11 | with { with err ; 12 | err:message dup null eq if { 13 | pop 14 | "Uncaught error." 15 | } err:trace 16 | 17 | with msg trace ; 18 | program-name dup if { 19 | program-name print " panicked at:" println 20 | } not if { 21 | "Program panicked at:" println 22 | } 23 | &println trace:foreach 24 | "\nPanic message:" println 25 | " " print msg println 26 | "\nRecovering." println 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "multicall" 7 | version = "0.1.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "428f6ba17d0c927e57c15a86cf5d7d07a2f35b3fbf15b1eb36b7075459e150a3" 10 | 11 | [[package]] 12 | name = "once_cell" 13 | version = "1.17.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" 16 | 17 | [[package]] 18 | name = "readformat" 19 | version = "0.1.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "b03f7fbd470aa8b3ad163c85cce8bccfc11cc9c44ef12da0a4eddd98bd307352" 22 | 23 | [[package]] 24 | name = "spl" 25 | version = "0.1.8" 26 | dependencies = [ 27 | "multicall", 28 | "once_cell", 29 | "readformat", 30 | ] 31 | -------------------------------------------------------------------------------- /messaging.spl: -------------------------------------------------------------------------------- 1 | "messaging bus, aka event bus" 2 | 3 | construct messaging namespace { 4 | Message 5 | Bus 6 | } 7 | 8 | construct messaging:Message { 9 | name 10 | content 11 | ; 12 | construct { this | with name content this ; 13 | name this:=name 14 | content this:=content 15 | this 16 | } 17 | } 18 | 19 | construct messaging:Bus { 20 | subscribers 21 | ; 22 | construct { this | with this ; 23 | MicroMap:new this:=subscribers 24 | this 25 | } 26 | subscribe { | with message callable this ; 27 | def entry message this:subscribers:get-or-create-entry =entry 28 | entry:1 null eq if { 29 | List:new entry:=1 30 | } 31 | callable entry:1:push 32 | } 33 | publish { | with message this ; 34 | message gettype messaging:Message eq not if { 35 | message null messaging:Message:new =message 36 | } 37 | def entry message:name this:subscribers:get =entry 38 | entry null eq not if { 39 | { | with it ; 40 | message it call 41 | } entry:foreach 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/mutex.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Display, 3 | sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}, 4 | }; 5 | 6 | #[derive(Debug)] 7 | pub struct Mut(RwLock); 8 | 9 | impl Mut { 10 | pub const fn new(obj: T) -> Mut { 11 | Mut(RwLock::new(obj)) 12 | } 13 | 14 | pub fn lock_ro(&self) -> RwLockReadGuard { 15 | self.0.read().unwrap() 16 | } 17 | 18 | pub fn lock(&self) -> RwLockWriteGuard { 19 | self.0.write().unwrap() 20 | } 21 | } 22 | 23 | impl Display for Mut 24 | where 25 | T: Display, 26 | { 27 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 28 | self.0.read().unwrap().fmt(f) 29 | } 30 | } 31 | 32 | impl Clone for Mut 33 | where 34 | T: Clone, 35 | { 36 | fn clone(&self) -> Self { 37 | Self(RwLock::new(self.0.read().unwrap().clone())) 38 | } 39 | } 40 | 41 | impl PartialEq for Mut 42 | where 43 | T: PartialEq, 44 | { 45 | fn eq(&self, other: &Self) -> bool { 46 | self.0.read().unwrap().eq(&other.0.read().unwrap()) 47 | } 48 | } 49 | 50 | impl Eq for Mut where T: Eq {} 51 | 52 | impl PartialOrd for Mut 53 | where 54 | T: PartialOrd, 55 | { 56 | fn partial_cmp(&self, other: &Self) -> Option { 57 | self.0.read().unwrap().partial_cmp(&other.0.read().unwrap()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use spl::{find_in_splpath, lex, oxidizer::RustAppBuilder, start_file}; 2 | 3 | use std::{env::args, fs}; 4 | 5 | fn main() { 6 | let mut args = args().skip(1); 7 | let arg = &args 8 | .next() 9 | .unwrap_or_else(|| find_in_splpath("repl.spl").expect("no file to be run")); 10 | if arg == "--build" || arg == "--run" { 11 | let file = args.next().unwrap(); 12 | let data = fs::read_to_string(file.clone()).expect("unable to read specified file"); 13 | let build_only = arg == "--build"; 14 | if build_only { 15 | println!("Building SPL with specified natives file..."); 16 | } 17 | let mut builder = RustAppBuilder::new(); 18 | if build_only { 19 | if let Some(name) = args.next() { 20 | builder.set_name(name); 21 | } 22 | println!("Embedding source..."); 23 | } 24 | builder.add_source(file.to_owned(), data.to_owned()); 25 | if build_only { 26 | println!("Preparing rust code..."); 27 | } 28 | builder.prepare(lex(data.to_owned()).expect("invalid SPL in natives file.")); 29 | if build_only { 30 | println!("Building..."); 31 | } 32 | let app = builder.build(build_only).unwrap(); 33 | if build_only { 34 | println!("Built! Binary is {}", app.get_binary()); 35 | } else { 36 | let mut args: Vec = args.collect(); 37 | args.insert(0, file); 38 | let mut command = app.execute(args).unwrap(); 39 | app.delete(); 40 | command.wait().unwrap(); 41 | } 42 | 43 | return; 44 | } 45 | if let Err(x) = start_file(arg) { 46 | println!("{x:?}"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /stream.spl: -------------------------------------------------------------------------------- 1 | "The SPL stream is an IO construct used to read and write to "; 2 | "some external thing, for example a file or a TCP socket."; 3 | 4 | "All functions here are encapsulations of their native counterparts."; 5 | 6 | "Examples:"; 7 | "def tcp 'localhost' 8080 StreamType:tcp:create =tcp"; 8 | "def file 'test.txt' 1 StreamType:file:create =file 'hi':to-bytes file:write-exact; file:close null =file"; 9 | 10 | construct Stream { 11 | id 12 | ; 13 | construct { this | with type this ; 14 | type new-stream this:=id 15 | this 16 | } 17 | read-one { mega | with this ; 18 | def buf 1 anew =buf 19 | while { buf this:id read-stream not } { } 20 | 0 buf:get _mega 21 | } 22 | "the buffer is written to in-place."; 23 | read { mega [int] | with buf this ; 24 | buf gettype "mega" eq if { buf anew =buf } 25 | buf this:id read-stream buf 26 | } 27 | "the buffer is written to in-place."; 28 | read-exact { [int] | with buf this ; 29 | buf gettype "mega" eq if { buf anew =buf } 30 | buf this:id read-all-stream buf 31 | } 32 | read-to-end { [int] | with buf this ; 33 | def full 0 anew =full 34 | buf gettype "mega" eq if { buf anew =buf } 35 | def read 36 | while { buf this:id read-stream pop _mega dup =read } { 37 | full (0 read buf:sub) aadd =full 38 | } 39 | full 40 | } 41 | write { mega | with buf this ; 42 | buf this:id write-stream 43 | } 44 | write-exact { | with buf this ; 45 | buf this:id write-all-stream 46 | } 47 | flush { | with this ; 48 | this:id flush-stream 49 | } 50 | close { | with this ; 51 | this:id close-stream 52 | } 53 | } 54 | 55 | construct StreamType { 56 | id 57 | ; 58 | construct { this | with id this ; 59 | id this:=id 60 | this 61 | } 62 | create { Stream | with this ; 63 | this:id Stream:new 64 | } 65 | } 66 | 67 | def stream-types 0 anew =stream-types 68 | 69 | construct _StreamType { 70 | ; 71 | construct { this | with this ; 72 | { | with type ; 73 | "type StreamType:new this:="; 74 | (type StreamType:new) (this ("=" type concat)) dyn-objcall 75 | } stream-types:foreach 76 | this 77 | } 78 | } 79 | 80 | func register-stream-type { | with id ; 81 | [ stream-types:to-stack id ] =stream-types 82 | id _StreamType dyn-def-field 83 | } 84 | 85 | "tcp" register-stream-type 86 | "udp" register-stream-type 87 | "file" register-stream-type 88 | 89 | func StreamTypes { _StreamType | 90 | _StreamType:new 91 | } 92 | -------------------------------------------------------------------------------- /src/oxidizer/splrs.rs: -------------------------------------------------------------------------------- 1 | use readformat::readf1; 2 | 3 | use super::RustFunction; 4 | 5 | /// Parses a #-expression and returns the string to be inserted in its place. 6 | fn parse_hash_expr(s: String, name: &str) -> String { 7 | if &s == "pop" { 8 | return "stack.pop().lock_ro()".to_owned(); 9 | } 10 | if &s == "pop_mut" { 11 | return "stack.pop().lock()".to_owned(); 12 | } 13 | if &s == "pop:Array" { 14 | return format!("{{ require_array_on_stack!(tmp, stack, {name:?}); tmp }}"); 15 | } 16 | if &s == "pop_mut:Array" { 17 | return format!("{{ require_mut_array_on_stack!(tmp, stack, {name:?}); tmp }}"); 18 | } 19 | if let Some(s) = readf1("pop:{}", &s) { 20 | return format!("{{ require_on_stack!(tmp, {s}, stack, {name:?}); tmp }}"); 21 | } 22 | if let Some(s) = readf1("push({})", &s) { 23 | return format!("stack.push(({s}).spl());"); 24 | } 25 | if let Some(s) = readf1("call({})", &s) { 26 | return format!("stack.call(&stack.get_func({s:?}.to_owned())?)?;"); 27 | } 28 | panic!("invalid #-expr - this error will be handled in the future") 29 | } 30 | 31 | pub fn to_rust(name: String, mut splrs: String) -> RustFunction { 32 | RustFunction { 33 | content: { 34 | loop { 35 | let mut did_anything = false; 36 | 37 | let mut rs = String::new(); 38 | let mut in_str = false; 39 | let mut escaping = false; 40 | let mut hash_expr = None; 41 | let mut brace = 0; 42 | for c in splrs.chars() { 43 | if in_str { 44 | if escaping { 45 | escaping = false; 46 | } else if c == '"' { 47 | in_str = false; 48 | } 49 | if c == '\\' { 50 | escaping = true; 51 | } 52 | } else if c == '"' { 53 | in_str = true; 54 | } 55 | if !in_str && c == '#' && hash_expr.is_none() { 56 | hash_expr = Some(String::new()); 57 | did_anything = true; 58 | continue; 59 | } 60 | if let Some(ref mut expr) = hash_expr { 61 | if c == '#' && brace == 0 { 62 | rs += &parse_hash_expr(expr.to_owned(), &name); 63 | hash_expr = None; 64 | continue; 65 | } 66 | expr.push(c); 67 | if c == '(' { 68 | brace += 1; 69 | } 70 | if c == ')' { 71 | brace -= 1; 72 | } 73 | continue; 74 | } 75 | rs += String::from(c).as_str(); 76 | } 77 | if !did_anything { 78 | break rs; 79 | } 80 | splrs = rs; 81 | } 82 | }, 83 | fn_name: name, 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /http.spl: -------------------------------------------------------------------------------- 1 | "#stream.spl" import 2 | "#net.spl" import 3 | 4 | "http" net:register 5 | 6 | construct net:http namespace { 7 | Request 8 | Response 9 | help 10 | } 11 | 12 | construct net:http:Request { 13 | host port 14 | method path 15 | headers 16 | body 17 | ; 18 | construct { this | with host port method path this ; 19 | host this:=host 20 | port this:=port 21 | method this:=method 22 | path this:=path 23 | List:new this:=headers 24 | "" this:=body 25 | this 26 | } 27 | add-header { this | with header this ; 28 | header this:headers:push 29 | this 30 | } 31 | set-body { this | with body this ; 32 | body this:=body 33 | this 34 | } 35 | send { net:http:Response | with this ; 36 | def stream this:host this:port StreamTypes:tcp:create =stream 37 | def response net:http:Response:new =response 38 | 39 | this:method:to-bytes stream:write-exact; 40 | " " :to-bytes stream:write-exact; 41 | this:path:to-bytes stream:write-exact; 42 | " HTTP/1.0\r\n" :to-bytes stream:write-exact; 43 | 44 | "Host: " :to-bytes stream:write-exact; 45 | this:host:to-bytes stream:write-exact; 46 | "\r\nConnection: Close\r\nUser-Agent: http.spl v0.1 2023-03 (spl@mail.tudbut.de)\r\n" 47 | :to-bytes stream:write-exact; 48 | 49 | { | with header ; 50 | header:to-bytes stream:write-exact; 51 | "\r\n" stream:write-exact; 52 | } this:headers:foreach 53 | 54 | "Content-Length: " :to-bytes stream:write-exact; 55 | def body this:body:to-bytes =body 56 | body:len _str:to-bytes stream:write-exact; 57 | "\r\n\r\n" :to-bytes stream:write-exact; 58 | 59 | body stream:write-exact; 60 | stream:flush; 61 | 62 | def response 1024 stream:read-to-end =response 63 | 64 | response net:http:Response:new:read-from-bytes 65 | 66 | stream:close; 67 | } 68 | } 69 | 70 | construct net:http:help namespace { 71 | ; 72 | assert-str { | with expected iter _ ; 73 | [ { | pop iter:next } (expected _array):len:foreach ] _str 74 | expected _str 75 | eq not if { 76 | "Expected " expected concat throw 77 | } 78 | } 79 | until-str { str | with expected iter _ ; 80 | def match 0 =match 81 | def bytes expected:to-bytes =bytes 82 | [ 83 | while { match bytes:len eq not } { 84 | iter:next dup (match bytes:get) eq dup if { 85 | match ++ =match 86 | } not if { 87 | 0 =match 88 | } 89 | } 90 | { | pop pop } match:foreach 91 | ] _str 92 | } 93 | } 94 | 95 | construct net:http:Response { 96 | version 97 | state-num state-msg 98 | headers 99 | body 100 | ; 101 | construct { this | with this ; 102 | MicroMap:new this:=headers 103 | "" this:=body 104 | this 105 | } 106 | read-from-bytes { this | with bytes this ; 107 | use net:http:help 108 | bytes:iter =bytes 109 | "HTTP/" bytes help:assert-str 110 | " " bytes help:until-str this:=version 111 | " " bytes help:until-str _mega this:=state-num 112 | "\r\n" bytes help:until-str this:=state-msg 113 | while { "\r\n" bytes help:until-str dup "" eq not } { 114 | def iter ": " swap:split:iter =iter 115 | ( 116 | iter:next ": " 117 | iter:join 118 | ) this:headers:set; 119 | } pop 120 | 0 ("Content-Length" this:headers:get _mega) bytes:collect:sub this:=body 121 | this 122 | } 123 | content-type { str | with this ; 124 | "Content-Type" this:headers:get 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /test.spl: -------------------------------------------------------------------------------- 1 | 2 | "#stream.spl" import 3 | "#http.spl" import 4 | "#messaging.spl" import 5 | 6 | "SPL tester" =program-name 7 | 8 | func main { int | with args ; 9 | def thing 10 | 11 | 1 anew =thing 12 | 13 | "hi" 0 thing:unwrap:set; 14 | 15 | def thing2 thing:unwrap List:new:from =thing2 16 | 17 | "world" thing2:unwrap:push 18 | "hello" 0 thing2:unwrap:insert 19 | 20 | "printing first two words of 'hello hi world' (should be 'hello hi')" println 21 | " " print 22 | 0 thing2:unwrap:get print " " print 23 | 1 thing2:unwrap:get println 24 | "removing hello" println 25 | thing2:pop-front; 26 | "printing first two words again" println 27 | " " print 28 | 0 thing2:unwrap:get print " " print 29 | 1 thing2:unwrap:get println 30 | 31 | "" println 32 | "testing closures and func-ptrs" println 33 | 34 | def thingy 35 | "heya1" =thingy 36 | "thingy println" dyn-read call 37 | 38 | "heya2" =thingy 39 | { | 40 | thingy println 41 | } call 42 | 43 | def ptr 44 | &println =ptr 45 | "ptr works" ptr call 46 | &&println =ptr 47 | "ptr-ptr works" ptr call call 48 | thingy:&unwrap =ptr 49 | "unwrap-ptr works" ptr call println 50 | thingy:&&unwrap =ptr 51 | "unwrap-ptr-ptr works" ptr call call println 52 | 53 | 54 | "" println 55 | "testing if" println 56 | 57 | def a "test" =a 58 | def b "test" =b 59 | a b eq dup if { 60 | a " is equal to " b concat concat println 61 | } not if { 62 | a " is not equal to " b concat concat panic 63 | } 64 | 65 | a b assert-eq; 66 | 67 | "" println 68 | "testing ranges & iterators: (0..30@5) + 1" println 69 | 70 | def range 5 (0 30 Range:new):set-step =range 71 | 72 | range:iter 73 | { | 1 + } swap:map 74 | { | _str println } swap:foreach 75 | 76 | "" println 77 | "testing Iter:sum of 5 10s" println 78 | 79 | 0 5 Range:new:iter 80 | { | pop 10 } swap:map 81 | :sum 82 | _str println 83 | 84 | "" println 85 | "testing MicroMap" println 86 | 87 | def map MicroMap:new =map 88 | "hey" "hello" map:set; 89 | "helloworld" "Hello, World" map:set; 90 | "{ " print 91 | { | with item ; 92 | "'" print 93 | 0 item:get print 94 | "': '" print 95 | 1 item:get print 96 | "', " print 97 | } map:foreach 98 | "}" println 99 | 100 | "" println 101 | "Running with args: " print 102 | argv:iter 103 | { str | " " concat } swap:map 104 | &print swap:foreach 105 | 106 | "" println 107 | "testing stream" println 108 | 109 | def file "test.txt" 1 StreamTypes:file:create =file 110 | "hi\n" :to-bytes file:write-exact; 111 | file:close null =file 112 | 113 | "" println 114 | "testing split" println 115 | { | println } (" " "hello how are you" :split):foreach 116 | "" println 117 | 118 | catch { 119 | use net:http:Request 120 | "testing http" println 121 | def req "tudbut.de" 81 "GET" "/spltest" Request:new =req 122 | req:send:body _str println 123 | } 124 | with { with e ; 125 | e:message println 126 | "it seems the internet is not available" println 127 | } 128 | "" println 129 | 130 | "testing cache" println 131 | 2 cached-test _str println 132 | 3 cached-test _str println 133 | 2 cached-test _str println 134 | 3 cached-test _str println 135 | "" println 136 | 137 | catch { 138 | "heya" throw 139 | } with { with e ; 140 | e:message println 141 | } 142 | "" println 143 | 144 | "testing messages" println 145 | def bus messaging:Bus:new =bus 146 | bus:subscribe <{ "testmsg1" { | with message ; message:name print " called1 1" println } } 147 | bus:subscribe <{ "testmsg1" { | with message ; message:name print " called1 2" println } } 148 | bus:subscribe <{ "testmsg2" { | with message ; message:name print " called2 1" println } } 149 | bus:subscribe <{ "testmsg2" { | with message ; message:name print " called2 2" println } } 150 | "testmsg1" bus:publish 151 | "testmsg2" bus:publish 152 | "testmsg1" bus:publish 153 | "testmsg3" bus:publish 154 | 155 | 100 156 | } 157 | 158 | func cached-test { mega | 1 "cached-test" cache <{ { mega | with i ; 159 | i 2 * 160 | "calculated " i _str concat println 161 | } } } 162 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Using SPL as a crate 2 | //! 3 | //! SPL has a complete API for use in applications and libraries. 4 | //! To start a file, use `start_file` with the path, which is relatively straightforward, just like 5 | //! `start_file_in_runtime`, which is the same as `start_file` but doesn't create and set a 6 | //! runtime. 7 | //! 8 | //! To start code more customizably, you will have to create a stack and a runtime yourself, then 9 | //! call `add_std` to include the standard library. 10 | //! 11 | //! Example: 12 | //! ``` 13 | //! use spl::*; 14 | //! fn main() -> OError { 15 | //! Runtime::new().set(); 16 | //! let mut stack = Stack::new(); 17 | //! add_std(&mut stack)?; 18 | //! Words::new(vec![ 19 | //! Word::Const(Value::Str("Hello, World!".to_owned())), 20 | //! Word::Call("println".to_owned(), /*pop result:*/ false, /*reference:*/ 0) 21 | //! ]).exec(&mut stack); 22 | //! Ok(()) 23 | //! } 24 | //! ``` 25 | 26 | #![allow(clippy::type_complexity)] 27 | #![allow(clippy::len_without_is_empty)] 28 | 29 | pub mod dyn_fns; 30 | pub mod lexer; 31 | pub mod mutex; 32 | pub mod oxidizer; 33 | pub mod runtime; 34 | pub mod sasm; 35 | pub mod std_fns; 36 | pub mod stdlib; 37 | pub mod stream; 38 | 39 | pub use lexer::*; 40 | pub use runtime::*; 41 | 42 | use std::fs; 43 | 44 | /// Creates a runtime, lexes and executes some SPL code from a file, returning the stack that was 45 | /// used for the operations, which should be empty in most cases. 46 | pub fn start_file(path: &str) -> Result { 47 | Runtime::new().set(); 48 | (start_file_in_runtime(path), Runtime::reset()).0 49 | } 50 | 51 | /// TO START A STANDALONE PIECE OF CODE, USE start_file!! 52 | /// Lexes and starts some SPL code from a file, returning the stack. 53 | pub fn start_file_in_runtime(path: &str) -> Result { 54 | let mut stack = Stack::new(); 55 | // import stdlib 56 | add_std(&mut stack)?; 57 | 58 | // run file 59 | Words::new(vec![ 60 | Word::Const(Value::Str(path.to_owned())), 61 | Word::Call("call-main-on-file".to_owned(), false, 0), 62 | ]) 63 | .exec(&mut stack)?; 64 | 65 | Ok(stack) 66 | } 67 | 68 | /// Include the standard library in a runtime-stack-pair, where the runtime has been .set(). 69 | pub fn add_std(stack: &mut Stack) -> OError { 70 | let f = find_in_splpath("std.spl"); 71 | let words = lex(if let Ok(f) = f { 72 | fs::read_to_string(f).unwrap() 73 | } else { 74 | f.unwrap_err() 75 | }) 76 | .map_err(|x| stack.error(ErrorKind::LexError(format!("{x:?}"))))?; 77 | words.exec(stack) 78 | } 79 | 80 | macro_rules! nofmt { 81 | {$($code:tt)*} => { 82 | $($code)* 83 | }; 84 | } 85 | 86 | // rustfmt adds infinite indentation to this, incrementing every time it is run. 87 | nofmt! { 88 | #[macro_export] 89 | macro_rules! require_on_stack { 90 | ($name:tt, $type:tt, $stack:expr, $fn:literal) => { 91 | let Value::$type($name) = $stack.pop().lock_ro().native.clone() else { 92 | return $stack.err(ErrorKind::InvalidCall($fn.to_owned())) 93 | }; 94 | }; 95 | } 96 | 97 | #[macro_export] 98 | macro_rules! require_int_on_stack { 99 | ($name:tt, $stack:expr, $fn:literal) => { 100 | let Value::Int($name) = $stack.pop().lock_ro().native.clone().try_mega_to_int() else { 101 | return $stack.err(ErrorKind::InvalidCall($fn.to_owned())) 102 | }; 103 | }; 104 | } 105 | #[macro_export] 106 | macro_rules! require_array { 107 | ($name:tt, $array:expr, $stack:expr, $fn:literal) => { 108 | let Value::Array(ref $name) = $array.lock_ro().native else { 109 | return $stack.err(ErrorKind::InvalidCall($fn.to_owned())) 110 | }; 111 | }; 112 | } 113 | #[macro_export] 114 | macro_rules! require_mut_array { 115 | ($name:tt, $array:expr, $stack:expr, $fn:literal) => { 116 | let Value::Array(ref mut $name) = $array.lock().native else { 117 | return $stack.err(ErrorKind::InvalidCall($fn.to_owned())) 118 | }; 119 | }; 120 | } 121 | #[macro_export] 122 | macro_rules! require_array_on_stack { 123 | ($name:tt, $stack:expr, $fn:literal) => { 124 | let binding = $stack.pop(); 125 | require_array!($name, binding, $stack, $fn) 126 | }; 127 | } 128 | #[macro_export] 129 | macro_rules! require_mut_array_on_stack { 130 | ($name:tt, $stack:expr, $fn:literal) => { 131 | let binding = $stack.pop(); 132 | require_mut_array!($name, binding, $stack, $fn) 133 | }; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /iter.spl: -------------------------------------------------------------------------------- 1 | 2 | construct _Iter { 3 | ; 4 | next-chunk { [item] | with amount this ; 5 | def i 0 =i 6 | def arr amount anew =arr 7 | while { i amount lt } { 8 | (this:next i arr:set;) 9 | i ++ =i 10 | } 11 | arr 12 | } 13 | foreach { | with callable this ; 14 | def itm 15 | while { this:next dup =itm null eq not } { 16 | itm callable call 17 | } 18 | } 19 | collect { array | with this ; 20 | [ { any | } this:foreach ] 21 | } 22 | map { MapIter | with map-function this ; 23 | map-function this MapIter:new 24 | } 25 | reduce { ReduceIter | with reduce-function this ; 26 | reduce-function this ReduceIter:new 27 | } 28 | fold { FoldIter | with accumulator fold-function this ; 29 | accumulator fold-function this FoldIter:new 30 | } 31 | sum { mega | with this ; 32 | { mega | with accum item ; 33 | accum item + 34 | } this:reduce:calculate 35 | } 36 | product { mega | with this ; 37 | { mega | with accum item ; 38 | accum item * 39 | } this:reduce:calculate 40 | } 41 | join { str | with separator this ; 42 | { str | with accum item ; 43 | accum _str separator item _str concat concat 44 | } this:reduce:calculate 45 | } 46 | filter { FilterIter | with filter this ; 47 | filter this FilterIter:new 48 | } 49 | skip { this | with amount this ; 50 | { | pop 51 | this:next; 52 | } amount:foreach 53 | this 54 | } 55 | count { mega | with this ; 56 | def n 0 =n 57 | while { this:next null eq not } { 58 | n ++ =n 59 | } 60 | n 61 | } 62 | last { any | with this ; 63 | def last 64 | def cur 65 | while { this:next dup =cur null eq not } { 66 | cur =last 67 | } 68 | last 69 | } 70 | chain { ChainIter | with other this ; 71 | other this ChainIter:new 72 | } 73 | enumerate { EnumerationIter | with this ; 74 | this EnumerationIter:new 75 | } 76 | nth { item | with idx this ; 77 | idx -- =idx 78 | while { idx 0 gt } { 79 | this:next; 80 | idx -- =idx 81 | } 82 | this:next 83 | } 84 | } 85 | 86 | construct MapIter { 87 | origin 88 | map-function 89 | ; 90 | construct { this | with map-function origin this ; 91 | origin this:=origin 92 | map-function this:=map-function 93 | this 94 | } 95 | next { any | with this ; 96 | this:origin:next dup null eq if { 97 | 2 stop 98 | } 99 | this:map-function call 100 | } 101 | } 102 | 103 | include _Iter in MapIter 104 | 105 | construct ReduceIter { 106 | origin 107 | accumulator 108 | reduce-function 109 | ; 110 | construct { this | with reduce-function origin this ; 111 | origin this:=origin 112 | reduce-function this:=reduce-function 113 | this 114 | } 115 | next { any | with this ; 116 | def itm 117 | this:origin:next dup null eq if { 118 | 2 stop 119 | } =itm 120 | this:accumulator null eq if { 121 | itm dup this:=accumulator 122 | 2 stop 123 | } 124 | this:accumulator itm this:reduce-function call dup this:=accumulator 125 | } 126 | calculate { any | with this ; 127 | { | pop } this:foreach 128 | this:accumulator 129 | } 130 | } 131 | 132 | include _Iter in ReduceIter 133 | 134 | construct FoldIter { 135 | origin 136 | accumulator 137 | reduce-function 138 | ; 139 | construct { this | with accumulator fold-function origin this ; 140 | accumulator this:=accumulator 141 | origin this:=origin 142 | fold-function this:=fold-function 143 | this 144 | } 145 | next { any | with this ; 146 | def itm 147 | this:origin:next dup null eq if { 148 | 2 stop 149 | } =itm 150 | this:accumulator itm this:fold-function call dup this:=accumulator 151 | } 152 | } 153 | 154 | include _Iter in FoldIter 155 | 156 | construct FilterIter { 157 | origin 158 | filter 159 | ; 160 | construct { this | with filter origin this ; 161 | origin this:=origin 162 | filter this:=filter 163 | this 164 | } 165 | next { any | with this ; 166 | while { 1 } { 167 | def next this:origin:next =next 168 | next null eq if { 169 | null 170 | 3 stop 171 | } 172 | next this:filter call if { 173 | next 3 stop 174 | } 175 | } 176 | } 177 | } 178 | 179 | include _Iter in FilterIter 180 | 181 | construct ChainIter { 182 | current 183 | next-iters 184 | ; 185 | construct { this | with other origin this ; 186 | [ other ] List:new:from this:=next-iters 187 | origin this:=current 188 | this 189 | } 190 | next { any | with this ; 191 | def item this:current:next =item 192 | while { item null eq } { 193 | this:next-iters:pop-front dup null eq not if { 194 | this:=current 195 | this:current:next =item 196 | } 2 stop 197 | } 198 | item 199 | } 200 | chain { this | with other this ; 201 | other this:next-iters:push 202 | } 203 | } 204 | 205 | include _Iter in ChainIter 206 | 207 | construct EnumerationIter { 208 | origin 209 | idx 210 | ; 211 | construct { this | with origin this ; 212 | origin this:=origin 213 | this 214 | } 215 | next { [mega,any]|null | with this ; 216 | this:origin:next dup null eq not if { 217 | [ swap this:idx swap ] 218 | this:idx ++ this:=idx 219 | } 220 | } 221 | } 222 | 223 | include _Iter in EnumerationIter 224 | -------------------------------------------------------------------------------- /src/oxidizer/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module creates a rust application that runs the desired SPL. 2 | //! At its current stage, this is just parsing and rewriting `@rust` functions from SPL into actual rust. 3 | //! The future plan is for this to effectively become a compiler. 4 | 5 | use std::{ 6 | collections::{hash_map::DefaultHasher, HashMap}, 7 | env, 8 | ffi::OsStr, 9 | fs, 10 | hash::{Hash, Hasher}, 11 | io, 12 | process::{Child, Command, Stdio}, 13 | }; 14 | 15 | use crate::{FuncImplType, Keyword, Word, Words}; 16 | 17 | mod splrs; 18 | 19 | /// A specially compiled SPL version with custom parameters included. 20 | pub struct RustApp { 21 | /// The path to the binary 22 | binary: String, 23 | /// The path to the project dir 24 | dir: String, 25 | } 26 | 27 | impl RustApp { 28 | /// Gets the path to the binary 29 | pub fn get_binary(&self) -> &str { 30 | &self.binary 31 | } 32 | 33 | /// Executes the binary with some args 34 | pub fn execute(&self, args: I) -> Result 35 | where 36 | I: IntoIterator, 37 | S: AsRef, 38 | { 39 | Command::new(self.binary.clone()).args(args).spawn() 40 | } 41 | 42 | /// Deletes the binary and rust project 43 | pub fn delete(self) { 44 | fs::remove_dir_all(self.dir).expect("unable to delete RustApp"); 45 | } 46 | } 47 | 48 | /// A rust function which was embedded in SPL 49 | pub struct RustFunction { 50 | fn_name: String, 51 | content: String, 52 | } 53 | 54 | /// A builder for [`RustApp`]s. This is work-in-progress. 55 | pub struct RustAppBuilder { 56 | rust_functions: Vec, 57 | to_embed: HashMap, 58 | default_file: String, 59 | name: Option, 60 | } 61 | 62 | impl Hash for RustAppBuilder { 63 | fn hash(&self, state: &mut H) { 64 | state.write_usize(self.rust_functions.len()); 65 | for f in &self.rust_functions { 66 | f.fn_name.hash(state); 67 | } 68 | for (k, _) in &self.to_embed { 69 | k.hash(state); 70 | } 71 | } 72 | } 73 | 74 | impl RustAppBuilder { 75 | pub fn new() -> RustAppBuilder { 76 | Self { 77 | rust_functions: Vec::new(), 78 | to_embed: HashMap::new(), 79 | default_file: "repl.spl".to_owned(), 80 | name: None, 81 | } 82 | } 83 | 84 | /// Embeds a file into the desired app 85 | pub fn add_source(&mut self, name: String, source: String) { 86 | self.to_embed.insert(name, source); 87 | } 88 | 89 | /// Sets the name of the folder it will sit in. 90 | pub fn set_name(&mut self, name: String) { 91 | self.name = Some(name); 92 | } 93 | 94 | /// Adds all `@rust` functions from the given SPL code's top level. Does NOT scan for lower levels at this time. 95 | pub fn prepare(&mut self, spl: Words) -> bool { 96 | let mut needs_new = false; 97 | for word in spl.words { 98 | match word { 99 | Word::Key(Keyword::FuncOf(name, content, FuncImplType::Rust)) => { 100 | self.rust_functions.push(splrs::to_rust(name, content)); 101 | needs_new = true; 102 | } 103 | _ => (), 104 | } 105 | } 106 | needs_new 107 | } 108 | 109 | /// Sets the default file to start when none is provided. This will not work if the file is not also embedded. 110 | pub fn set_default_file(&mut self, name: String) { 111 | self.default_file = name; 112 | } 113 | 114 | /// Builds the desired app, including literally building it using cargo. 115 | pub fn build(self, output: bool) -> Result { 116 | // we need a temp folder! 117 | let tmp = "."; // TODO replace? 118 | let name = match self.name { 119 | Some(x) => x, 120 | None => { 121 | let mut hash = DefaultHasher::new(); 122 | self.hash(&mut hash); 123 | let hash = hash.finish(); 124 | hash.to_string() 125 | } 126 | }; 127 | let _ = Command::new("cargo") 128 | .arg("new") 129 | .arg(format!("spl-{name}")) 130 | .current_dir(tmp) 131 | .stdout(Stdio::null()) 132 | .stderr(Stdio::null()) 133 | .spawn() 134 | .unwrap() 135 | .wait(); 136 | Command::new("cargo") 137 | .arg("add") 138 | .arg(format!("spl@{}", env!("CARGO_PKG_VERSION"))) 139 | .current_dir(format!("{tmp}/spl-{name}")) 140 | .stdout(Stdio::null()) 141 | .stderr(Stdio::null()) 142 | .spawn() 143 | .unwrap() 144 | .wait()?; 145 | let mut runtime_init = String::new(); 146 | let mut code = String::new(); 147 | for func in self.rust_functions.into_iter().enumerate() { 148 | code += &format!( 149 | "fn spl_oxidizer_{}(stack: &mut Stack) -> OError {{ {} Ok(()) }}", 150 | func.0, func.1.content 151 | ); 152 | runtime_init += &format!( 153 | "rt.native_functions.insert({:?}, (0, FuncImpl::Native(spl_oxidizer_{})));", 154 | func.1.fn_name, func.0 155 | ) 156 | } 157 | for (name, data) in self.to_embed.into_iter() { 158 | runtime_init += &format!("rt.embedded_files.insert({:?}, {:?});", name, data); 159 | } 160 | fs::write( 161 | format!("{tmp}/spl-{name}/src/main.rs"), 162 | stringify! { 163 | use spl::{runtime::*, *}; 164 | 165 | use std::env::args; 166 | 167 | pub fn start_file(path: &str) -> Result { 168 | let mut rt = Runtime::new(); 169 | runtime_init 170 | rt.set(); 171 | (start_file_in_runtime(path), Runtime::reset()).0 172 | } 173 | 174 | fn main() { 175 | if let Err(x) = start_file( 176 | &args() 177 | .nth(1) 178 | .unwrap_or_else(|| find_in_splpath("default_file").expect("no file to be run")), 179 | ) { 180 | println!("{x:?}"); 181 | } 182 | } 183 | }.to_owned().replace("default_file", &self.default_file).replace("runtime_init", &runtime_init) + &code, 184 | )?; 185 | Command::new("cargo") 186 | .arg("build") 187 | .arg("--release") 188 | .current_dir(format!("{tmp}/spl-{name}")) 189 | .stdout(if output { 190 | Stdio::inherit() 191 | } else { 192 | Stdio::null() 193 | }) 194 | .stderr(if output { 195 | Stdio::inherit() 196 | } else { 197 | Stdio::null() 198 | }) 199 | .spawn() 200 | .unwrap() 201 | .wait()?; 202 | Ok(RustApp { 203 | dir: format!("{tmp}/spl-{name}"), 204 | binary: { 205 | // insanity. will have to clean this up at some point. 206 | let dir = format!("{tmp}/spl-{name}/target/release/"); 207 | fs::read_dir(dir) 208 | .expect("unable to build: dir was not created.") 209 | .filter(|x| { 210 | let x = x 211 | .as_ref() 212 | .expect("file system did something i cannot comprehend"); 213 | let n = x.file_name().into_string().unwrap(); 214 | x.file_type().expect("file system uhhh?????").is_file() 215 | && !n.ends_with(".d") 216 | && !n.starts_with(".") 217 | }) 218 | .next() 219 | .expect("cargo was unable to build the binary") 220 | .expect("file system did something i cannot comprehend") 221 | .path() 222 | .into_os_string() 223 | .into_string() 224 | .expect("bad unicode in file path") 225 | }, 226 | }) 227 | } 228 | } 229 | 230 | impl Default for RustAppBuilder { 231 | fn default() -> Self { 232 | Self::new() 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # "Stack Programming Language" =SPL 2 | 3 | SPL is a simple, concise, concatenative scripting language. 4 | 5 | Example: 6 | ```js 7 | func main { mega | with args ; 8 | "Running with args: " print 9 | args:iter 10 | { str | " " concat } swap:map 11 | &print swap:foreach 12 | "" println 13 | println <{ "and with that, we're done" } 14 | 0 15 | } 16 | ``` 17 | 18 | ## "5 minutes" SPL:in 19 | 20 | 21 | - `def` introduces a variable. 22 | ```js 23 | def a 24 | ``` 25 | - Writing a constant pushes it to the stack. This works with strings and numbers. 26 | ```js 27 | "Hello, World!" 28 | ``` 29 | - Use `=` to assign the topmost value to a variable. In this case, that is 30 | "Hello, World!" 31 | ```js 32 | =a 33 | ``` 34 | - This can be written as a single line - line breaks are always optional, and 35 | equal to a space. 36 | ```js 37 | def a "Hello, World!" =a 38 | ``` 39 | - Variables consist of two functions: `` and `=`. Use `` to 40 | obtain the value again. 41 | ```js 42 | a 43 | ``` 44 | - The `print` function is used to print a value. It takes one value from the stack 45 | and prints it without a newline. To print with a newline, use `println`. The 46 | semicolon at the end means 'if this function returns anything, throw it away'. 47 | This can be used on strings to make them comments, but is not available for 48 | numeric constants. 49 | ```js 50 | println; 51 | ``` 52 | ```txt 53 | Hello, World! 54 | ``` 55 | - The `func` keyword introduces a function. The `{ mega |` is the return type 56 | declaration, which in SPL is done within the block. In this case, our function 57 | returns one of the `mega` type, which is a 128-bit integer. 58 | ```js 59 | func main { mega | 60 | ``` 61 | - The `with` declaration will be explained below. It defines the `args` argument. 62 | ```js 63 | with args ; 64 | ``` 65 | - Now, we can write code like before: 66 | ```js 67 | def list 68 | ``` 69 | - SPL has a varying-length array type, the list. To create any construct (object), 70 | we use `:new`. 71 | ```js 72 | List:new =list 73 | ``` 74 | - To add to the end of a list, we `push` to it. All construct methods are 75 | written with a colon, like before in the `new` example. 76 | ```js 77 | "Hello," list:push 78 | ``` 79 | Note the lowercase `list`, because we are pushing to the construct in the 80 | variable. 81 | - Now, let's also push "World!". 82 | ```js 83 | "World" list:push 84 | ``` 85 | Beautiful. I'd like to print it now, but how? 86 | - We can't print a list directly (with what we know so far), but we can iterate 87 | through it! 88 | ```js 89 | { | with item ; 90 | item print; 91 | " " print; 92 | } list:foreach; 93 | "" println; 94 | ``` 95 | **There is a lot to unpack here!** 96 | - `{ |` creates a closure with no return type (in C-style languages, that'd be 97 | a void function). 98 | - `with item ;` declares arguments. This is optional, and not needed if the 99 | function does not take arguments. Running `"a" "b" "c"` and calling 100 | something with a b c ; will leave each letter in the corresponding variable. 101 | - We already know what print does - it prints the item and a space in this 102 | case. 103 | - The semicolons mean we don't care about the result of printing. In this 104 | case, printing does not return anything, but I added the semicolons just for 105 | clarity or in case it did. 106 | - `}` ends the closure, and puts it on the top of our stack. 107 | - `list:foreach` calls the `foreach` method on our `list`, which is declared 108 | with callable this ; - that means we need to provide one argument along with 109 | the implied `this` argument (it can have any name - the interpreter does not 110 | care about names in any way - `this` is just convention). The `callable` 111 | here is *not* a type! 112 | - `foreach` also does not return anything, but I added the semicolon for 113 | clarity. 114 | - We then print a newline. 115 | ```txt 116 | Hello, World! 117 | ``` 118 | - SPL has Ranges, constructed using ` Range:new`. You can iterate 119 | over them. 120 | ```js 121 | 0 5 Range:new:iter 122 | ``` 123 | - Now, let's multiply all of these values by 5. 124 | ```js 125 | { mega | 5 * } swap:map 126 | ``` 127 | Wait, what? 128 | Why is there suddenly an inconsistency in method calls, the iterator isn't 129 | being called, it's something else now! 130 | 131 | It sure does look like it, doesn't it? `swap` swaps the topmost two values on 132 | the stack. `a b -> b a`. That means we are actually calling to our iterator. 133 | The closure and the iterator are swapped before the call is made. `swap:map` 134 | is a more concise way of writing `swap :map`. 135 | 136 | The map function on the iterator (which is available through `:iter` on most 137 | collection constructs) is used to apply a function to all items in the 138 | iterator. The closure here actually takes an argument, but the with 139 | declaration is omitted. The longer version would be: 140 | ```js 141 | { mega | with item ; 142 | item 5 * 143 | } 144 | ``` 145 | But this is quite clunky, so when arguments are directly passed on to the next 146 | function, they are often simply kept on the stack. The `*` is simply a 147 | function taking two numbers and multilying them. The same goes for `+`, `-`, 148 | `%`, and `/`. `a b -` is equivalent to `a - b` in other languages. `lt`, 149 | `gt`, and `eq` are used to compare values. 150 | 151 | Returning is simply done by leaving something on the stack when the function 152 | exits, and the return declaration *can* technically be left off, but the 153 | semicolon won't be able to determine the amount of constructs to discard that 154 | way, so this should never be done unless you're absolutely sure. In this case, 155 | we are absolutely sure that it will never be called with a 156 | semicolon, because the mapping iterator has no use for the closure other than 157 | the returned object (which is the case for most closures in practice.), 158 | therefore we could even omit the return type declaration and get `{ | 5 *}`. 159 | Neat! 160 | - We can use `foreach` on iterators just like arrays. `_str` is used to convert 161 | a number to a string. 162 | ```js 163 | { | _str println } swap:foreach 164 | ``` 165 | ```txt 166 | 0 167 | 5 168 | 10 169 | 15 170 | 20 171 | ``` 172 | Ranges are inclusive of the lower bound and exclusive in the upper bound. 173 | They are often used similarly to the (pseudocode) equivalent in other 174 | languages: 175 | ```java 176 | for(int i = 0; i < 5; i++) { println((String) i * 5); } 177 | ``` 178 | - SPL actually isn't fully concatenative. It supports postfix arguments as well: 179 | ```js 180 | println <{ "and with that, we're done" } 181 | ``` 182 | This is actually not a special interpreter feature, more so is it a special 183 | lexer feature. This is 100% equivalent with the non-postfix version, where the 184 | string is right before the `println`. 185 | 186 | The same can be done for object calls. Let's rewrite the previous code with 187 | postfix: 188 | ```js 189 | Range:new <{ 0 5 } 190 | :iter 191 | :map <{ { | 5 * } } 192 | :foreach <{ { | _str println } } 193 | ``` 194 | 195 | I lied. This is now no longer 100% equivalent. Let's look at what happens 196 | under the hood. 197 | 198 | ```js 199 | call Range 200 | objpush 201 | const mega 0 202 | const mega 5 203 | objpop 204 | objcall new 205 | objcall iter 206 | objpush 207 | const func 0 208 | const mega 5 209 | call * 210 | end 211 | objpop 212 | objcall map 213 | objpush 214 | const func 0 215 | call _str 216 | call println 217 | end 218 | objpop 219 | objcall foreach 220 | ``` 221 | 222 | You can see there are now `objpush` and `objpop` instructions. This is doing 223 | the job that `swap` used to do in our previous example. However, swap can only 224 | swap the topmost values, but postfix arguments allow any amount. That's why 225 | there is a special instruction just for that. It can also be used through AST 226 | modifications, but there is no way to get it in normal language use as it can 227 | cause interpreter panics when they are used wrongly. 228 | 229 | `objpush` and `objpop` operate on a separate stack, called the objcall stack, 230 | as opposed to the main object stack. 231 | 232 | More of this tutorial to follow. 233 | 234 | ## Embedding rust into SPL 235 | 236 | Because SPL does not nearly have a complete standard library, embedding rust is required for many tasks. 237 | This can be done as follows 238 | 239 | ``` 240 | > cat rust-test.spl 241 | func main { | 242 | 1 rusty-test _str println 243 | 0 244 | } 245 | func rusty-test @rust !{ 246 | println!("hii"); 247 | let v = #pop:Mega#; 248 | #push(v + 1)#; 249 | } 250 | > spl --build rust-test.spl demo 251 | ---snip--- 252 | > ./spl-demo/target/release/spl-demo rust-test.spl 253 | hii 254 | 2 255 | ``` 256 | 257 | As you can see, it's relatively straight-forward to do; but there are some major limitations right now: 258 | 259 | - It's a new binary, and can't be linked into the currently running program 260 | - No crates can be added automatically at the moment 261 | 262 | The second one is easy to fix, but I intend to fix the first one first. Sadly, fixing it requires 263 | compiling the code as a dynamic library and also getting it to work with the program its running in. 264 | If anyone knows how to do this properly, I'd REALLY appreciate a PR or issue explaining it. -------------------------------------------------------------------------------- /src/dyn_fns.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::sasm::sasm_read; 4 | use crate::*; 5 | use crate::{lexer, runtime::*}; 6 | 7 | pub fn dyn_dump(stack: &mut Stack) -> OError { 8 | Words { 9 | words: vec![Word::Key(Keyword::Dump)], 10 | } 11 | .exec(stack) 12 | } 13 | 14 | pub fn dyn_def(stack: &mut Stack) -> OError { 15 | let Value::Str(s) = stack.pop().lock_ro().native.clone() else { 16 | return stack.err(ErrorKind::InvalidCall("dyn-def".to_owned())) 17 | }; 18 | Words { 19 | words: vec![Word::Key(Keyword::Def(s))], 20 | } 21 | .exec(stack)?; 22 | Ok(()) 23 | } 24 | 25 | pub fn dyn_func(stack: &mut Stack) -> OError { 26 | let ( 27 | Value::Str(s), 28 | Value::Func(f), 29 | ) = ( 30 | stack.pop().lock_ro().native.clone(), 31 | stack.pop().lock_ro().native.clone(), 32 | ) else { 33 | return stack.err(ErrorKind::InvalidCall("dyn-func".to_owned())) 34 | }; 35 | stack.define_func(s, f); 36 | Ok(()) 37 | } 38 | 39 | pub fn dyn_construct(stack: &mut Stack) -> OError { 40 | let Value::Str(s) = stack.pop().lock_ro().native.clone() else { 41 | return stack.err(ErrorKind::InvalidCall("dyn-construct".to_owned())) 42 | }; 43 | Words { 44 | words: vec![Word::Key(Keyword::Construct( 45 | s, 46 | Vec::new(), 47 | Vec::new(), 48 | false, 49 | ))], 50 | } 51 | .exec(stack)?; 52 | Ok(()) 53 | } 54 | 55 | pub fn dyn_namespace(stack: &mut Stack) -> OError { 56 | let Value::Str(s) = stack.pop().lock_ro().native.clone() else { 57 | return stack.err(ErrorKind::InvalidCall("dyn-construct".to_owned())) 58 | }; 59 | Words { 60 | words: vec![Word::Key(Keyword::Construct( 61 | s, 62 | Vec::new(), 63 | Vec::new(), 64 | true, 65 | ))], 66 | } 67 | .exec(stack)?; 68 | Ok(()) 69 | } 70 | 71 | pub fn dyn_def_field(stack: &mut Stack) -> OError { 72 | let ( 73 | Value::Str(s), 74 | Value::Str(name), 75 | ) = ( 76 | stack.pop().lock_ro().native.clone(), 77 | stack.pop().lock_ro().native.clone(), 78 | ) else { 79 | return stack.err(ErrorKind::InvalidCall("dyn-def-field".to_owned())) 80 | }; 81 | runtime(|rt| rt.get_type_by_name(&s)) 82 | .ok_or_else(|| stack.error(ErrorKind::TypeNotFound(s)))? 83 | .lock() 84 | .add_property(name, stack.get_frame())?; 85 | Ok(()) 86 | } 87 | 88 | pub fn dyn_def_method(stack: &mut Stack) -> OError { 89 | let ( 90 | Value::Str(s), 91 | Value::Str(name), 92 | Value::Func(f), 93 | ) = ( 94 | stack.pop().lock_ro().native.clone(), 95 | stack.pop().lock_ro().native.clone(), 96 | stack.pop().lock_ro().native.clone(), 97 | ) else { 98 | return stack.err(ErrorKind::InvalidCall("dyn-def-method".to_owned())) 99 | }; 100 | runtime(|rt| rt.get_type_by_name(&s)) 101 | .ok_or_else(|| stack.error(ErrorKind::TypeNotFound(s)))? 102 | .lock() 103 | .functions 104 | .insert(name, f); 105 | Ok(()) 106 | } 107 | 108 | pub fn dyn_include(stack: &mut Stack) -> OError { 109 | let ( 110 | Value::Str(b), 111 | Value::Str(a), 112 | ) = ( 113 | stack.pop().lock_ro().native.clone(), 114 | stack.pop().lock_ro().native.clone(), 115 | ) else { 116 | return stack.err(ErrorKind::InvalidCall("dyn-include".to_owned())) 117 | }; 118 | Words { 119 | words: vec![Word::Key(Keyword::Include(a, b))], 120 | } 121 | .exec(stack)?; 122 | Ok(()) 123 | } 124 | 125 | pub fn dyn_while(stack: &mut Stack) -> OError { 126 | let ( 127 | Value::Func(blk), 128 | Value::Func(cond), 129 | ) = ( 130 | stack.pop().lock_ro().native.clone(), 131 | stack.pop().lock_ro().native.clone(), 132 | ) else { 133 | return stack.err(ErrorKind::InvalidCall("dyn-while".to_owned())) 134 | }; 135 | loop { 136 | cond.to_call.call(stack)?; 137 | if !stack.pop().lock_ro().is_truthy() { 138 | break; 139 | } 140 | blk.to_call.call(stack)?; 141 | } 142 | Ok(()) 143 | } 144 | 145 | pub fn dyn_if(stack: &mut Stack) -> OError { 146 | let Value::Func(blk) = stack.pop().lock_ro().native.clone() else { 147 | return stack.err(ErrorKind::InvalidCall("dyn-if".to_owned())) 148 | }; 149 | if stack.pop().lock_ro().is_truthy() { 150 | blk.to_call.call(stack)?; 151 | } 152 | Ok(()) 153 | } 154 | 155 | pub fn dyn_call(stack: &mut Stack) -> OError { 156 | let Value::Str(mut s) = stack.pop().lock_ro().native.clone() else { 157 | return stack.err(ErrorKind::InvalidCall("dyn-call".to_owned())) 158 | }; 159 | let mut words = Vec::new(); 160 | let mut ra = 0; 161 | while s.starts_with('&') { 162 | ra += 1; 163 | s = s[1..].to_owned(); 164 | } 165 | if s.ends_with(';') { 166 | words.push(Word::Call(s[..s.len() - 1].to_owned(), true, ra)); 167 | } else { 168 | words.push(Word::Call(s, false, ra)); 169 | } 170 | Words { words }.exec(stack)?; 171 | Ok(()) 172 | } 173 | 174 | pub fn dyn_objcall(stack: &mut Stack) -> OError { 175 | let Value::Str(mut s) = stack.pop().lock_ro().native.clone() else { 176 | return stack.err(ErrorKind::InvalidCall("dyn-objcall".to_owned())) 177 | }; 178 | let mut words = Vec::new(); 179 | let mut ra = 0; 180 | while s.starts_with('&') { 181 | ra += 1; 182 | s = s[1..].to_owned(); 183 | } 184 | if s.ends_with(';') { 185 | words.push(Word::ObjCall(s[..s.len() - 1].to_owned(), true, ra)); 186 | } else { 187 | words.push(Word::ObjCall(s, false, ra)); 188 | } 189 | Words { words }.exec(stack)?; 190 | Ok(()) 191 | } 192 | 193 | pub fn dyn_all_types(stack: &mut Stack) -> OError { 194 | stack.push( 195 | Value::Array( 196 | runtime(|rt| rt.get_types()) 197 | .into_iter() 198 | .map(|x| Value::Str(x.lock_ro().get_name()).spl()) 199 | .collect(), 200 | ) 201 | .spl(), 202 | ); 203 | Ok(()) 204 | } 205 | 206 | pub fn dyn_read(stack: &mut Stack) -> OError { 207 | let Value::Str(s) = stack.pop().lock_ro().native.clone() else { 208 | return stack.err(ErrorKind::InvalidCall("dyn-read".to_owned())) 209 | }; 210 | stack.push( 211 | Value::Func(AFunc::new(Func { 212 | ret_count: 0, 213 | to_call: FuncImpl::SPL( 214 | lexer::lex(s).map_err(|x| stack.error(ErrorKind::LexError(format!("{x:?}"))))?, 215 | ), 216 | run_as_base: false, 217 | origin: stack.get_frame(), 218 | fname: None, 219 | name: "(dyn-read)".to_owned(), 220 | })) 221 | .spl(), 222 | ); 223 | Ok(()) 224 | } 225 | 226 | pub fn dyn_readf(stack: &mut Stack) -> OError { 227 | let ( 228 | Value::Str(s), 229 | Value::Str(n), 230 | ) = ( 231 | stack.pop().lock_ro().native.clone(), 232 | stack.pop().lock_ro().native.clone(), 233 | ) else { 234 | return stack.err(ErrorKind::InvalidCall("dyn-readf".to_owned())) 235 | }; 236 | stack.push( 237 | Value::Func(AFunc::new(Func { 238 | ret_count: 0, 239 | to_call: FuncImpl::SPL( 240 | lexer::lex(s).map_err(|x| stack.error(ErrorKind::LexError(format!("{x:?}"))))?, 241 | ), 242 | run_as_base: true, 243 | origin: stack.get_frame(), 244 | fname: Some(n), 245 | name: "root".to_owned(), 246 | })) 247 | .spl(), 248 | ); 249 | Ok(()) 250 | } 251 | 252 | pub fn dyn_sasm(stack: &mut Stack) -> OError { 253 | require_on_stack!(sasm, Str, stack, "dyn-sasm"); 254 | stack.push( 255 | Value::Func(AFunc::new(Func { 256 | ret_count: 0, 257 | to_call: FuncImpl::SPL(sasm_read(sasm)), 258 | origin: stack.get_frame(), 259 | fname: None, 260 | name: "(dyn-read)".to_owned(), 261 | run_as_base: false, 262 | })) 263 | .spl(), 264 | ); 265 | Ok(()) 266 | } 267 | 268 | pub fn dyn_sasmf(stack: &mut Stack) -> OError { 269 | require_on_stack!(sasm, Str, stack, "dyn-sasmf"); 270 | require_on_stack!(n, Str, stack, "dyn-sasmf"); 271 | stack.push( 272 | Value::Func(AFunc::new(Func { 273 | ret_count: 0, 274 | to_call: FuncImpl::SPL(sasm_read(sasm)), 275 | origin: stack.get_frame(), 276 | fname: Some(n), 277 | name: "root".to_owned(), 278 | run_as_base: true, 279 | })) 280 | .spl(), 281 | ); 282 | Ok(()) 283 | } 284 | 285 | pub fn dyn_catch(stack: &mut Stack) -> OError { 286 | require_on_stack!(ctch, Func, stack, "dyn-catch"); 287 | require_on_stack!(blk, Func, stack, "dyn-catch"); 288 | require_on_stack!(types, Array, stack, "dyn-catch"); 289 | if let Err(e) = blk.to_call.call(stack) { 290 | if types.is_empty() || types.contains(&e.kind.to_string().spl()) { 291 | stack.push(e.spl()); 292 | ctch.to_call.call(stack) 293 | } else { 294 | Err(e) 295 | } 296 | } else { 297 | Ok(()) 298 | } 299 | } 300 | 301 | pub fn dyn_use(stack: &mut Stack) -> OError { 302 | require_on_stack!(item, Str, stack, "dyn-use"); 303 | Words::new(vec![Word::Key(Keyword::Use(item))]).exec(stack) 304 | } 305 | 306 | pub(crate) fn wrap(f: fn(&mut Stack) -> OError) -> impl Fn(&mut Stack) -> OError { 307 | move |stack| unsafe { 308 | let frame = stack.pop_frame(0); 309 | let r = f(stack); 310 | stack.push_frame(frame); 311 | r 312 | } 313 | } 314 | 315 | pub fn register(r: &mut Stack, o: Arc) { 316 | type Fn = fn(&mut Stack) -> OError; 317 | let fns: [(&str, Fn, u32); 19] = [ 318 | ("dyn-__dump", dyn_dump, 0), 319 | ("dyn-def", dyn_def, 0), 320 | ("dyn-func", dyn_func, 0), 321 | ("dyn-construct", dyn_construct, 0), 322 | ("dyn-namespace", dyn_namespace, 0), 323 | ("dyn-def-field", dyn_def_field, 0), 324 | ("dyn-def-method", dyn_def_method, 0), 325 | ("dyn-include", dyn_include, 0), 326 | ("dyn-while", dyn_while, 0), 327 | ("dyn-if", dyn_if, 0), 328 | ("dyn-call", dyn_call, 0), 329 | ("dyn-objcall", dyn_objcall, 0), 330 | ("dyn-all-types", dyn_all_types, 1), 331 | ("dyn-read", dyn_read, 1), 332 | ("dyn-readf", dyn_readf, 1), 333 | ("dyn-sasm", dyn_sasm, 1), 334 | ("dyn-sasmf", dyn_sasmf, 1), 335 | ("dyn-catch", dyn_catch, 0), 336 | ("dyn-use", dyn_use, 0), 337 | ]; 338 | for f in fns { 339 | r.define_func( 340 | f.0.to_owned(), 341 | AFunc::new(Func { 342 | ret_count: f.2, 343 | to_call: FuncImpl::NativeDyn(Arc::new(Box::new(wrap(f.1)))), 344 | run_as_base: false, 345 | origin: o.clone(), 346 | fname: None, 347 | name: f.0.to_owned(), 348 | }), 349 | ); 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /src/stream.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | fs::OpenOptions, 4 | io::Read, 5 | io::Write, 6 | mem, 7 | net::{Shutdown, TcpStream, UdpSocket}, 8 | sync::Arc, 9 | }; 10 | 11 | use once_cell::sync::Lazy; 12 | 13 | use crate::{mutex::Mut, runtime::*, *}; 14 | 15 | static STREAM_TYPES: Lazy>>> = 16 | Lazy::new(|| Arc::new(Mut::new(HashMap::new()))); 17 | static IS_INITIALIZED: Lazy>> = Lazy::new(|| Arc::new(Mut::new(false))); 18 | 19 | /// Registers a custom stream type. 20 | pub fn register_stream_type( 21 | name: &str, 22 | supplier: impl Fn(&mut Stack) -> Result + Sync + Send + 'static, 23 | ) { 24 | STREAM_TYPES 25 | .lock() 26 | .insert(name.to_owned(), StreamType::from(supplier)); 27 | } 28 | 29 | /// Gets a stream type by name. 30 | pub fn get_stream_type(name: String) -> Option { 31 | STREAM_TYPES.lock_ro().get(&name).cloned() 32 | } 33 | 34 | /// An SPL stream type. 35 | #[derive(Clone)] 36 | pub struct StreamType { 37 | func: Arc Result + Sync + Send + 'static>>, 38 | } 39 | 40 | impl StreamType { 41 | pub fn make_stream(&self, stack: &mut Stack) -> Result { 42 | (self.func)(stack) 43 | } 44 | } 45 | 46 | /// An SPL stream, holding a reader and a writer, and a function to close it. 47 | pub struct Stream { 48 | reader: Box, 49 | writer: Box, 50 | close: fn(&mut Self), 51 | } 52 | 53 | impl Stream { 54 | pub fn new(main: T, close: fn(&mut Self)) -> Self { 55 | let mut rw = Box::new(main); 56 | Self { 57 | // SAFETY: Because these are both in private fields on one object, they can not be 58 | // written to simultaneously or read from while writing due to the guards put in place 59 | // by the borrow checker on the Stream. 60 | reader: Box::new(unsafe { mem::transmute::<&mut _, &mut T>(rw.as_mut()) }), 61 | writer: rw, 62 | close, 63 | } 64 | } 65 | pub fn new_split( 66 | reader: impl Read + 'static, 67 | writer: impl Write + 'static, 68 | close: fn(&mut Self), 69 | ) -> Self { 70 | Self { 71 | reader: Box::new(reader), 72 | writer: Box::new(writer), 73 | close, 74 | } 75 | } 76 | } 77 | 78 | impl Read for Stream { 79 | fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result { 80 | self.reader.read_vectored(bufs) 81 | } 82 | 83 | fn read_to_end(&mut self, buf: &mut Vec) -> std::io::Result { 84 | self.reader.read_to_end(buf) 85 | } 86 | 87 | fn read_to_string(&mut self, buf: &mut String) -> std::io::Result { 88 | self.reader.read_to_string(buf) 89 | } 90 | 91 | fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> { 92 | self.reader.read_exact(buf) 93 | } 94 | 95 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 96 | self.reader.read(buf) 97 | } 98 | } 99 | 100 | impl Write for Stream { 101 | fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result { 102 | self.writer.write_vectored(bufs) 103 | } 104 | 105 | fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { 106 | self.writer.write_all(buf) 107 | } 108 | 109 | fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> std::io::Result<()> { 110 | self.writer.write_fmt(fmt) 111 | } 112 | 113 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 114 | self.writer.write(buf) 115 | } 116 | 117 | fn flush(&mut self) -> std::io::Result<()> { 118 | self.writer.flush() 119 | } 120 | } 121 | 122 | impl From for StreamType 123 | where 124 | T: Fn(&mut Stack) -> Result + Sync + Send + 'static, 125 | { 126 | fn from(value: T) -> Self { 127 | StreamType { 128 | func: Arc::new(Box::new(value)), 129 | } 130 | } 131 | } 132 | 133 | pub fn new_stream(stack: &mut Stack) -> OError { 134 | require_on_stack!(s, Str, stack, "write-stream"); 135 | let stream = get_stream_type(s.clone()) 136 | .ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-type-{s}"))))? 137 | .make_stream(stack)?; 138 | let stream = runtime_mut(move |mut rt| Ok(rt.register_stream(stream)))?; 139 | stack.push(Value::Mega(stream.0 as i128).spl()); 140 | Ok(()) 141 | } 142 | 143 | pub fn write_stream(stack: &mut Stack) -> OError { 144 | require_on_stack!(id, Mega, stack, "write-stream"); 145 | require_array_on_stack!(a, stack, "write-stream"); 146 | let stream = runtime(|rt| { 147 | rt.get_stream(id as u128) 148 | .ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}")))) 149 | })?; 150 | let mut fixed = Vec::with_capacity(a.len()); 151 | for item in a.iter() { 152 | match item.lock_ro().native { 153 | Value::Int(x) => fixed.push(x as u8), 154 | _ => type_err!(stack, "!int", "int"), 155 | } 156 | } 157 | stack.push( 158 | Value::Mega( 159 | stream 160 | .lock() 161 | .write(&fixed[..]) 162 | .map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))? as i128, 163 | ) 164 | .spl(), 165 | ); 166 | Ok(()) 167 | } 168 | 169 | pub fn write_all_stream(stack: &mut Stack) -> OError { 170 | require_on_stack!(id, Mega, stack, "write-all-stream"); 171 | require_array_on_stack!(a, stack, "write-all-stream"); 172 | let stream = runtime(|rt| { 173 | rt.get_stream(id as u128) 174 | .ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}")))) 175 | })?; 176 | let mut fixed = Vec::with_capacity(a.len()); 177 | for item in a.iter() { 178 | match item.lock_ro().native { 179 | Value::Int(x) => fixed.push(x as u8), 180 | _ => type_err!(stack, "!int", "int"), 181 | } 182 | } 183 | stream 184 | .lock() 185 | .write_all(&fixed[..]) 186 | .map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))?; 187 | Ok(()) 188 | } 189 | 190 | pub fn flush_stream(stack: &mut Stack) -> OError { 191 | require_on_stack!(id, Mega, stack, "flush-stream"); 192 | let stream = runtime(|rt| { 193 | rt.get_stream(id as u128) 194 | .ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}")))) 195 | })?; 196 | stream 197 | .lock() 198 | .flush() 199 | .map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))?; 200 | Ok(()) 201 | } 202 | 203 | pub fn read_stream(stack: &mut Stack) -> OError { 204 | require_on_stack!(id, Mega, stack, "read-stream"); 205 | let array = stack.pop(); 206 | { 207 | require_mut_array!(a, array, stack, "read-stream"); 208 | let stream = runtime(|rt| { 209 | rt.get_stream(id as u128) 210 | .ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}")))) 211 | })?; 212 | let mut vec = vec![0; a.len()]; 213 | stack.push( 214 | Value::Mega( 215 | stream 216 | .lock() 217 | .read(&mut vec[..]) 218 | .map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))? 219 | as i128, 220 | ) 221 | .spl(), 222 | ); 223 | a.clone_from_slice( 224 | &vec.into_iter() 225 | .map(|x| Value::Int(x as i32).spl()) 226 | .collect::>(), 227 | ); 228 | } 229 | stack.push(array); 230 | Ok(()) 231 | } 232 | 233 | pub fn read_all_stream(stack: &mut Stack) -> OError { 234 | require_on_stack!(id, Mega, stack, "read-all-stream"); 235 | let array = stack.pop(); 236 | { 237 | require_mut_array!(a, array, stack, "read-all-stream"); 238 | let stream = runtime(|rt| { 239 | rt.get_stream(id as u128) 240 | .ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}")))) 241 | })?; 242 | let mut vec = vec![0; a.len()]; 243 | stream 244 | .lock() 245 | .read_exact(&mut vec[..]) 246 | .map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))?; 247 | a.clone_from_slice( 248 | &vec.into_iter() 249 | .map(|x| Value::Int(x as i32).spl()) 250 | .collect::>(), 251 | ); 252 | } 253 | stack.push(array); 254 | Ok(()) 255 | } 256 | 257 | pub fn close_stream(stack: &mut Stack) -> OError { 258 | require_on_stack!(id, Mega, stack, "close-stream"); 259 | if let Some(stream) = runtime(|rt| rt.get_stream(id as u128)) { 260 | let mut stream = stream.lock(); 261 | (stream.close)(&mut stream); 262 | } 263 | Ok(()) 264 | } 265 | 266 | fn nop(_stream: &mut Stream) {} 267 | 268 | fn stream_file(stack: &mut Stack) -> Result { 269 | let truncate = stack.pop().lock_ro().is_truthy(); 270 | require_on_stack!(path, Str, stack, "FILE new-stream"); 271 | Ok(Stream::new( 272 | OpenOptions::new() 273 | .read(!truncate) 274 | .write(true) 275 | .create(truncate) 276 | .truncate(truncate) 277 | .open(path) 278 | .map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?, 279 | nop, 280 | )) 281 | } 282 | 283 | fn stream_tcp(stack: &mut Stack) -> Result { 284 | require_int_on_stack!(port, stack, "TCP new-stream"); 285 | require_on_stack!(ip, Str, stack, "TCP new-stream"); 286 | fn close_tcp(stream: &mut Stream) { 287 | unsafe { 288 | let f = ((stream.reader.as_mut() as *mut dyn Read).cast() as *mut TcpStream) 289 | .as_mut() 290 | .unwrap(); 291 | let _ = f.shutdown(Shutdown::Both); 292 | } 293 | } 294 | Ok(Stream::new( 295 | TcpStream::connect((ip, port as u16)) 296 | .map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?, 297 | close_tcp, 298 | )) 299 | } 300 | 301 | fn stream_udp(stack: &mut Stack) -> Result { 302 | require_int_on_stack!(port, stack, "UDP new-stream"); 303 | require_on_stack!(ip, Str, stack, "UDP new-stream"); 304 | require_int_on_stack!(self_port, stack, "UDP new-stream"); 305 | require_on_stack!(self_ip, Str, stack, "UDP new-stream"); 306 | fn close_udp(_stream: &mut Stream) {} 307 | let sock = UdpSocket::bind((self_ip, self_port as u16)) 308 | .map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?; 309 | sock.connect((ip, port as u16)) 310 | .map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?; 311 | struct UdpRW(UdpSocket); 312 | impl Write for UdpRW { 313 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 314 | self.0.send(buf) 315 | } 316 | 317 | fn flush(&mut self) -> std::io::Result<()> { 318 | Ok(()) 319 | } 320 | } 321 | impl Read for UdpRW { 322 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 323 | self.0.recv(buf) 324 | } 325 | } 326 | Ok(Stream::new(UdpRW(sock), close_udp)) 327 | } 328 | 329 | pub fn register(r: &mut Stack, o: Arc) { 330 | if !*IS_INITIALIZED.lock_ro() { 331 | register_stream_type("file", stream_file); 332 | register_stream_type("tcp", stream_tcp); 333 | register_stream_type("udp", stream_udp); 334 | *IS_INITIALIZED.lock() = true; 335 | } 336 | 337 | type Fn = fn(&mut Stack) -> OError; 338 | let fns: [(&str, Fn, u32); 7] = [ 339 | ("new-stream", new_stream, 1), 340 | ("write-stream", write_stream, 1), 341 | ("write-all-stream", write_all_stream, 0), 342 | ("flush-stream", flush_stream, 0), 343 | ("read-stream", read_stream, 1), 344 | ("read-all-stream", read_all_stream, 0), 345 | ("close-stream", close_stream, 0), 346 | ]; 347 | for f in fns { 348 | r.define_func( 349 | f.0.to_owned(), 350 | AFunc::new(Func { 351 | ret_count: f.2, 352 | to_call: FuncImpl::Native(f.1), 353 | run_as_base: false, 354 | origin: o.clone(), 355 | fname: None, 356 | name: f.0.to_owned(), 357 | }), 358 | ); 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /std.spl: -------------------------------------------------------------------------------- 1 | 2 | def null 3 | func =null { | pop 4 | "`null` must not be assigned a value!" panic 5 | } 6 | 7 | def program-name 8 | 9 | def std.alias.print &print =std.alias.print 10 | func print { | 11 | _str std.alias.print call 12 | } 13 | 14 | func println { | 15 | print "\n" print 16 | } 17 | 18 | construct error { 19 | kind 20 | message 21 | object 22 | trace 23 | mr-trace 24 | } 25 | 26 | construct FrameInfo { 27 | file 28 | function 29 | } 30 | 31 | construct _str_ext { 32 | ; 33 | new { any | with this ; 34 | null clone this settype:construct 35 | } 36 | to-bytes { [int] | str-to-bytes } 37 | split { str | with splitter this ; 38 | def bytes splitter:to-bytes =bytes 39 | def iter this:to-bytes:iter =iter 40 | def item 0 =item 41 | [ while { item null eq not } { 42 | def match 0 =match 43 | [ 44 | while { match bytes:len eq not } { 45 | iter:next =item 46 | item null eq if { 47 | 3 stop 48 | } 49 | item dup (match bytes:get) eq dup if { 50 | match ++ =match 51 | } not if { 52 | 0 =match 53 | } 54 | } 55 | { | pop pop } match:foreach 56 | ] _str 57 | } ] 58 | } 59 | } include _str_ext in str 60 | 61 | construct _mega-ext { 62 | ; 63 | swap { .. | with this ; 64 | this mswap 65 | this -- mswap 66 | } 67 | mswap { .. | mswap } 68 | foreach { | with callable this ; 69 | def i 0 =i 70 | while { i this lt } { i callable call i ++ =i } 71 | } 72 | } include _mega-ext in mega 73 | 74 | construct _array-ext { 75 | ; 76 | get { any | array-get } 77 | sget { any|null | with idx this ; 78 | idx this:len lt idx -1 gt and dup if { 79 | pop 80 | idx this:get 81 | 2 stop 82 | } not if { 83 | null 84 | } 85 | } 86 | len { mega | array-len } 87 | set { any | array-set } 88 | to-stack { .. | with this ; 89 | def len this:len =len 90 | def i 0 =i 91 | while { i len lt } { 92 | i this:get 93 | i ++ =i 94 | } 95 | } 96 | foreach { | with callable this ; 97 | def i 0 =i 98 | while { i this:len lt } { i this:get callable call i ++ =i } 99 | } 100 | to-str { str | bytes-to-str } 101 | sub { [any] | with begin end this ; 102 | this (end begin - anew) begin 0 (end begin -) acopy 103 | } 104 | 0 { any | with this ; 105 | 0 this:get 106 | } 107 | 1 { any | with this ; 108 | 1 this:get 109 | } 110 | 2 { any | with this ; 111 | 2 this:get 112 | } 113 | 3 { any | with this ; 114 | 3 this:get 115 | } 116 | 4 { any | with this ; 117 | 4 this:get 118 | } 119 | =0 { | with this ; 120 | 0 this:set; 121 | } 122 | =1 { | with this ; 123 | 1 this:set; 124 | } 125 | =2 { | with this ; 126 | 2 this:set; 127 | } 128 | =3 { | with this ; 129 | 3 this:set; 130 | } 131 | =4 { | with this ; 132 | 4 this:set; 133 | } 134 | } include _array-ext in array 135 | 136 | construct _func-ext { 137 | args 138 | ; 139 | call { | with this ; 140 | this:args null eq if { 141 | 0 anew this:=args 142 | } 143 | this:args:to-stack this call 144 | } 145 | add-args { this | with args this ; 146 | this:args null eq if { 147 | 0 anew this:=args 148 | } 149 | [ this:args:to-stack args:to-stack ] this:=args 150 | this 151 | } 152 | add-arg { this | with arg this ; 153 | this:args null eq if { 154 | 0 anew this:=args 155 | } 156 | [ this:args:to-stack arg ] this:=args 157 | this 158 | } 159 | } include _func-ext in func 160 | 161 | "#iter.spl" import 162 | 163 | construct List { 164 | array 165 | ; 166 | construct { this | with this ; 167 | 0 anew this:=array 168 | this 169 | } 170 | from { this | with array this ; 171 | array this:=array 172 | this 173 | } 174 | foreach { | :array:foreach } 175 | get { any | :array:get } 176 | sget { any|null | :array:sget } 177 | len { mega | :array:len } 178 | set { any | :array:set } 179 | to-stack { .. | :array:to-stack } 180 | to-str { str | :array:to-str } 181 | sub { [any] | :array:sub } 182 | } 183 | construct _GrowingArray { 184 | ; 185 | push-front { | with item this ; 186 | [ item this:array:to-stack ] this:=array 187 | } 188 | push { | with item this ; 189 | [ this:array:to-stack item ] this:=array 190 | } 191 | insert { | with item index this ; 192 | this:array:len index - =index 193 | [ this:array:to-stack index:mswap item (index ++):mswap ] this:=array 194 | } 195 | } 196 | construct _ShrinkingArray { 197 | ; 198 | pop-front { any | with this ; 199 | 0 this:remove 200 | } 201 | pop { any | with this ; 202 | this:array:len not if { 203 | null 2 stop 204 | } 205 | def item 206 | [ this:array:to-stack =item ] this:=array 207 | item 208 | } 209 | remove { any | with index this ; 210 | this:array:len not if { 211 | null 2 stop 212 | } 213 | def item 214 | this:array:len index - =index 215 | [ this:array:to-stack index:mswap =item (index --):mswap ] this:=array 216 | item 217 | } 218 | } 219 | 220 | include _GrowingArray in List 221 | include _ShrinkingArray in List 222 | 223 | construct ArrayIter { 224 | array 225 | idx 226 | ; 227 | construct { this | with array this ; 228 | array this:=array 229 | 0 this:=idx 230 | this 231 | } 232 | next { any | with this ; 233 | this:idx dup ++ this:=idx this:array:sget 234 | } 235 | } 236 | construct _IterableArray { 237 | ; 238 | iter { ArrayIter | with this ; 239 | this gettype "array" eq dup if { 240 | pop 241 | this ArrayIter:new 242 | 2 stop 243 | } not if { 244 | this:array ArrayIter:new 245 | } 246 | } 247 | } 248 | include _Iter in ArrayIter 249 | include _IterableArray in List 250 | include _IterableArray in array 251 | 252 | construct MicroMap { 253 | pairs 254 | ; 255 | construct { this | with this ; 256 | List:new this:=pairs 257 | this 258 | } 259 | from { this | with pairs this ; 260 | pairs this:=pairs 261 | this 262 | } 263 | get-entry { [any,any]|null | with key this ; 264 | this:pairs:iter 265 | { mega | 0 swap:get key eq } swap:filter 266 | :next 267 | } 268 | get-or-create-entry { [any,any] | with key this ; 269 | { [any,any] | 270 | [ key null ] dup this:pairs:push 271 | } key this:get-entry:unwrap-or 272 | } 273 | get { any | with key this ; 274 | this:pairs:iter 275 | { mega | 0 swap:get key eq } swap:filter 276 | { any | 1 swap:get } swap:map 277 | :next 278 | } 279 | set { any | with key val this ; 280 | val 1 (key this:get-or-create-entry):set 281 | } 282 | remove { any | with key this ; 283 | this:pairs:iter 284 | { mega | 0 swap:get key eq not } swap:filter 285 | :collect 286 | List:new:from 287 | =pairs 288 | } 289 | iter { ArrayIter | with this ; 290 | this:pairs:iter 291 | } 292 | foreach { | with callable this ; 293 | callable this:pairs:foreach 294 | } 295 | } 296 | 297 | construct Range { 298 | lower 299 | upper 300 | step 301 | ; 302 | construct { this | with lower upper this ; 303 | lower _mega this:=lower 304 | upper _mega this:=upper 305 | 1 this:=step 306 | this 307 | } 308 | set-step { this | with step this ; 309 | step this:=step 310 | this 311 | } 312 | iter { RangeIter | with this ; 313 | this RangeIter:new 314 | } 315 | item { mega|null | with index this ; 316 | def itm index this:step * this:lower + =itm 317 | (itm this:upper lt) (itm this:lower lt not) and dup if { 318 | pop 319 | itm 320 | 2 stop 321 | } not if { 322 | null 323 | 2 stop 324 | } 325 | } 326 | } 327 | 328 | construct RangeIter { 329 | range 330 | idx 331 | ; 332 | construct { this | with range this ; 333 | range this:=range 334 | 0 this:=idx 335 | this 336 | } 337 | next { mega | with this ; 338 | this:idx dup ++ this:=idx this:range:item 339 | } 340 | } 341 | 342 | include _Iter in RangeIter 343 | 344 | construct shadow { } 345 | 346 | func aadd { array | with arr1 arr2 ; 347 | 348 | def newarr arr1:len arr2:len + anew =newarr 349 | 350 | arr1 newarr 0 0 arr1:len acopy; 351 | arr2 newarr 0 arr1:len arr2:len acopy; 352 | 353 | newarr 354 | } 355 | 356 | func concat { str | with a b ; 357 | a _array b _array aadd _str 358 | } 359 | 360 | func nconcat { str | with amt ; 361 | _array 362 | (amt 1 -):foreach <{ { | pop 363 | swap _array swap aadd 364 | } } 365 | _str 366 | } 367 | 368 | def cached-results MicroMap:new =cached-results 369 | func cache { ... | with arg-amt id body ; 370 | def args arg-amt anew =args 371 | def i arg-amt -- =i 372 | while { i 0 lt not } { 373 | "from stack"; i args:set; 374 | i -- =i 375 | } 376 | def result [ args id ] cached-results:get =result 377 | result null eq if { 378 | args:to-stack body call dup =result [ args id ] swap cached-results:set; 379 | } 380 | result 381 | } 382 | 383 | func handle-panic { | with msg trace ; 384 | program-name dup if { 385 | program-name print " panicked at:" println 386 | } not if { 387 | "Program panicked at:" println 388 | } 389 | &println trace:foreach 390 | "\nPanic message:" println 391 | " " print msg println 392 | def map env =map 393 | "SPL_PANIC_DUMP" env:get dup if { 394 | "Dumping because SPL_PANIC_DUMP is set." println 395 | null =map 396 | dyn-__dump 397 | } not if { 398 | "SPL_PLAIN_PANIC" map:get dup if { 399 | "Not dumping because SPL_PLAIN_PANIC is set." println 400 | } not if { 401 | "Type 'Yes^M' to dump. You can set SPL_PANIC_DUMP to always dump " 402 | "on panic, or SPL_PLAIN_PANIC to never dump." concat println 403 | readln "Yes" eq if { 404 | null =map 405 | dyn-__dump 406 | } 407 | } 408 | } 409 | "Exiting." println 410 | 1 exit 411 | } 412 | 413 | func panic { | trace handle-panic } 414 | 415 | { | with msg this ; 416 | this not if { 417 | "Assertion failed!" panic 418 | } 419 | } "assert" "int" dyn-def-method 420 | 421 | { | with msg this ; 422 | this if { 423 | "Assertion failed!" panic 424 | } 425 | } "nassert" "int" dyn-def-method 426 | 427 | func assert-eq { any any | with a b ; 428 | a b eq not if { 429 | "Equality assertion failed!" panic 430 | } 431 | a b 432 | } 433 | 434 | func [ { shadow | 435 | "array" "shadow" settype 436 | } 437 | 438 | func ] { array | 439 | [ alit-end 440 | } 441 | 442 | func env { MicroMap | 443 | get-env List:new:from MicroMap:new:from 444 | } 445 | 446 | func ++ { mega | 447 | 1 + 448 | } 449 | 450 | func -- { mega | 451 | 1 - 452 | } 453 | 454 | func times { | with amount callable ; 455 | def i 0 =i 456 | while { i amount lt } { 457 | i callable call 458 | i ++ =i 459 | } 460 | } 461 | 462 | def _'has-been-called 0 =_'has-been-called 463 | func _ { | 464 | _'has-been-called not if { 465 | "WARN: The _ function is deprecated!" println 466 | 1 =_'has-been-called 467 | } 468 | } 469 | 470 | func call-main-on-file { | with file ; 471 | catch { 472 | "@" file concat import 473 | update-types 474 | argv main exit 475 | } 476 | with { with err ; 477 | err:message dup null eq if { 478 | pop 479 | "Uncaught error." 480 | } err:trace handle-panic 481 | } 482 | } 483 | 484 | func update-types { | 485 | { | with type ; 486 | { self | } "unwrap" type dyn-def-method 487 | { self | swap pop } "unwrap-or" type dyn-def-method 488 | } dyn-all-types:foreach 489 | { | with this ; 490 | "null cannot be unwrapped." panic 491 | } "unwrap" "null" dyn-def-method 492 | { any | with fb this ; 493 | fb call 494 | } "unwrap-or" "null" dyn-def-method 495 | } 496 | update-types 497 | 498 | "Adds a field to a namespace and initially sets it to the field's name."; 499 | func register-field { | with field-name namespace-name namespace ; 500 | field-name namespace-name dyn-def-field; 501 | namespace namespace-name settype ("=" namespace-name concat) dyn-call 502 | } 503 | 504 | -------------------------------------------------------------------------------- /src/lexer.rs: -------------------------------------------------------------------------------- 1 | use std::{mem, sync::Arc}; 2 | 3 | use crate::runtime::*; 4 | use readformat::*; 5 | 6 | #[derive(Debug, PartialEq, Eq)] 7 | pub enum LexerError { 8 | FunctionBlockExpected, 9 | WrongFunctionDeclaration, 10 | InvalidInclude, 11 | InvalidConstructBlock, 12 | InvalidNumber(String), 13 | ArgsWithoutCall, 14 | } 15 | 16 | pub fn lex(input: String) -> Result { 17 | let str_words = parse(input); 18 | Ok(read_block(&str_words[..], false)?.1) 19 | } 20 | 21 | fn read_block(str_words: &[String], isfn: bool) -> Result<(Option, Words, usize), LexerError> { 22 | if str_words.is_empty() { 23 | return Ok((None, Words::new(Vec::new()), 0)); 24 | } 25 | let mut rem = None; 26 | let mut words = Vec::new(); 27 | let mut i = 0; 28 | if str_words[0] == "{" && isfn { 29 | let mut r = 0_u32; 30 | while str_words[r as usize + 1] != "|" { 31 | r += 1; 32 | } 33 | i += r as usize + 2; 34 | rem = Some(r); 35 | } 36 | while i < str_words.len() { 37 | let word = str_words[i].to_owned(); 38 | match word.as_str() { 39 | "def" => { 40 | words.push(Word::Key(Keyword::Def(str_words[i + 1].to_owned()))); 41 | i += 1; 42 | } 43 | "func" => { 44 | if let Some(dat) = readf1("func\0{}\0{", str_words[i..=i + 2].join("\0").as_str()) { 45 | let block = read_block(&str_words[i + 2..], true)?; 46 | i += 2 + block.2; 47 | words.push(Word::Key(Keyword::Func( 48 | dat.to_owned(), 49 | block.0.ok_or(LexerError::FunctionBlockExpected)?, 50 | block.1, 51 | ))); 52 | } else if let Some(dat) = 53 | readf1("func\0{}\0@rust", str_words[i..=i + 2].join("\0").as_str()) 54 | { 55 | i += 3; 56 | words.push(Word::Key(Keyword::FuncOf( 57 | dat.to_owned(), 58 | str_words[i][2..].to_owned(), 59 | FuncImplType::Rust, 60 | ))); 61 | } else { 62 | return Err(LexerError::FunctionBlockExpected); 63 | } 64 | } 65 | // lambda 66 | "{" => { 67 | let block = read_block(&str_words[i..], true)?; 68 | i += block.2; 69 | words.push(Word::Const(Value::Func(AFunc::new(Func { 70 | ret_count: block.0.ok_or(LexerError::FunctionBlockExpected)?, 71 | to_call: FuncImpl::SPL(block.1), 72 | origin: Arc::new(Frame::dummy()), 73 | fname: None, 74 | name: "dyn".to_owned(), 75 | run_as_base: false, 76 | })))) 77 | } 78 | x if x.len() >= 2 && &x[0..2] == "!{" => { 79 | words.push(Word::Const(Value::Str(x[2..].to_owned()))); 80 | } 81 | "<{" => { 82 | let block = read_block(&str_words[i + 1..], false)?; 83 | i += block.2 + 1; 84 | let mut block = block.1.words; 85 | match words.remove(words.len() - 1) { 86 | Word::Call(a, b, c) => { 87 | words.append(&mut block); 88 | words.push(Word::Call(a, b, c)); 89 | } 90 | Word::ObjCall(a, b, c) => { 91 | words.push(Word::Key(Keyword::ObjPush)); 92 | words.append(&mut block); 93 | words.push(Word::Key(Keyword::ObjPop)); 94 | words.push(Word::ObjCall(a, b, c)); 95 | } 96 | _ => return Err(LexerError::ArgsWithoutCall), 97 | } 98 | } 99 | "construct" => { 100 | let name = str_words[i + 1].to_owned(); 101 | let is_namespace = if str_words[i + 2] == "namespace" { 102 | i += 1; 103 | true 104 | } else { 105 | false 106 | }; 107 | if str_words[i + 2] != "{" { 108 | return Err(LexerError::InvalidConstructBlock); 109 | } 110 | let mut fields = Vec::new(); 111 | i += 3; 112 | while str_words[i] != ";" && str_words[i] != "}" { 113 | fields.push(str_words[i].to_owned()); 114 | i += 1; 115 | } 116 | let mut methods = Vec::new(); 117 | let mut has_construct = false; 118 | if str_words[i] == ";" { 119 | i += 1; 120 | while str_words[i] != "}" { 121 | let name = str_words[i].to_owned(); 122 | if name == "construct" { 123 | has_construct = true; 124 | } 125 | let block = read_block(&str_words[i + 1..], true)?; 126 | i += 1 + block.2; 127 | methods.push(( 128 | name, 129 | (block.0.ok_or(LexerError::FunctionBlockExpected)?, block.1), 130 | )); 131 | i += 1; 132 | } 133 | } 134 | if !has_construct && !is_namespace { 135 | methods.push(("construct".to_string(), (1, Words { words: vec![] }))); 136 | } 137 | words.push(Word::Key(Keyword::Construct( 138 | name, 139 | fields, 140 | methods, 141 | is_namespace, 142 | ))); 143 | } 144 | "include" => { 145 | if let Some(x) = readf( 146 | "include\0{}\0in\0{}", 147 | str_words[i..i + 4].join("\0").as_str(), 148 | ) { 149 | words.push(Word::Key(Keyword::Include( 150 | x[0].to_owned(), 151 | x[1].to_owned(), 152 | ))) 153 | } else { 154 | return Err(LexerError::InvalidInclude); 155 | } 156 | i += 3; 157 | } 158 | "use" => { 159 | let item = str_words[i + 1].to_owned(); 160 | i += 1; 161 | words.push(Word::Key(Keyword::Use(item))); 162 | } 163 | "while" => { 164 | let cond = read_block(&str_words[i + 2..], false)?; 165 | i += 2 + cond.2; 166 | let blk = read_block(&str_words[i + 2..], false)?; 167 | i += 2 + blk.2; 168 | words.push(Word::Key(Keyword::While(cond.1, blk.1))); 169 | } 170 | "if" => { 171 | let blk = read_block(&str_words[i + 2..], false)?; 172 | i += 2 + blk.2; 173 | words.push(Word::Key(Keyword::If(blk.1))); 174 | } 175 | "catch" => { 176 | let mut types = Vec::new(); 177 | i += 1; 178 | while &str_words[i] != "{" { 179 | types.push(str_words[i].to_owned()); 180 | i += 1; 181 | } 182 | let blk = read_block(&str_words[i + 1..], false)?; 183 | i += 1 + blk.2; 184 | let ctch = read_block(&str_words[i + 1..], false)?; 185 | i += 1 + ctch.2; 186 | words.push(Word::Key(Keyword::Catch(types, blk.1, ctch.1))) 187 | } 188 | "with" => { 189 | let mut vars = Vec::new(); 190 | i += 1; 191 | while &str_words[i] != ";" { 192 | vars.push(str_words[i].to_owned()); 193 | i += 1; 194 | } 195 | words.push(Word::Key(Keyword::With(vars))); 196 | } 197 | "}" => { 198 | break; 199 | } 200 | x if x.starts_with('\"') => { 201 | words.push(Word::Const(Value::Str(x[1..].to_owned()))); 202 | } 203 | x if x.chars().all(|c| c.is_numeric() || c == '_' || c == '-') 204 | && !x.starts_with('_') 205 | && x.contains(char::is_numeric) => 206 | { 207 | words.push(Word::Const(Value::Mega( 208 | x.parse() 209 | .map_err(|_| LexerError::InvalidNumber(x.to_owned()))?, 210 | ))); 211 | } 212 | x if x 213 | .chars() 214 | .all(|c| c.is_numeric() || c == '.' || c == '_' || c == '-') 215 | && !x.starts_with('_') 216 | && x.contains(char::is_numeric) => 217 | { 218 | words.push(Word::Const(Value::Double( 219 | x.parse() 220 | .map_err(|_| LexerError::InvalidNumber(x.to_owned()))?, 221 | ))); 222 | } 223 | x => { 224 | let mut word = x.split(':').next().unwrap(); // SAFETY: One item always exists after a split. 225 | if !word.is_empty() { 226 | let mut ra = 0; 227 | while word.starts_with('&') { 228 | ra += 1; 229 | word = &word[1..]; 230 | } 231 | if let Some(word) = word.strip_suffix(';') { 232 | words.push(Word::Call(word.to_owned(), true, ra)); 233 | } else { 234 | words.push(Word::Call(word.to_owned(), false, ra)); 235 | } 236 | } 237 | for mut word in x.split(':').skip(1) { 238 | let mut ra = 0; 239 | while word.starts_with('&') { 240 | ra += 1; 241 | word = &word[1..]; 242 | } 243 | if let Some(word) = word.strip_suffix(';') { 244 | words.push(Word::ObjCall(word.to_owned(), true, ra)); 245 | } else { 246 | words.push(Word::ObjCall(word.to_owned(), false, ra)); 247 | } 248 | } 249 | } 250 | } 251 | i += 1; 252 | } 253 | Ok((rem, Words { words }, i)) 254 | } 255 | 256 | fn parse(input: String) -> Vec { 257 | let mut words = Vec::new(); 258 | let mut s = String::new(); 259 | 260 | let mut exclam = false; 261 | let mut raw = 0; 262 | 263 | for line in input.split('\n') { 264 | let mut in_string = false; 265 | let mut escaping = false; 266 | let mut was_in_string = false; 267 | for c in line.chars() { 268 | if in_string { 269 | if escaping { 270 | if raw == 0 { 271 | if c == '\\' { 272 | s += "\\"; 273 | } 274 | if c == 'n' { 275 | s += "\n"; 276 | } 277 | if c == 'r' { 278 | s += "\r"; 279 | } 280 | if c == '"' { 281 | s += "\""; 282 | } 283 | escaping = false; 284 | continue; 285 | } else { 286 | escaping = false; 287 | } 288 | } else if c == '"' { 289 | in_string = false; 290 | escaping = false; 291 | was_in_string = true; 292 | if raw == 0 { 293 | continue; 294 | } 295 | } 296 | if c == '\\' { 297 | escaping = true; 298 | if raw == 0 { 299 | continue; 300 | } 301 | } 302 | } else { 303 | if c == '"' { 304 | s += "\""; 305 | in_string = true; 306 | continue; 307 | } 308 | if raw == 0 { 309 | if c == ';' && was_in_string { 310 | s = String::new(); 311 | continue; 312 | } 313 | if c == '(' || c == ')' { 314 | continue; 315 | } 316 | if c == ' ' || c == '\t' { 317 | if s.is_empty() { 318 | continue; 319 | } 320 | words.push(s); 321 | s = String::new(); 322 | was_in_string = false; 323 | continue; 324 | } 325 | if c == '{' && exclam { 326 | raw = 1; 327 | } 328 | exclam = false; 329 | if c == '!' { 330 | exclam = true; 331 | } 332 | } else { 333 | if c == '{' { 334 | raw += 1; 335 | } 336 | if c == '}' { 337 | raw -= 1; 338 | } 339 | if raw == 0 { 340 | words.push(mem::take(&mut s)); 341 | continue; 342 | } 343 | } 344 | } 345 | was_in_string = false; 346 | s += String::from(c).as_str(); 347 | } 348 | if !s.is_empty() && raw == 0 { 349 | words.push(mem::take(&mut s)); 350 | } 351 | } 352 | if !s.is_empty() { 353 | words.push(mem::take(&mut s)); 354 | } 355 | words 356 | } 357 | -------------------------------------------------------------------------------- /src/sasm.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time::SystemTime}; 2 | 3 | use crate::{Frame, Func, FuncImpl, FuncImplType, Keyword, Value, Word, Words}; 4 | 5 | /// Reads sasm, the text representation of an SPL AST. 6 | pub fn sasm_read(s: String) -> Words { 7 | let mut lines = s.split("\n"); 8 | sasm_read_func(&mut lines) 9 | } 10 | 11 | pub fn sasm_read_func<'a>(lines: &mut impl Iterator) -> Words { 12 | let mut words = Vec::new(); 13 | while let Some(line) = lines.next() { 14 | let line = line.trim_start(); 15 | if line == "end" { 16 | break; 17 | } 18 | sasm_parse(line, &mut words, lines); 19 | } 20 | Words::new(words) 21 | } 22 | 23 | fn sasm_parse<'a>(line: &str, words: &mut Vec, lines: &mut impl Iterator) { 24 | let line: Vec<_> = line.split(" ").collect(); 25 | match line[0] { 26 | "dump" => words.push(Word::Key(Keyword::Dump)), 27 | "def" => words.push(Word::Key(Keyword::Def(line[1].to_owned()))), 28 | "func" => words.push(Word::Key(Keyword::Func( 29 | line[1].to_owned(), 30 | line[2].parse().expect("invalid sasm func: func ... NAN"), 31 | sasm_read_func(lines), 32 | ))), 33 | "construct" => { 34 | let name = line[1].to_owned(); 35 | let mut fields = Vec::new(); 36 | let mut methods = Vec::new(); 37 | let mut iter = line.into_iter().skip(2); 38 | for word in &mut iter { 39 | if word == ";" { 40 | break; 41 | } 42 | fields.push(word.to_owned()); 43 | } 44 | while let Some(word) = iter.next() { 45 | if word == ";" { 46 | break; 47 | } 48 | methods.push(( 49 | word.to_owned(), 50 | ( 51 | iter.next() 52 | .map(|x| x.parse().ok()) 53 | .flatten() 54 | .expect("invalid sasm construct: construct .... ; ... NAN ...."), 55 | sasm_read_func(lines), 56 | ), 57 | )); 58 | } 59 | words.push(Word::Key(Keyword::Construct( 60 | name, 61 | fields, 62 | methods, 63 | match iter.next() { 64 | None => false, 65 | Some(x) => x == "namespace", 66 | }, 67 | ))) 68 | } 69 | "include" => words.push(Word::Key(Keyword::Include( 70 | line[1].to_owned(), 71 | line[2].to_owned(), 72 | ))), 73 | "use" => words.push(Word::Key(Keyword::Use(line[1].to_owned()))), 74 | "while" => words.push(Word::Key(Keyword::While( 75 | sasm_read_func(lines), 76 | sasm_read_func(lines), 77 | ))), 78 | "if" => words.push(Word::Key(Keyword::If(sasm_read_func(lines)))), 79 | "with" => words.push(Word::Key(Keyword::With( 80 | line.into_iter().skip(1).map(ToOwned::to_owned).collect(), 81 | ))), 82 | "catch" => words.push(Word::Key(Keyword::Catch( 83 | line.into_iter().skip(1).map(ToOwned::to_owned).collect(), 84 | sasm_read_func(lines), 85 | sasm_read_func(lines), 86 | ))), 87 | "objpush" => words.push(Word::Key(Keyword::ObjPush)), 88 | "objpop" => words.push(Word::Key(Keyword::ObjPop)), 89 | "func_of_Rust" => { 90 | // output += &format!( 91 | // "func_of_{kind:?} {name} {t}\0\0{}\0\0end {t}\n", 92 | // content.replace("\0", "\0\x01").replace("\n", "\0\0") 93 | // ); 94 | let name = line[1]; 95 | let marker = line[2]; 96 | let mut s = String::new(); 97 | let mut line; 98 | while ( 99 | line = lines.next().expect("sasm string without end marker"), 100 | line, 101 | ) 102 | .1 103 | != "end ".to_owned() + marker 104 | { 105 | s = s + line + "\n"; 106 | } 107 | if let Some(l) = s.strip_suffix("\n") { 108 | s = l.to_owned(); 109 | } 110 | words.push(Word::Key(Keyword::FuncOf( 111 | name.to_owned(), 112 | s, 113 | FuncImplType::Rust, 114 | ))); 115 | } 116 | // main words 117 | "const" => match line[1] { 118 | "str" => { 119 | let marker = line[2]; 120 | let mut s = String::new(); 121 | let mut line; 122 | while ( 123 | line = lines.next().expect("sasm string without end marker"), 124 | line, 125 | ) 126 | .1 127 | != "end ".to_owned() + marker 128 | { 129 | s = s + line + "\n"; 130 | } 131 | if let Some(l) = s.strip_suffix("\n") { 132 | s = l.to_owned(); 133 | } 134 | words.push(Word::Const(Value::Str(s))); 135 | } 136 | "int" => { 137 | words.push(Word::Const(Value::Int( 138 | line[2].parse().expect("invalid sasm const: const int NAN"), 139 | ))); 140 | } 141 | "long" => { 142 | words.push(Word::Const(Value::Long( 143 | line[2].parse().expect("invalid sasm const: const long NAN"), 144 | ))); 145 | } 146 | "mega" => { 147 | words.push(Word::Const(Value::Mega( 148 | line[2].parse().expect("invalid sasm const: const mega NAN"), 149 | ))); 150 | } 151 | "float" => { 152 | words.push(Word::Const(Value::Float( 153 | line[2] 154 | .parse() 155 | .expect("invalid sasm const: const float NAN"), 156 | ))); 157 | } 158 | "double" => { 159 | words.push(Word::Const(Value::Double( 160 | line[2] 161 | .parse() 162 | .expect("invalid sasm const: const double NAN"), 163 | ))); 164 | } 165 | "func" => words.push(Word::Const(Value::Func(Arc::new(Func { 166 | ret_count: line[2].parse().expect("invalid sasm const: const fun NAN"), 167 | to_call: FuncImpl::SPL(sasm_read_func(lines)), 168 | origin: Arc::new(Frame::dummy()), 169 | fname: None, 170 | name: "dyn".to_owned(), 171 | run_as_base: false, 172 | })))), 173 | "null" => words.push(Word::Const(Value::Null)), 174 | "array" => panic!("invalid sasm const: array - not all Values can be consts!"), 175 | _ => panic!("invalid sasm const: {}", line[1]), 176 | }, 177 | "call" => { 178 | let name = line[1].to_owned(); 179 | let mut rem = false; 180 | let mut ra = 0; 181 | for word in line.into_iter().skip(2) { 182 | if word == "pop" { 183 | rem = true; 184 | } else if word == "ref" { 185 | ra += 1; 186 | } else { 187 | panic!("invalid sasm call: words after name must be either `pop` or `ref`"); 188 | } 189 | } 190 | words.push(Word::Call(name, rem, ra)); 191 | } 192 | "objcall" => { 193 | let name = line[1].to_owned(); 194 | let mut rem = false; 195 | let mut ra = 0; 196 | for word in line.into_iter().skip(2) { 197 | if word == "pop" { 198 | rem = true; 199 | } else if word == "ref" { 200 | ra += 1; 201 | } else { 202 | panic!("invalid sasm objcall: words after name must be either `pop` or `ref`"); 203 | } 204 | } 205 | words.push(Word::ObjCall(name, rem, ra)); 206 | } 207 | "" => {} 208 | _ => panic!("invalid sasm instruction: {}", line[0]), 209 | } 210 | } 211 | 212 | pub fn sasm_write(words: Words) -> String { 213 | sasm_write_func(words) 214 | .replace("\0\0", "\n") 215 | .replace("\0\x01", "\0") 216 | } 217 | 218 | fn sasm_write_func(words: Words) -> String { 219 | let mut output = String::new(); 220 | for word in words.words { 221 | match word { 222 | Word::Key(word) => match word { 223 | Keyword::Dump => { 224 | output += "dump\n"; 225 | } 226 | Keyword::Def(x) => { 227 | output += "def "; 228 | output += &x; 229 | output += "\n"; 230 | } 231 | Keyword::Func(name, returns, text) => { 232 | output += &format!("func {name} {returns}\n\t"); 233 | let text = sasm_write_func(text).replace("\n", "\n\t"); 234 | let text = text.trim_end(); 235 | output += &text; 236 | output += "\nend\n"; 237 | } 238 | Keyword::Construct(name, vars, methods, is_namespace) => { 239 | output += &format!("construct {name} "); 240 | for var in vars { 241 | output += &var; 242 | output += " "; 243 | } 244 | output += ";"; 245 | for method in &methods { 246 | output += " "; 247 | output += &method.0; 248 | output += " "; 249 | output += &method.1 .0.to_string(); 250 | } 251 | if is_namespace { 252 | output += " ; namespace"; 253 | } 254 | output += "\n"; 255 | for method in methods { 256 | output += "\t"; 257 | output += &sasm_write_func(method.1 .1) 258 | .replace("\n", "\n\t") 259 | .trim_end(); 260 | output += "\nend\n"; 261 | } 262 | } 263 | Keyword::Include(type_to_include, t) => { 264 | output += &format!("include {type_to_include} {t}\n"); 265 | } 266 | Keyword::Use(path) => output += &format!("use {path}\n"), 267 | Keyword::While(cond, blk) => { 268 | output += "while\n\t"; 269 | output += &sasm_write_func(cond).replace("\n", "\n\t").trim_end(); 270 | output += "\nend\n\t"; 271 | output += &sasm_write_func(blk).replace("\n", "\n\t").trim_end(); 272 | output += "\nend\n"; 273 | } 274 | Keyword::If(blk) => { 275 | output += "if\n\t"; 276 | output += &sasm_write_func(blk).replace("\n", "\n\t").trim_end(); 277 | output += "\nend\n"; 278 | } 279 | Keyword::With(items) => { 280 | output += "with"; 281 | for item in items { 282 | output += " "; 283 | output += &item; 284 | } 285 | output += "\n"; 286 | } 287 | Keyword::Catch(kinds, blk, ctch) => { 288 | output += "catch"; 289 | for kind in kinds { 290 | output += " "; 291 | output += &kind; 292 | } 293 | output += "\n\t"; 294 | output += &sasm_write_func(blk).replace("\n", "\n\t").trim_end(); 295 | output += "\nend\n\t"; 296 | output += &sasm_write_func(ctch).replace("\n", "\n\t").trim_end(); 297 | output += "\nend\n"; 298 | } 299 | Keyword::ObjPush => output += "objpush\n", 300 | Keyword::ObjPop => output += "objpop\n", 301 | Keyword::FuncOf(name, content, kind) => { 302 | fn time() -> String { 303 | SystemTime::now() 304 | .duration_since(SystemTime::UNIX_EPOCH) 305 | .unwrap() 306 | .as_micros() 307 | .to_string() 308 | } 309 | let mut t = time(); 310 | while content.contains(&t) { 311 | t = time(); 312 | } 313 | output += &format!( 314 | "func_of_{kind:?} {name} {t}\0\0{}\0\0end {t}\n", 315 | content.replace("\0", "\0\x01").replace("\n", "\0\0") 316 | ); 317 | } 318 | }, 319 | Word::Const(item) => match item { 320 | Value::Null => output += "const null\n", 321 | Value::Int(x) => output += &format!("const int {x}\n"), 322 | Value::Long(x) => output += &format!("const long {x}\n"), 323 | Value::Mega(x) => output += &format!("const mega {x}\n"), 324 | Value::Float(x) => output += &format!("const float {x}\n"), 325 | Value::Double(x) => output += &format!("const double {x}\n"), 326 | Value::Func(x) => { 327 | let text = match &x.to_call { 328 | FuncImpl::Native(_) => panic!("sasm can't write native function"), 329 | FuncImpl::NativeDyn(_) => panic!("sasm can't write native function"), 330 | FuncImpl::SPL(x) => sasm_write_func(x.clone()).replace("\n", "\n\t"), 331 | }; 332 | let text = text.trim_end(); 333 | output += &format!("const func {}\n\t{}\nend\n", x.ret_count, text); 334 | } 335 | Value::Array(_) => panic!("sasm can't write arrays"), 336 | Value::Str(text) => { 337 | fn time() -> String { 338 | SystemTime::now() 339 | .duration_since(SystemTime::UNIX_EPOCH) 340 | .unwrap() 341 | .as_micros() 342 | .to_string() 343 | } 344 | let mut t = time(); 345 | while text.contains(&t) { 346 | t = time(); 347 | } 348 | output += &format!( 349 | "const str {t}\0\0{}\0\0end {t}\n", 350 | text.replace("\0", "\0\x01").replace("\n", "\0\0") 351 | ); 352 | } 353 | }, 354 | Word::Call(name, rem, ra) => { 355 | output += "call "; 356 | output += &name; 357 | if rem { 358 | output += " pop"; 359 | } 360 | for _ in 0..ra { 361 | output += " ref"; 362 | } 363 | output += "\n"; 364 | } 365 | Word::ObjCall(name, rem, ra) => { 366 | output += "objcall "; 367 | output += &name; 368 | if rem { 369 | output += " pop"; 370 | } 371 | for _ in 0..ra { 372 | output += " ref"; 373 | } 374 | output += "\n"; 375 | } 376 | } 377 | } 378 | output 379 | } 380 | -------------------------------------------------------------------------------- /src/std_fns.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::VecDeque, 3 | env::{args, vars}, 4 | fs, 5 | io::{stdin, stdout, Write}, 6 | mem, 7 | ops::{Add, Div, Mul, Rem, Sub}, 8 | process::{self, Stdio}, 9 | sync::Arc, 10 | }; 11 | 12 | use crate::{dyn_fns, mutex::Mut, runtime::*, sasm::sasm_write, *}; 13 | 14 | #[macro_export] 15 | macro_rules! type_err { 16 | ($stack:expr, $a:expr, $b:expr) => { 17 | $stack.err(ErrorKind::InvalidType($a.to_owned(), $b.to_owned()))? 18 | }; 19 | } 20 | 21 | macro_rules! array { 22 | ($stack:expr, $i:expr) => { 23 | || { 24 | $stack.error(ErrorKind::PropertyNotFound( 25 | "array".to_owned(), 26 | $i.to_string(), 27 | )) 28 | } 29 | }; 30 | } 31 | 32 | pub fn print(stack: &mut Stack) -> OError { 33 | let Value::Str(s) = stack.pop().lock_ro().native.clone() else { 34 | return stack.err(ErrorKind::InvalidCall("print".to_owned())) 35 | }; 36 | print!("{s}"); 37 | stdout().lock().flush().unwrap(); 38 | Ok(()) 39 | } 40 | 41 | pub fn clone(stack: &mut Stack) -> OError { 42 | let o = stack.pop(); 43 | stack.push(Arc::new(Mut::new(o.lock_ro().clone()))); 44 | Ok(()) 45 | } 46 | 47 | pub fn dup(stack: &mut Stack) -> OError { 48 | let o = stack.peek(); 49 | stack.push(o); 50 | Ok(()) 51 | } 52 | 53 | pub fn pop(stack: &mut Stack) -> OError { 54 | stack.pop(); 55 | Ok(()) 56 | } 57 | 58 | pub fn swap(stack: &mut Stack) -> OError { 59 | let a = stack.pop(); 60 | let b = stack.pop(); 61 | stack.push(a); 62 | stack.push(b); 63 | Ok(()) 64 | } 65 | 66 | pub fn mswap(stack: &mut Stack) -> OError { 67 | let Value::Mega(i) = stack.pop().lock_ro().native.clone() else { 68 | return stack.err(ErrorKind::InvalidCall("nswap".to_owned())) 69 | }; 70 | let mut array = VecDeque::with_capacity(i as usize); 71 | for _ in 0..i { 72 | array.push_back(stack.pop()); 73 | } 74 | for _ in 0..i { 75 | // SAFETY: Items must exist because they are added in the previous loop 76 | stack.push(array.pop_front().unwrap()); 77 | } 78 | Ok(()) 79 | } 80 | 81 | pub fn settype(stack: &mut Stack) -> OError { 82 | let Value::Str(s) = stack.pop().lock_ro().native.clone() else { 83 | return stack.err(ErrorKind::InvalidCall("settype".to_owned())) 84 | }; 85 | let o = stack.pop(); 86 | let kind = runtime(|rt| rt.get_type_by_name(&s)) 87 | .ok_or_else(|| stack.error(ErrorKind::TypeNotFound(s)))?; 88 | let mut obj = o.lock(); 89 | kind.lock_ro().write_into(&mut obj); 90 | obj.kind = kind; 91 | mem::drop(obj); 92 | stack.push(o); 93 | Ok(()) 94 | } 95 | 96 | pub fn gettype(stack: &mut Stack) -> OError { 97 | let o = stack.pop(); 98 | stack.push(Value::Str(o.lock_ro().kind.lock_ro().get_name()).spl()); 99 | Ok(()) 100 | } 101 | 102 | pub fn array_new(stack: &mut Stack) -> OError { 103 | let Value::Mega(i) = stack.pop().lock_ro().native.clone() else { 104 | return stack.err(ErrorKind::InvalidCall("anew".to_owned())) 105 | }; 106 | stack.push(Value::Array(vec![Value::Null.spl(); i as usize]).spl()); 107 | Ok(()) 108 | } 109 | 110 | pub fn array_len(stack: &mut Stack) -> OError { 111 | let binding = stack.pop(); 112 | let Value::Array(ref a) = binding.lock_ro().native else { 113 | return stack.err(ErrorKind::InvalidCall("array-len".to_owned())) 114 | }; 115 | stack.push(Value::Mega(a.len() as i128).spl()); 116 | Ok(()) 117 | } 118 | 119 | pub fn array_get(stack: &mut Stack) -> OError { 120 | let binding = stack.pop(); 121 | let Value::Array(ref a) = binding.lock_ro().native else { 122 | return stack.err(ErrorKind::InvalidCall("array-get".to_owned())) 123 | }; 124 | let Value::Mega(i) = stack.pop().lock_ro().native.clone() else { 125 | return stack.err(ErrorKind::InvalidCall("array-get".to_owned())) 126 | }; 127 | stack.push(a.get(i as usize).ok_or_else(array!(stack, i))?.clone()); 128 | Ok(()) 129 | } 130 | 131 | pub fn array_set(stack: &mut Stack) -> OError { 132 | let binding = stack.pop(); 133 | let Value::Array(ref mut a) = binding.lock().native else { 134 | return stack.err(ErrorKind::InvalidCall("array-set".to_owned())) 135 | }; 136 | let Value::Mega(i) = stack.pop().lock_ro().native.clone() else { 137 | return stack.err(ErrorKind::InvalidCall("array-set".to_owned())) 138 | }; 139 | let o = stack.pop(); 140 | stack.push(a.get(i as usize).ok_or_else(array!(stack, i))?.clone()); 141 | *a.get_mut(i as usize).ok_or_else(array!(stack, i))? = o; 142 | Ok(()) 143 | } 144 | 145 | pub fn eq(stack: &mut Stack) -> OError { 146 | let b = stack.pop(); 147 | let a = stack.pop(); 148 | stack.push(Value::Int(if a == b { 1 } else { 0 }).spl()); 149 | Ok(()) 150 | } 151 | 152 | pub fn lt(stack: &mut Stack) -> OError { 153 | let b = stack.pop(); 154 | let a = stack.pop(); 155 | stack.push(Value::Int(if a < b { 1 } else { 0 }).spl()); 156 | Ok(()) 157 | } 158 | 159 | pub fn gt(stack: &mut Stack) -> OError { 160 | let b = stack.pop(); 161 | let a = stack.pop(); 162 | stack.push(Value::Int(if a > b { 1 } else { 0 }).spl()); 163 | Ok(()) 164 | } 165 | 166 | pub fn not(stack: &mut Stack) -> OError { 167 | let o = stack.pop(); 168 | stack.push(Value::Int(if o.lock_ro().is_truthy() { 0 } else { 1 }).spl()); 169 | Ok(()) 170 | } 171 | 172 | pub fn and(stack: &mut Stack) -> OError { 173 | let a = stack.pop(); 174 | let b = stack.pop(); 175 | stack.push( 176 | Value::Int(if a.lock_ro().is_truthy() && b.lock_ro().is_truthy() { 177 | 1 178 | } else { 179 | 0 180 | }) 181 | .spl(), 182 | ); 183 | Ok(()) 184 | } 185 | 186 | pub fn or(stack: &mut Stack) -> OError { 187 | let a = stack.pop(); 188 | let b = stack.pop(); 189 | stack.push( 190 | Value::Int(if a.lock_ro().is_truthy() || b.lock_ro().is_truthy() { 191 | 1 192 | } else { 193 | 0 194 | }) 195 | .spl(), 196 | ); 197 | Ok(()) 198 | } 199 | 200 | macro_rules! impl_op { 201 | ($a:expr, $b:expr, $op:tt, $err:expr, $($kind:tt,)*) => { 202 | match ($a, $b) { 203 | $( 204 | (Value::$kind(a), Value::$kind(b)) => Value::$kind(a.$op(b)), 205 | )* 206 | _ => $err?, 207 | } 208 | }; 209 | } 210 | 211 | pub fn plus(stack: &mut Stack) -> OError { 212 | let b = stack.pop().lock_ro().native.clone(); 213 | let a = stack.pop().lock_ro().native.clone(); 214 | stack.push( 215 | impl_op!( 216 | a, 217 | b, 218 | add, 219 | stack.err(ErrorKind::InvalidCall("plus".to_owned())), 220 | Mega, 221 | Long, 222 | Int, 223 | ) 224 | .spl(), 225 | ); 226 | Ok(()) 227 | } 228 | 229 | pub fn minus(stack: &mut Stack) -> OError { 230 | let b = stack.pop().lock_ro().native.clone(); 231 | let a = stack.pop().lock_ro().native.clone(); 232 | stack.push( 233 | impl_op!( 234 | a, 235 | b, 236 | sub, 237 | stack.err(ErrorKind::InvalidCall("minus".to_owned())), 238 | Mega, 239 | Long, 240 | Int, 241 | ) 242 | .spl(), 243 | ); 244 | Ok(()) 245 | } 246 | 247 | pub fn slash(stack: &mut Stack) -> OError { 248 | let b = stack.pop().lock_ro().native.clone(); 249 | let a = stack.pop().lock_ro().native.clone(); 250 | stack.push( 251 | impl_op!( 252 | a, 253 | b, 254 | div, 255 | stack.err(ErrorKind::InvalidCall("slash".to_owned())), 256 | Mega, 257 | Long, 258 | Int, 259 | ) 260 | .spl(), 261 | ); 262 | Ok(()) 263 | } 264 | 265 | pub fn star(stack: &mut Stack) -> OError { 266 | let b = stack.pop().lock_ro().native.clone(); 267 | let a = stack.pop().lock_ro().native.clone(); 268 | stack.push( 269 | impl_op!( 270 | a, 271 | b, 272 | mul, 273 | stack.err(ErrorKind::InvalidCall("star".to_owned())), 274 | Mega, 275 | Long, 276 | Int, 277 | ) 278 | .spl(), 279 | ); 280 | Ok(()) 281 | } 282 | 283 | pub fn percent(stack: &mut Stack) -> OError { 284 | let b = stack.pop().lock_ro().native.clone(); 285 | let a = stack.pop().lock_ro().native.clone(); 286 | stack.push( 287 | impl_op!( 288 | a, 289 | b, 290 | rem, 291 | stack.err(ErrorKind::InvalidCall("star".to_owned())), 292 | Mega, 293 | Long, 294 | Int, 295 | ) 296 | .spl(), 297 | ); 298 | Ok(()) 299 | } 300 | 301 | pub fn to_int(stack: &mut Stack) -> OError { 302 | let o = stack.pop().lock_ro().native.clone(); 303 | stack.push( 304 | Value::Int(match o { 305 | Value::Null => type_err!(stack, "null", "int"), 306 | Value::Int(x) => x, 307 | Value::Long(x) => x as i32, 308 | Value::Mega(x) => x as i32, 309 | Value::Float(x) => x as i32, 310 | Value::Double(x) => x as i32, 311 | Value::Func(_) => type_err!(stack, "func", "int"), 312 | Value::Array(_) => type_err!(stack, "array", "int"), 313 | Value::Str(x) => x 314 | .parse() 315 | .map_err(|_| stack.error(ErrorKind::Parse(x, "int".to_owned())))?, 316 | }) 317 | .spl(), 318 | ); 319 | Ok(()) 320 | } 321 | 322 | pub fn to_long(stack: &mut Stack) -> OError { 323 | let o = stack.pop().lock_ro().native.clone(); 324 | stack.push( 325 | Value::Long(match o { 326 | Value::Null => type_err!(stack, "null", "long"), 327 | Value::Int(x) => x as i64, 328 | Value::Long(x) => x, 329 | Value::Mega(x) => x as i64, 330 | Value::Float(x) => x as i64, 331 | Value::Double(x) => x as i64, 332 | Value::Func(_) => type_err!(stack, "func", "long"), 333 | Value::Array(_) => type_err!(stack, "array", "long"), 334 | Value::Str(x) => x 335 | .parse() 336 | .map_err(|_| stack.error(ErrorKind::Parse(x, "long".to_owned())))?, 337 | }) 338 | .spl(), 339 | ); 340 | Ok(()) 341 | } 342 | 343 | pub fn to_mega(stack: &mut Stack) -> OError { 344 | let o = stack.pop().lock_ro().native.clone(); 345 | stack.push( 346 | Value::Mega(match o { 347 | Value::Null => type_err!(stack, "null", "mega"), 348 | Value::Int(x) => x as i128, 349 | Value::Long(x) => x as i128, 350 | Value::Mega(x) => x, 351 | Value::Float(x) => x as i128, 352 | Value::Double(x) => x as i128, 353 | Value::Func(_) => type_err!(stack, "func", "mega"), 354 | Value::Array(_) => type_err!(stack, "array", "mega"), 355 | Value::Str(x) => x 356 | .parse() 357 | .map_err(|_| stack.error(ErrorKind::Parse(x, "mega".to_owned())))?, 358 | }) 359 | .spl(), 360 | ); 361 | Ok(()) 362 | } 363 | 364 | pub fn to_float(stack: &mut Stack) -> OError { 365 | let o = stack.pop().lock_ro().native.clone(); 366 | stack.push( 367 | Value::Float(match o { 368 | Value::Null => type_err!(stack, "null", "float"), 369 | Value::Int(x) => x as f32, 370 | Value::Long(x) => x as f32, 371 | Value::Mega(x) => x as f32, 372 | Value::Float(x) => x, 373 | Value::Double(x) => x as f32, 374 | Value::Func(_) => type_err!(stack, "func", "float"), 375 | Value::Array(_) => type_err!(stack, "array", "float"), 376 | Value::Str(x) => x 377 | .parse() 378 | .map_err(|_| stack.error(ErrorKind::Parse(x, "float".to_owned())))?, 379 | }) 380 | .spl(), 381 | ); 382 | Ok(()) 383 | } 384 | 385 | pub fn to_double(stack: &mut Stack) -> OError { 386 | let o = stack.pop().lock_ro().native.clone(); 387 | stack.push( 388 | Value::Double(match o { 389 | Value::Null => type_err!(stack, "null", "double"), 390 | Value::Int(x) => x as f64, 391 | Value::Long(x) => x as f64, 392 | Value::Mega(x) => x as f64, 393 | Value::Float(x) => x as f64, 394 | Value::Double(x) => x, 395 | Value::Func(_) => type_err!(stack, "func", "double"), 396 | Value::Array(_) => type_err!(stack, "array", "double"), 397 | Value::Str(x) => x 398 | .parse() 399 | .map_err(|_| stack.error(ErrorKind::Parse(x, "double".to_owned())))?, 400 | }) 401 | .spl(), 402 | ); 403 | Ok(()) 404 | } 405 | 406 | pub fn to_array(stack: &mut Stack) -> OError { 407 | let o = stack.pop().lock_ro().native.clone(); 408 | stack.push( 409 | Value::Array(match o { 410 | Value::Null => type_err!(stack, "null", "array"), 411 | Value::Int(_) => type_err!(stack, "int", "array"), 412 | Value::Long(_) => type_err!(stack, "long", "array"), 413 | Value::Mega(_) => type_err!(stack, "mega", "array"), 414 | Value::Float(_) => type_err!(stack, "float", "array"), 415 | Value::Double(_) => type_err!(stack, "double", "array"), 416 | Value::Func(_) => type_err!(stack, "func", "array"), 417 | Value::Array(x) => x, 418 | Value::Str(x) => x 419 | .chars() 420 | .map(|x| Value::Int(x as u32 as i32).spl()) 421 | .collect(), 422 | }) 423 | .spl(), 424 | ); 425 | Ok(()) 426 | } 427 | 428 | pub fn to_str(stack: &mut Stack) -> OError { 429 | let o = stack.pop().lock_ro().native.clone(); 430 | stack.push( 431 | Value::Str(match o { 432 | Value::Null => type_err!(stack, "null", "str"), 433 | Value::Int(x) => x.to_string(), 434 | Value::Long(x) => x.to_string(), 435 | Value::Mega(x) => x.to_string(), 436 | Value::Float(x) => x.to_string(), 437 | Value::Double(x) => x.to_string(), 438 | Value::Func(_) => type_err!(stack, "func", "str"), 439 | Value::Array(x) => { 440 | let iter: Vec<_> = x 441 | .into_iter() 442 | .map(|x| match &x.lock_ro().native { 443 | Value::Int(x) => char::from_u32(*x as u32).ok_or_else(|| { 444 | stack.error(ErrorKind::InvalidType( 445 | format!("int-{x}"), 446 | "__str-element".to_owned(), 447 | )) 448 | }), 449 | _ => stack.err(ErrorKind::InvalidType( 450 | "int".to_owned(), 451 | "__str-element".to_owned(), 452 | )), 453 | }) 454 | .collect(); 455 | let mut fixed = String::with_capacity(iter.len()); 456 | for item in iter { 457 | fixed.push(item?); 458 | } 459 | fixed 460 | } 461 | Value::Str(x) => x, 462 | }) 463 | .spl(), 464 | ); 465 | Ok(()) 466 | } 467 | 468 | pub fn call(stack: &mut Stack) -> OError { 469 | let Value::Func(a) = stack.pop().lock_ro().native.clone() else { 470 | return stack.err(ErrorKind::InvalidCall("call".to_owned())) 471 | }; 472 | stack.call(&a) 473 | } 474 | 475 | pub fn callp(stack: &mut Stack) -> OError { 476 | let Value::Func(a) = stack.pop().lock_ro().native.clone() else { 477 | return stack.err(ErrorKind::InvalidCall("callp".to_owned())) 478 | }; 479 | stack.call(&a)?; 480 | for _ in 0..a.ret_count { 481 | stack.pop(); 482 | } 483 | Ok(()) 484 | } 485 | 486 | pub fn trace(stack: &mut Stack) -> OError { 487 | let trace = stack.trace(); 488 | stack.push(Value::Array(trace.into_iter().map(|x| Value::Str(x).spl()).collect()).spl()); 489 | Ok(()) 490 | } 491 | 492 | pub fn mr_trace(stack: &mut Stack) -> OError { 493 | let trace = stack.mr_trace(); 494 | stack.push( 495 | Value::Array( 496 | trace 497 | .into_iter() 498 | .map(|x| Value::Array(x.into_iter().map(|x| x.spl()).collect()).spl()) 499 | .collect(), 500 | ) 501 | .spl(), 502 | ); 503 | Ok(()) 504 | } 505 | 506 | pub fn exit(stack: &mut Stack) -> OError { 507 | let Value::Int(a) = stack.pop().lock_ro().native.clone().try_mega_to_int() else { 508 | return stack.err(ErrorKind::InvalidCall("exit".to_owned())) 509 | }; 510 | process::exit(a) 511 | } 512 | 513 | pub fn exec(stack: &mut Stack) -> OError { 514 | let Value::Func(a) = stack.pop().lock_ro().native.clone() else { 515 | return stack.err(ErrorKind::InvalidCall("exec".to_owned())) 516 | }; 517 | unsafe { 518 | let f = stack.pop_frame(0); 519 | let r = a.to_call.call(stack); 520 | stack.push_frame(f); 521 | r 522 | } 523 | } 524 | 525 | pub fn exec2(stack: &mut Stack) -> OError { 526 | let Value::Func(a) = stack.pop().lock_ro().native.clone() else { 527 | return stack.err(ErrorKind::InvalidCall("exec2".to_owned())) 528 | }; 529 | unsafe { 530 | let f = stack.pop_frame(0); 531 | let f1 = stack.pop_frame(0); 532 | let r = a.to_call.call(stack); 533 | stack.push_frame(f1); 534 | stack.push_frame(f); 535 | r 536 | } 537 | } 538 | 539 | pub fn exec3(stack: &mut Stack) -> OError { 540 | let Value::Func(a) = stack.pop().lock_ro().native.clone() else { 541 | return stack.err(ErrorKind::InvalidCall("exec3".to_owned())) 542 | }; 543 | unsafe { 544 | let f = stack.pop_frame(0); 545 | let f1 = stack.pop_frame(0); 546 | let f2 = stack.pop_frame(0); 547 | let r = a.to_call.call(stack); 548 | stack.push_frame(f2); 549 | stack.push_frame(f1); 550 | stack.push_frame(f); 551 | r 552 | } 553 | } 554 | 555 | pub fn stop(stack: &mut Stack) -> OError { 556 | let Value::Int(i) = stack.pop().lock_ro().native.clone().try_mega_to_int() else { 557 | return stack.err(ErrorKind::InvalidCall("stop".to_owned())) 558 | }; 559 | stack.return_accumultor += i as u32; 560 | Ok(()) 561 | } 562 | 563 | pub fn argv(stack: &mut Stack) -> OError { 564 | stack.push(Value::Array(args().into_iter().map(|x| Value::Str(x).spl()).collect()).spl()); 565 | Ok(()) 566 | } 567 | 568 | pub fn get_env(stack: &mut Stack) -> OError { 569 | stack.push( 570 | Value::Array( 571 | vars() 572 | .into_iter() 573 | .map(|x| Value::Array(vec![Value::Str(x.0).spl(), Value::Str(x.1).spl()]).spl()) 574 | .collect(), 575 | ) 576 | .spl(), 577 | ); 578 | Ok(()) 579 | } 580 | 581 | pub fn read_file(stack: &mut Stack) -> OError { 582 | let Value::Str(s) = stack.pop().lock_ro().native.clone() else { 583 | return stack.err(ErrorKind::InvalidCall("read_file".to_owned())) 584 | }; 585 | stack.push( 586 | Value::Str( 587 | fs::read_to_string(s).map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))?, 588 | ) 589 | .spl(), 590 | ); 591 | Ok(()) 592 | } 593 | 594 | pub fn alit_end(stack: &mut Stack) -> OError { 595 | let s = stack.pop(); 596 | let popped = stack.pop_until(s); 597 | stack.push(Value::Array(popped).spl()); 598 | Ok(()) 599 | } 600 | 601 | pub fn import(stack: &mut Stack) -> OError { 602 | let Value::Str(mut s) = stack.pop().lock_ro().native.clone() else { 603 | return stack.err(ErrorKind::InvalidCall("import".to_owned())) 604 | }; 605 | let fallback = s 606 | .as_str() 607 | .rsplit_once(|x| x == '/' || x == '#') 608 | .map(|(.., x)| x) 609 | .unwrap_or(s.as_str()); 610 | let fallback = runtime(|x| { 611 | for (&p, &data) in &x.embedded_files { 612 | if fallback == p { 613 | return Some(data.to_owned()); 614 | } 615 | } 616 | None 617 | }); 618 | if let Some(x) = s.strip_prefix('#') { 619 | s = find_in_splpath(x).unwrap_or(x.to_owned()); 620 | } else if let Some(x) = s.strip_prefix('@') { 621 | s = x.to_owned(); 622 | } else { 623 | s = stack 624 | .peek_frame(1) 625 | .origin 626 | .file 627 | .rsplit_once('/') 628 | .map(|x| x.0) 629 | .unwrap_or(".") 630 | .to_owned() 631 | + "/" 632 | + &s; 633 | } 634 | if stack.include_file( 635 | &(*fs::canonicalize(s.clone()) 636 | .unwrap_or_else(|_| s.clone().into()) 637 | .as_os_str() 638 | .to_string_lossy()) 639 | .to_owned(), 640 | ) { 641 | stack.push(Value::Str(s.clone()).spl()); 642 | dup(stack)?; 643 | read_file(stack).or_else(|x| { 644 | if let Some(fallback) = fallback { 645 | stack.push(Value::Str(fallback.to_owned()).spl()); 646 | Ok(()) 647 | } else { 648 | Err(x) 649 | } 650 | })?; 651 | dyn_fns::wrap(if s.ends_with(".sasm") { 652 | dyn_fns::dyn_sasmf 653 | } else { 654 | dyn_fns::dyn_readf 655 | })(stack)?; 656 | call(stack)?; 657 | } 658 | Ok(()) 659 | } 660 | 661 | pub fn readln(stack: &mut Stack) -> OError { 662 | let mut s = String::new(); 663 | stdin() 664 | .read_line(&mut s) 665 | .map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))?; 666 | let s = if let Some(s) = s.strip_suffix("\r\n") { 667 | s.to_owned() 668 | } else { 669 | s 670 | }; 671 | let s = if let Some(s) = s.strip_suffix('\n') { 672 | s.to_owned() 673 | } else { 674 | s 675 | }; 676 | stack.push(Value::Str(s).spl()); 677 | Ok(()) 678 | } 679 | 680 | pub fn command(stack: &mut Stack) -> OError { 681 | let binding = stack.pop(); 682 | let Value::Array(ref a) = binding.lock_ro().native else { 683 | return stack.err(ErrorKind::InvalidCall("command".to_owned())) 684 | }; 685 | let mut args = Vec::new(); 686 | for item in a.iter() { 687 | if let Value::Str(ref s) = item.lock_ro().native { 688 | args.push(s.to_owned()); 689 | } 690 | } 691 | if args.is_empty() { 692 | return stack.err(ErrorKind::InvalidCall("command".to_owned())); 693 | } 694 | process::Command::new(&args[0]) 695 | .args(&args[1..]) 696 | .stdin(Stdio::inherit()) 697 | .stdout(Stdio::inherit()) 698 | .stderr(Stdio::inherit()) 699 | .spawn() 700 | .map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?; 701 | Ok(()) 702 | } 703 | 704 | pub fn command_wait(stack: &mut Stack) -> OError { 705 | let binding = stack.pop(); 706 | let Value::Array(ref a) = binding.lock_ro().native else { 707 | return stack.err(ErrorKind::InvalidCall("command".to_owned())) 708 | }; 709 | let mut args = Vec::new(); 710 | for item in a.iter() { 711 | if let Value::Str(ref s) = item.lock_ro().native { 712 | args.push(s.to_owned()); 713 | } else { 714 | return stack.err(ErrorKind::InvalidCall("command".to_owned())); 715 | } 716 | } 717 | if args.is_empty() { 718 | return stack.err(ErrorKind::InvalidCall("command".to_owned())); 719 | } 720 | stack.push( 721 | Value::Int( 722 | process::Command::new(&args[0]) 723 | .args(&args[1..]) 724 | .stdin(Stdio::inherit()) 725 | .stdout(Stdio::inherit()) 726 | .stderr(Stdio::inherit()) 727 | .spawn() 728 | .map_err(|x| stack.error(ErrorKind::IO(x.to_string())))? 729 | .wait() 730 | .map_err(|x| stack.error(ErrorKind::IO(x.to_string())))? 731 | .code() 732 | .unwrap_or(-1), 733 | ) 734 | .spl(), 735 | ); 736 | Ok(()) 737 | } 738 | 739 | pub fn str_to_bytes(stack: &mut Stack) -> OError { 740 | require_on_stack!(s, Str, stack, "str-to-bytes"); 741 | stack.push( 742 | Value::Array( 743 | s.bytes() 744 | .into_iter() 745 | .map(|x| Value::Int(x as i32).spl()) 746 | .collect(), 747 | ) 748 | .spl(), 749 | ); 750 | Ok(()) 751 | } 752 | 753 | pub fn bytes_to_str(stack: &mut Stack) -> OError { 754 | require_array_on_stack!(a, stack, "str-to-bytes"); 755 | let mut chars = Vec::new(); 756 | for item in a.iter() { 757 | if let Value::Int(x) = item.lock_ro().native.clone().try_mega_to_int() { 758 | chars.push(x as u8); 759 | } else { 760 | return stack.err(ErrorKind::InvalidCall("command".to_owned())); 761 | } 762 | } 763 | stack.push(Value::Str(String::from_utf8_lossy(&chars[..]).into_owned()).spl()); 764 | Ok(()) 765 | } 766 | 767 | pub fn acopy(stack: &mut Stack) -> OError { 768 | require_on_stack!(len, Mega, stack, "acopy"); 769 | require_on_stack!(idx_dest, Mega, stack, "acopy"); 770 | require_on_stack!(idx_src, Mega, stack, "acopy"); 771 | let dest_array = stack.pop(); 772 | { 773 | require_mut_array!(dest, dest_array, stack, "acopy"); 774 | require_array_on_stack!(src, stack, "acopy"); 775 | let offset = idx_dest - idx_src; 776 | if (src.len() as i128) < idx_src + len 777 | || idx_src < 0 778 | || (dest.len() as i128) < idx_dest + len 779 | || idx_dest < 0 780 | { 781 | stack.err(ErrorKind::InvalidCall("acopy".to_owned()))?; 782 | } 783 | for i in idx_src..idx_src + len { 784 | *dest.get_mut((i + offset) as usize).unwrap() = src.get(i as usize).unwrap().clone(); 785 | } 786 | } 787 | stack.push(dest_array); 788 | Ok(()) 789 | } 790 | 791 | pub fn throw(stack: &mut Stack) -> OError { 792 | let obj = stack.pop(); 793 | if let Value::Str(ref s) = obj.lock_ro().native { 794 | if obj.lock_ro().kind.lock_ro().get_id() 795 | == get_type("str") 796 | .expect("str type must exist") 797 | .lock_ro() 798 | .get_id() 799 | { 800 | stack.err(ErrorKind::Custom(s.to_owned()))?; 801 | } 802 | } 803 | stack.err(ErrorKind::CustomObject(obj)) 804 | } 805 | 806 | pub fn write_sasm(stack: &mut Stack) -> OError { 807 | require_on_stack!(code, Str, stack, "write-sasm"); 808 | stack.push( 809 | Value::Str( 810 | lexer::lex(code) 811 | .map(|x| sasm_write(x)) 812 | .map_err(|x| stack.error(ErrorKind::LexError(format!("{x:?}"))))?, 813 | ) 814 | .spl(), 815 | ); 816 | Ok(()) 817 | } 818 | 819 | pub fn write_file_sasm(stack: &mut Stack) -> OError { 820 | require_on_stack!(file, Str, stack, "write-file-sasm"); 821 | stack.push( 822 | Value::Str( 823 | lexer::lex( 824 | fs::read_to_string(file).map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?, 825 | ) 826 | .map(|x| sasm_write(x)) 827 | .map_err(|x| stack.error(ErrorKind::LexError(format!("{x:?}"))))?, 828 | ) 829 | .spl(), 830 | ); 831 | Ok(()) 832 | } 833 | 834 | pub fn register(r: &mut Stack, o: Arc) { 835 | type Fn = fn(&mut Stack) -> OError; 836 | let fns: [(&str, Fn, u32); 53] = [ 837 | ("pop", pop, 0), 838 | ("dup", dup, 2), 839 | ("clone", clone, 1), 840 | ("swap", swap, 2), 841 | ("mswap", mswap, 2), 842 | ("print", print, 0), 843 | ("gettype", gettype, 1), 844 | ("settype", settype, 1), 845 | ("anew", array_new, 1), 846 | ("array-len", array_len, 1), 847 | ("array-get", array_get, 1), 848 | ("array-set", array_set, 1), 849 | ("eq", eq, 1), 850 | ("lt", lt, 1), 851 | ("gt", gt, 1), 852 | ("not", not, 1), 853 | ("and", and, 1), 854 | ("or", or, 1), 855 | ("+", plus, 1), 856 | ("-", minus, 1), 857 | ("/", slash, 1), 858 | ("*", star, 1), 859 | ("%", percent, 1), 860 | ("_int", to_int, 1), 861 | ("_long", to_long, 1), 862 | ("_mega", to_mega, 1), 863 | ("_float", to_float, 1), 864 | ("_double", to_double, 1), 865 | ("_array", to_array, 1), 866 | ("_str", to_str, 1), 867 | ("call", call, 0), 868 | ("callp", callp, 0), 869 | ("trace", trace, 1), 870 | ("mr-trace", mr_trace, 1), 871 | ("exit", exit, 0), 872 | ("exec", exec, 0), 873 | ("exec2", exec2, 0), 874 | ("exec3", exec3, 0), 875 | ("stop", stop, 0), 876 | ("argv", argv, 1), 877 | ("get-env", get_env, 1), 878 | ("read-file", read_file, 1), 879 | ("alit-end", alit_end, 1), 880 | ("import", import, 0), 881 | ("readln", readln, 1), 882 | ("command", command, 0), 883 | ("command-wait", command_wait, 1), 884 | ("str-to-bytes", str_to_bytes, 1), 885 | ("bytes-to-str", bytes_to_str, 1), 886 | ("acopy", acopy, 1), 887 | ("throw", throw, 0), 888 | ("write-sasm", write_sasm, 1), 889 | ("write-file-sasm", write_file_sasm, 1), 890 | ]; 891 | for f in fns { 892 | r.define_func( 893 | f.0.to_owned(), 894 | AFunc::new(Func { 895 | ret_count: f.2, 896 | to_call: FuncImpl::Native(f.1), 897 | run_as_base: false, 898 | origin: o.clone(), 899 | fname: None, 900 | name: f.0.to_owned(), 901 | }), 902 | ); 903 | } 904 | } 905 | -------------------------------------------------------------------------------- /src/runtime.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | dyn_fns, 3 | mutex::*, 4 | std_fns, stdlib, 5 | stream::{self, *}, 6 | }; 7 | 8 | use core::panic; 9 | use std::collections::VecDeque; 10 | use std::sync::{RwLockReadGuard, RwLockWriteGuard}; 11 | use std::{ 12 | cell::RefCell, 13 | collections::HashMap, 14 | fmt::{Debug, Display, Formatter}, 15 | sync::Arc, 16 | vec, 17 | }; 18 | use std::{env::var, mem, path::Path}; 19 | 20 | pub type AMObject = Arc>; 21 | pub type AMType = Arc>; 22 | pub type AFunc = Arc; 23 | pub type OError = Result<(), Error>; 24 | 25 | thread_local! { 26 | static RUNTIME: RefCell>>> = RefCell::new(None); 27 | } 28 | 29 | /// Obtains a reference to the runtime. 30 | pub fn runtime(f: impl FnOnce(RwLockReadGuard) -> T) -> T { 31 | RUNTIME.with(|rt| { 32 | f(rt.borrow_mut() 33 | .as_mut() 34 | .expect("no runtime (use .set())") 35 | .lock_ro()) 36 | }) 37 | } 38 | 39 | /// Obtains a mutable reference to the runtime. 40 | pub fn runtime_mut(f: impl FnOnce(RwLockWriteGuard) -> T) -> T { 41 | RUNTIME.with(|rt| { 42 | f(rt.borrow_mut() 43 | .as_mut() 44 | .expect("no runtime (use .set())") 45 | .lock()) 46 | }) 47 | } 48 | 49 | pub fn get_type(name: &str) -> Option { 50 | runtime(|rt| rt.get_type_by_name(name)) 51 | } 52 | 53 | /// An SPL runtime. 54 | /// 55 | /// This holds: 56 | /// - types 57 | /// - type refs 58 | /// - streams 59 | #[derive(Clone)] 60 | pub struct Runtime { 61 | next_type_id: u32, 62 | types_by_name: HashMap, 63 | types_by_id: HashMap, 64 | next_stream_id: u128, 65 | streams: HashMap>>, 66 | pub embedded_files: HashMap<&'static str, &'static str>, 67 | pub native_functions: HashMap<&'static str, (u32, FuncImpl)>, 68 | } 69 | 70 | impl Debug for Runtime { 71 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 72 | f.debug_struct("Runtime") 73 | .field("next_type_id", &self.next_type_id) 74 | .field("types_by_name", &self.types_by_name) 75 | .field("types_by_id", &self.types_by_id) 76 | .field("next_stream_id", &self.next_stream_id) 77 | .finish() 78 | } 79 | } 80 | 81 | impl Default for Runtime { 82 | fn default() -> Self { 83 | Self::new() 84 | } 85 | } 86 | 87 | impl Runtime { 88 | pub fn new() -> Self { 89 | let mut rt = Runtime { 90 | next_type_id: 0, 91 | types_by_name: HashMap::new(), 92 | types_by_id: HashMap::new(), 93 | next_stream_id: 0, 94 | streams: HashMap::new(), 95 | embedded_files: HashMap::new(), 96 | native_functions: HashMap::new(), 97 | }; 98 | let _ = rt.make_type("null".to_owned(), Ok); // infallible 99 | let _ = rt.make_type("int".to_owned(), Ok); // infallible 100 | let _ = rt.make_type("long".to_owned(), Ok); // infallible 101 | let _ = rt.make_type("mega".to_owned(), Ok); // infallible 102 | let _ = rt.make_type("float".to_owned(), Ok); // infallible 103 | let _ = rt.make_type("double".to_owned(), Ok); // infallible 104 | let _ = rt.make_type("func".to_owned(), Ok); // infallible 105 | let _ = rt.make_type("array".to_owned(), Ok); // infallible 106 | let _ = rt.make_type("str".to_owned(), Ok); // infallible 107 | stdlib::register(&mut rt); 108 | rt 109 | } 110 | 111 | pub fn get_type_by_name(&self, name: &str) -> Option { 112 | self.types_by_name.get(name).cloned() 113 | } 114 | 115 | pub fn get_type_by_id(&self, id: u32) -> Option { 116 | self.types_by_id.get(&id).cloned() 117 | } 118 | 119 | pub fn get_types(&self) -> Vec { 120 | self.types_by_id.clone().into_values().collect() 121 | } 122 | 123 | pub fn make_type( 124 | &mut self, 125 | name: String, 126 | op: impl FnOnce(Type) -> Result, 127 | ) -> Result { 128 | let t = Arc::new(Mut::new(op(Type { 129 | name: name.clone(), 130 | id: (self.next_type_id, self.next_type_id += 1).0, 131 | parents: Vec::new(), 132 | functions: HashMap::new(), 133 | properties: Vec::new(), 134 | })?)); 135 | self.types_by_id.insert(self.next_type_id - 1, t.clone()); 136 | self.types_by_name.insert(name, t.clone()); 137 | Ok(t) 138 | } 139 | 140 | pub fn register_stream(&mut self, stream: Stream) -> (u128, Arc>) { 141 | let id = (self.next_stream_id, self.next_stream_id += 1).0; 142 | self.streams.insert(id, Arc::new(Mut::new(stream))); 143 | (id, self.streams.get(&id).unwrap().clone()) 144 | } 145 | 146 | pub fn get_stream(&self, id: u128) -> Option>> { 147 | self.streams.get(&id).cloned() 148 | } 149 | 150 | pub fn destroy_stream(&mut self, id: u128) { 151 | self.streams.remove(&id); 152 | } 153 | 154 | pub fn load_native_function(&self, name: &str) -> &(u32, FuncImpl) { 155 | self.native_functions.get(name).unwrap_or_else(|| { 156 | panic!( 157 | "It seems the native function {name} was not compiled into this program. Stopping." 158 | ) 159 | }) 160 | } 161 | 162 | pub fn reset() { 163 | RUNTIME.with(|x| *x.borrow_mut() = None); 164 | } 165 | } 166 | 167 | /// Anything that can be .set() and result in the runtime being set. 168 | /// Implemented for Arc> and Runtime. 169 | pub trait SetRuntime { 170 | fn set(self); 171 | } 172 | 173 | impl SetRuntime for Runtime { 174 | fn set(self) { 175 | Arc::new(Mut::new(self)).set() 176 | } 177 | } 178 | 179 | impl SetRuntime for Arc> { 180 | fn set(self) { 181 | RUNTIME.with(move |x| *x.borrow_mut() = Some(self)); 182 | } 183 | } 184 | 185 | /// A frame's location in SPL code. 186 | #[derive(Clone, Debug, PartialEq, Eq)] 187 | pub struct FrameInfo { 188 | pub file: String, 189 | pub function: String, 190 | } 191 | 192 | /// An SPL stack frame. 193 | /// 194 | /// This holds: 195 | /// - its parent 196 | /// - variables 197 | /// - functions 198 | /// - its origin ([FrameInfo]) 199 | /// - whether all functions in it should be made global. 200 | #[derive(Clone, Debug)] 201 | pub struct Frame { 202 | parent: Option>, 203 | pub variables: Mut>, 204 | pub functions: Mut>, 205 | pub origin: FrameInfo, 206 | pub redirect_to_base: bool, 207 | } 208 | 209 | impl Display for Frame { 210 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 211 | f.write_str("\nVars: \n")?; 212 | for (name, object) in self.variables.lock_ro().iter() { 213 | f.write_str(" ")?; 214 | f.write_str(name)?; 215 | f.write_str(": ")?; 216 | std::fmt::Display::fmt(&object.lock_ro(), f)?; 217 | f.write_str("\n")?; 218 | } 219 | f.write_str("\nFuncs: \n")?; 220 | for (name, ..) in self.functions.lock_ro().iter() { 221 | f.write_str(" ")?; 222 | f.write_str(name)?; 223 | f.write_str("\n")?; 224 | } 225 | f.write_str("\n\n")?; 226 | Ok(()) 227 | } 228 | } 229 | 230 | impl Frame { 231 | pub fn dummy() -> Self { 232 | Frame { 233 | parent: None, 234 | variables: Mut::new(HashMap::new()), 235 | functions: Mut::new(HashMap::new()), 236 | origin: FrameInfo { 237 | file: "\0".to_owned(), 238 | function: "\0".to_owned(), 239 | }, 240 | redirect_to_base: false, 241 | } 242 | } 243 | 244 | pub fn root() -> Self { 245 | Frame { 246 | parent: None, 247 | variables: Mut::new(HashMap::new()), 248 | functions: Mut::new(HashMap::new()), 249 | origin: FrameInfo { 250 | file: "std.spl".to_owned(), 251 | function: "root".to_owned(), 252 | }, 253 | redirect_to_base: false, 254 | } 255 | } 256 | 257 | pub fn root_in(info: FrameInfo) -> Self { 258 | Frame { 259 | parent: None, 260 | variables: Mut::new(HashMap::new()), 261 | functions: Mut::new(HashMap::new()), 262 | origin: info, 263 | redirect_to_base: false, 264 | } 265 | } 266 | 267 | pub fn new(parent: Arc, function: String) -> Self { 268 | Frame { 269 | variables: Mut::new(HashMap::new()), 270 | functions: Mut::new(HashMap::new()), 271 | origin: FrameInfo { 272 | function, 273 | ..parent.origin.clone() 274 | }, 275 | parent: Some(parent), 276 | redirect_to_base: false, 277 | } 278 | } 279 | 280 | pub fn new_in( 281 | parent: Arc, 282 | origin: String, 283 | function: String, 284 | redirect_to_parent: bool, 285 | ) -> Self { 286 | Frame { 287 | parent: Some(parent), 288 | variables: Mut::new(HashMap::new()), 289 | functions: Mut::new(HashMap::new()), 290 | origin: FrameInfo { 291 | file: origin, 292 | function, 293 | }, 294 | redirect_to_base: redirect_to_parent, 295 | } 296 | } 297 | 298 | pub fn set_var(&self, name: String, obj: AMObject, stack: &Stack) -> OError { 299 | let mut frame = self; 300 | loop { 301 | if let Some(x) = frame.variables.lock().get_mut(&name) { 302 | *x = obj; 303 | return Ok(()); 304 | } 305 | if let Some(ref x) = frame.parent { 306 | frame = x; 307 | } else { 308 | return Err(stack.error(ErrorKind::VariableNotFound(name))); 309 | } 310 | } 311 | } 312 | 313 | pub fn get_var(&self, name: String, stack: &Stack) -> Result { 314 | let mut frame = self; 315 | loop { 316 | if let Some(x) = frame.variables.lock_ro().get(&name) { 317 | return Ok(x.clone()); 318 | } 319 | if let Some(ref x) = frame.parent { 320 | frame = x; 321 | } else { 322 | return Err(stack.error(ErrorKind::VariableNotFound(name))); 323 | } 324 | } 325 | } 326 | 327 | pub fn path(&self) -> Vec { 328 | let mut r = Vec::new(); 329 | let mut frame = self; 330 | loop { 331 | r.insert(0, frame.origin.clone()); 332 | 333 | if let Some(ref parent) = frame.parent { 334 | frame = parent; 335 | } else { 336 | break; 337 | } 338 | } 339 | r 340 | } 341 | 342 | pub fn readable_path(&self) -> String { 343 | let mut item = String::new(); 344 | let path = self.path(); 345 | let mut file = "\0".to_owned(); 346 | for element in path { 347 | if element.file != file { 348 | item += " | in "; 349 | item += &element.file; 350 | item += ":"; 351 | file = element.file; 352 | } 353 | item += " "; 354 | item += &element.function; 355 | } 356 | item 357 | } 358 | 359 | pub fn is_dummy(&self) -> bool { 360 | self.parent.is_none() && self.origin.file == "\0" && self.origin.function == "\0" 361 | } 362 | } 363 | 364 | /// An SPL stack. 365 | /// 366 | /// This holds: 367 | /// - a stack of frames 368 | /// - the main stack of objects 369 | /// - a return accumultor: how many blocks to return directly from 370 | #[derive(Clone, Debug)] 371 | pub struct Stack { 372 | frames: Vec>, 373 | object_stack: Vec, 374 | objcall_stack: Vec, 375 | files: Vec, 376 | pub return_accumultor: u32, 377 | } 378 | 379 | impl Display for Stack { 380 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 381 | for frame in &self.frames { 382 | f.write_str("Frame:")?; 383 | f.write_str(&frame.readable_path())?; 384 | f.write_str("\n")?; 385 | std::fmt::Display::fmt(&frame.as_ref(), f)?; 386 | f.write_str("\n\n")?; 387 | } 388 | f.write_str("Stack: \n")?; 389 | for object in &self.object_stack { 390 | f.write_str(" ")?; 391 | std::fmt::Display::fmt(&object.lock_ro(), f)?; 392 | f.write_str("\n")?; 393 | } 394 | Ok(()) 395 | } 396 | } 397 | 398 | impl Default for Stack { 399 | fn default() -> Self { 400 | Self::new() 401 | } 402 | } 403 | 404 | impl Stack { 405 | pub fn new() -> Self { 406 | let o = Arc::new(Frame::root()); 407 | let mut r = Stack { 408 | frames: vec![o.clone()], 409 | object_stack: Vec::new(), 410 | objcall_stack: Vec::new(), 411 | files: Vec::new(), 412 | return_accumultor: 0, 413 | }; 414 | 415 | dyn_fns::register(&mut r, o.clone()); 416 | std_fns::register(&mut r, o.clone()); 417 | stream::register(&mut r, o); 418 | 419 | r 420 | } 421 | 422 | pub fn new_in(frame_info: FrameInfo) -> Self { 423 | let o = Arc::new(Frame::root_in(frame_info)); 424 | let mut r = Stack { 425 | frames: vec![o.clone()], 426 | object_stack: Vec::new(), 427 | objcall_stack: Vec::new(), 428 | files: Vec::new(), 429 | return_accumultor: 0, 430 | }; 431 | 432 | dyn_fns::register(&mut r, o.clone()); 433 | std_fns::register(&mut r, o.clone()); 434 | stream::register(&mut r, o); 435 | 436 | r 437 | } 438 | 439 | pub fn define_func(&mut self, name: String, func: AFunc) { 440 | let mut frame = self.frames.last().unwrap().clone(); 441 | if frame.redirect_to_base { 442 | frame = self.frames.first().unwrap().clone(); 443 | } 444 | frame.functions.lock().insert(name, func); 445 | } 446 | 447 | pub fn call(&mut self, func: &AFunc) -> OError { 448 | let f = if let Some(ref cname) = func.fname { 449 | Frame::new_in( 450 | func.origin.clone(), 451 | cname.clone(), 452 | func.name.clone(), 453 | func.run_as_base, 454 | ) 455 | } else { 456 | Frame::new(func.origin.clone(), func.name.clone()) 457 | }; 458 | self.frames.push(Arc::new(f)); 459 | let r = func.to_call.call(self); 460 | self.frames.pop().unwrap(); 461 | r 462 | } 463 | 464 | pub fn get_func(&self, name: String) -> Result { 465 | let mut frame = self.frames.last().unwrap(); 466 | loop { 467 | let functions = &frame.functions; 468 | if let Some(x) = functions.lock_ro().get(&name) { 469 | return Ok(x.clone()); 470 | } 471 | if let Some(ref x) = frame.parent { 472 | frame = x; 473 | } else { 474 | return Err(self.error(ErrorKind::FuncNotFound(name))); 475 | } 476 | } 477 | } 478 | 479 | pub fn define_var(&mut self, name: String) { 480 | let mut frame = self.frames.last().unwrap().clone(); 481 | if frame.redirect_to_base { 482 | frame = self.frames.first().unwrap().clone(); 483 | } 484 | let tmpname = name.clone(); 485 | let tmpframe = frame.clone(); 486 | frame.functions.lock().insert( 487 | name.clone(), 488 | Arc::new(Func { 489 | ret_count: 1, 490 | origin: frame.clone(), 491 | to_call: FuncImpl::NativeDyn(Arc::new(Box::new(move |stack| { 492 | stack.push(tmpframe.get_var(tmpname.clone(), stack)?); 493 | Ok(()) 494 | }))), 495 | run_as_base: false, 496 | fname: Some("RUNTIME".to_owned()), 497 | name: name.clone(), 498 | }), 499 | ); 500 | let tmpname = name.clone(); 501 | let tmpframe = frame.clone(); 502 | frame.functions.lock().insert( 503 | "=".to_owned() + &name, 504 | Arc::new(Func { 505 | ret_count: 0, 506 | origin: frame.clone(), 507 | to_call: FuncImpl::NativeDyn(Arc::new(Box::new(move |stack| { 508 | let v = stack.pop(); 509 | tmpframe.set_var(tmpname.clone(), v, stack) 510 | }))), 511 | run_as_base: false, 512 | fname: Some("RUNTIME".to_owned()), 513 | name: "=".to_owned() + &name, 514 | }), 515 | ); 516 | frame.variables.lock().insert(name, Value::Null.spl()); 517 | } 518 | 519 | pub fn pop_until(&mut self, obj: AMObject) -> Vec { 520 | let Some((idx, ..)) = self.object_stack.iter().enumerate().rfind(|o| *o.1.lock_ro() == *obj.lock_ro()) else { 521 | return Vec::new() 522 | }; 523 | let items = self.object_stack[idx + 1..].to_vec(); 524 | self.object_stack = self.object_stack[0..idx].to_vec(); 525 | items 526 | } 527 | 528 | pub fn len(&self) -> usize { 529 | self.object_stack.len() 530 | } 531 | 532 | pub fn set_var(&self, name: String, obj: AMObject) -> OError { 533 | self.get_frame().set_var(name, obj, self) 534 | } 535 | 536 | pub fn get_var(&self, name: String) -> Result { 537 | self.get_frame().get_var(name, self) 538 | } 539 | 540 | pub fn push(&mut self, obj: AMObject) { 541 | self.object_stack.push(obj) 542 | } 543 | 544 | pub fn peek(&mut self) -> AMObject { 545 | self.object_stack 546 | .last() 547 | .cloned() 548 | .unwrap_or(Value::Null.spl()) 549 | } 550 | 551 | pub fn pop(&mut self) -> AMObject { 552 | self.object_stack.pop().unwrap_or(Value::Null.spl()) 553 | } 554 | 555 | pub fn get_origin(&self) -> FrameInfo { 556 | self.frames.last().unwrap().origin.clone() 557 | } 558 | 559 | pub fn get_frame(&self) -> Arc { 560 | self.frames.last().unwrap().clone() 561 | } 562 | 563 | pub fn err(&self, kind: ErrorKind) -> Result { 564 | Err(Error { 565 | kind, 566 | stack: self.trace(), 567 | mr_stack: self.mr_trace(), 568 | }) 569 | } 570 | 571 | pub fn error(&self, kind: ErrorKind) -> Error { 572 | Error { 573 | kind, 574 | stack: self.trace(), 575 | mr_stack: self.mr_trace(), 576 | } 577 | } 578 | 579 | pub fn mr_trace(&self) -> Vec> { 580 | self.frames.iter().map(|frame| frame.path()).collect() 581 | } 582 | 583 | pub fn trace(&self) -> Vec { 584 | self.frames 585 | .iter() 586 | .map(|frame| frame.readable_path()) 587 | .collect() 588 | } 589 | 590 | pub fn peek_frame(&self, index: usize) -> Arc { 591 | self.frames 592 | .get(self.frames.len() - index - 1) 593 | .unwrap() 594 | .clone() 595 | } 596 | 597 | /// # Safety 598 | /// This function is not technically unsafe. It is marked this way to indicate that it 599 | /// can cause unstable runtime states and panics. However, memory safety is still guaranteed. 600 | pub unsafe fn pop_frame(&mut self, index: usize) -> Arc { 601 | self.frames.remove(self.frames.len() - index - 1) 602 | } 603 | 604 | /// # Safety 605 | /// This function is not technically unsafe. It is marked this way to indicate that it 606 | /// can cause unstable runtime states and panics. However, memory safety is still guaranteed. 607 | pub unsafe fn push_frame(&mut self, frame: Arc) { 608 | self.frames.push(frame); 609 | } 610 | 611 | pub(crate) fn include_file(&mut self, s: &String) -> bool { 612 | if self.files.contains(s) { 613 | false 614 | } else { 615 | self.files.push(s.to_owned()); 616 | true 617 | } 618 | } 619 | } 620 | 621 | #[derive(Clone, Debug)] 622 | pub enum FuncImplType { 623 | Rust, 624 | } 625 | 626 | /// An SPL keyword. Used to deviate from normal linear code structure. 627 | /// 628 | /// This is different from a [Word], which are any SPL code. 629 | #[derive(Clone, Debug)] 630 | pub enum Keyword { 631 | /// 632 | /// 633 | /// Dumps stack. Not available as actual keyword, therefore only obtainable through AST 634 | /// manipulation, a dyn call, or modding. 635 | /// equivalent to dyn-__dump 636 | /// example: func main { int | "Hello, world!" dyn-__dump pop 0 } 637 | Dump, 638 | /// def 639 | /// 640 | /// Defines a variable. 641 | /// equivalent to "" dyn-def 642 | Def(String), 643 | /// func { | } 644 | /// 645 | /// Defines function with return size 646 | /// equivalent to { | } "" dyn-func 647 | Func(String, u32, Words), 648 | /// construct { <...> ; { | } <...> } 649 | /// 650 | /// Creates type 651 | /// equivalent to 652 | /// "" dyn-construct; "" "" dyn-def-field { | } "" 653 | /// "" dyn-def-method 654 | Construct(String, Vec, Vec<(String, (u32, Words))>, bool), 655 | /// include in 656 | /// 657 | /// Adds as a parent type of . 658 | /// equivalent to "" "" dyn-include 659 | Include(String, String), 660 | /// use : 661 | /// 662 | /// equivalent to ":" dyn-use 663 | Use(String), 664 | /// while { } { } 665 | /// 666 | /// If wordsA result in a truthy value being on the top of the stack, execute wordsB, and 667 | /// repeat. 668 | /// equivalent to { int | } { | } dyn-while 669 | While(Words, Words), 670 | /// if { } 671 | /// 672 | /// If wordsA result in a truthy value being on the top of the stack, execute wordsB. 673 | /// equivalent to { | } dyn-if 674 | If(Words), 675 | /// with <...> ; 676 | /// 677 | /// Defines variables in reverse order. 678 | /// equivalent to def <...> =<...> def = 679 | /// or "<...>" dyn-def "=<...>" dyn-call "" dyn-def "=" dyn-call 680 | With(Vec), 681 | /// catch [ <...>] { } with { } 682 | /// 683 | /// Catches errors that happen within , running when an error is 684 | /// encountered and the error is of (or, if no type is specified, any error). 685 | /// equivalent to \[ ["" <...>] \] { | } { | } dyn-catch 686 | Catch(Vec, Words, Words), 687 | /// 688 | /// 689 | /// Used by `object:method <{ arg1 arg2 }` syntax. Generates as: 690 | /// - call object 691 | /// - objpush 692 | /// - call arg1 693 | /// - call arg2 694 | /// - objpop 695 | /// - objcall method 696 | ObjPush, 697 | /// 698 | /// 699 | /// see [Keyword::ObjPush] 700 | ObjPop, 701 | /// func @ !{ } 702 | /// 703 | /// Defines function with impl type 704 | /// equivalent to "" "" "" dyn-func-of 705 | FuncOf(String, String, FuncImplType), 706 | } 707 | 708 | /// Any SPL value that is not a construct. 709 | /// 710 | /// Holds its rust representation. 711 | #[derive(Clone, Debug, PartialEq)] 712 | pub enum Value { 713 | Null, 714 | Int(i32), 715 | Long(i64), 716 | Mega(i128), 717 | Float(f32), 718 | Double(f64), 719 | Func(AFunc), 720 | Array(Vec), 721 | Str(String), 722 | } 723 | 724 | impl Value { 725 | fn ensure_init(self, stack: &Stack) -> Self { 726 | match self { 727 | Value::Func(x) if x.origin.is_dummy() => Value::Func(AFunc::new(Func { 728 | origin: stack.get_frame(), 729 | ..x.as_ref().clone() 730 | })), 731 | x => x, 732 | } 733 | } 734 | 735 | pub fn try_mega_to_int(self) -> Value { 736 | if let Value::Mega(x) = self { 737 | Value::Int(x as i32) 738 | } else { 739 | self 740 | } 741 | } 742 | } 743 | 744 | impl PartialOrd for Value { 745 | fn partial_cmp(&self, other: &Self) -> Option { 746 | match (self, other) { 747 | (Value::Mega(a), Value::Mega(b)) => a.partial_cmp(b), 748 | _ => panic!(), 749 | } 750 | } 751 | } 752 | 753 | /// The smallest fragment of SPL code. 754 | #[derive(Clone, Debug)] 755 | pub enum Word { 756 | /// A keyword, used to deviate from normal code structure. 757 | Key(Keyword), 758 | /// A constant to push to the stack when encountered. 759 | Const(Value), 760 | /// A function call. 761 | Call(String, bool, u32), 762 | /// A method call. 763 | ObjCall(String, bool, u32), 764 | } 765 | 766 | /// A collection of executable words. 767 | #[derive(Clone, Debug)] 768 | pub struct Words { 769 | pub words: Vec, 770 | } 771 | 772 | impl Words { 773 | pub fn new(words: Vec) -> Self { 774 | Words { words } 775 | } 776 | } 777 | 778 | /// Any kind of SPL-executable code. 779 | #[derive(Clone)] 780 | pub enum FuncImpl { 781 | Native(fn(&mut Stack) -> OError), 782 | NativeDyn(Arc OError>>), 783 | SPL(Words), 784 | } 785 | 786 | impl FuncImpl { 787 | pub fn call(&self, stack: &mut Stack) -> OError { 788 | match self { 789 | FuncImpl::Native(x) => x(stack), 790 | FuncImpl::NativeDyn(x) => x(stack), 791 | FuncImpl::SPL(x) => x.exec(stack), 792 | } 793 | } 794 | } 795 | 796 | /// Any kind of SPL-executable code with metadata surrounding it. 797 | /// 798 | /// This holds: 799 | /// - the amount of values returned when called 800 | /// - the actual executable code ([FuncImpl]) 801 | /// - the frame that defined it 802 | /// - the name of the file it was defined in, if this is different form the definition frame 803 | /// - the name of the function. 804 | /// - wether it should be run as the root layer (= wether functions it defines should be made 805 | /// global) 806 | #[derive(Clone)] 807 | pub struct Func { 808 | pub ret_count: u32, 809 | pub to_call: FuncImpl, 810 | pub origin: Arc, 811 | pub fname: Option, 812 | pub name: String, 813 | pub run_as_base: bool, 814 | } 815 | 816 | impl PartialEq for Func { 817 | fn eq(&self, _other: &Self) -> bool { 818 | false 819 | } 820 | } 821 | 822 | impl Debug for Func { 823 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 824 | f.write_str(&self.ret_count.to_string())?; 825 | Ok(()) 826 | } 827 | } 828 | 829 | /// Any SPL type. 830 | /// 831 | /// This holds: 832 | /// - the name 833 | /// - the numeric ID 834 | /// - its parent types 835 | /// - its methods 836 | /// - its fields 837 | #[derive(Clone, Debug)] 838 | pub struct Type { 839 | name: String, 840 | id: u32, 841 | pub parents: Vec, 842 | pub functions: HashMap, 843 | pub properties: Vec, 844 | } 845 | 846 | impl PartialEq for Type { 847 | fn eq(&self, other: &Self) -> bool { 848 | self.id == other.id 849 | } 850 | } 851 | 852 | impl Type { 853 | pub fn get_name(&self) -> String { 854 | self.name.clone() 855 | } 856 | 857 | pub fn get_id(&self) -> u32 { 858 | self.id 859 | } 860 | 861 | pub fn get_fn(&self, name: String) -> Option { 862 | if let Some(x) = self.functions.get(&name) { 863 | return Some(x.clone()); 864 | } 865 | let mut q = VecDeque::from(self.parents.clone()); 866 | while let Some(t) = q.pop_front() { 867 | if let Some(x) = t.lock_ro().functions.get(&name) { 868 | return Some(x.clone()); 869 | } 870 | q.append(&mut VecDeque::from(t.lock_ro().parents.clone())); 871 | } 872 | None 873 | } 874 | 875 | pub fn write_into(&self, object: &mut Object) { 876 | let mut to_apply = self.properties.clone(); 877 | let mut q = VecDeque::from(self.parents.clone()); 878 | while let Some(t) = q.pop_front() { 879 | to_apply.append(&mut t.lock_ro().properties.clone()); 880 | q.append(&mut VecDeque::from(t.lock_ro().parents.clone())); 881 | } 882 | for property in to_apply.into_iter().rev() { 883 | object 884 | .property_map 885 | .entry(property) 886 | .or_insert_with(|| Value::Null.spl()); 887 | } 888 | } 889 | 890 | pub fn add_property(&mut self, name: String, origin: Arc) -> OError { 891 | let tmpname = name.clone(); 892 | self.functions.insert( 893 | name.clone(), 894 | Arc::new(Func { 895 | ret_count: 1, 896 | to_call: FuncImpl::NativeDyn(Arc::new(Box::new(move |stack| { 897 | let o = stack.pop(); 898 | let o = o.lock_ro(); 899 | stack.push( 900 | o.property_map 901 | .get(&tmpname) 902 | .ok_or_else(|| { 903 | stack.error(ErrorKind::PropertyNotFound( 904 | o.kind.lock_ro().name.clone(), 905 | tmpname.clone(), 906 | )) 907 | })? 908 | .clone(), 909 | ); 910 | Ok(()) 911 | }))), 912 | origin: origin.clone(), 913 | run_as_base: false, 914 | fname: Some("RUNTIME".to_owned()), 915 | name: name.clone(), 916 | }), 917 | ); 918 | let tmpname = name.clone(); 919 | self.functions.insert( 920 | "=".to_owned() + &name, 921 | Arc::new(Func { 922 | ret_count: 0, 923 | to_call: FuncImpl::NativeDyn(Arc::new(Box::new(move |stack| { 924 | let o = stack.pop(); 925 | let v = stack.pop(); 926 | o.lock().property_map.insert(tmpname.clone(), v); 927 | Ok(()) 928 | }))), 929 | origin, 930 | run_as_base: false, 931 | fname: Some("RUNTIME".to_owned()), 932 | name: "=".to_owned() + &name, 933 | }), 934 | ); 935 | self.properties.push(name); 936 | Ok(()) 937 | } 938 | } 939 | 940 | /// Any kind of SPL object, no matter if it is a construct or not. 941 | /// 942 | /// This holds: 943 | /// - the type of the object 944 | /// - the fields mandated by the type 945 | /// - the native value ([Value]), null for constructs unless set manually. 946 | #[derive(Clone, Debug)] 947 | pub struct Object { 948 | pub kind: AMType, 949 | pub property_map: HashMap, 950 | pub native: Value, 951 | } 952 | 953 | impl PartialEq for Object { 954 | fn eq(&self, other: &Self) -> bool { 955 | self.kind == other.kind 956 | && self.property_map == other.property_map 957 | && self.native == other.native 958 | } 959 | } 960 | 961 | impl PartialOrd for Object { 962 | fn partial_cmp(&self, other: &Self) -> Option { 963 | if self.kind != other.kind { 964 | return None; 965 | } 966 | self.native.partial_cmp(&other.native) 967 | } 968 | } 969 | 970 | impl Eq for Object {} 971 | 972 | impl Display for Object { 973 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 974 | f.write_str(&self.kind.lock_ro().name)?; 975 | f.write_str("(")?; 976 | self.native.fmt(f)?; 977 | f.write_str(") {")?; 978 | for (k, v) in &self.property_map { 979 | f.write_str(" ")?; 980 | f.write_str(k)?; 981 | f.write_str(": ")?; 982 | std::fmt::Display::fmt(&v.lock_ro(), f)?; 983 | f.write_str(",")?; 984 | } 985 | f.write_str(" }")?; 986 | Ok(()) 987 | } 988 | } 989 | 990 | impl Object { 991 | pub fn new(kind: AMType, native: Value) -> Object { 992 | let mut r = Object { 993 | property_map: HashMap::new(), 994 | kind: kind.clone(), 995 | native, 996 | }; 997 | kind.lock_ro().write_into(&mut r); 998 | r 999 | } 1000 | 1001 | pub fn is_truthy(&self) -> bool { 1002 | match &self.native { 1003 | Value::Null => self.kind.lock_ro().id != 0, 1004 | Value::Int(x) => x > &0, 1005 | Value::Long(x) => x > &0, 1006 | Value::Mega(x) => x > &0, 1007 | Value::Float(_) => true, 1008 | Value::Double(_) => true, 1009 | Value::Func(_) => true, 1010 | Value::Array(_) => true, 1011 | Value::Str(x) => !x.is_empty(), 1012 | } 1013 | } 1014 | 1015 | pub fn field(&self, name: &str, stack: &mut Stack) -> Result { 1016 | Ok(self 1017 | .property_map 1018 | .get(name) 1019 | .ok_or_else(|| { 1020 | stack.error(ErrorKind::PropertyNotFound( 1021 | self.kind.lock_ro().name.to_owned(), 1022 | name.to_owned(), 1023 | )) 1024 | })? 1025 | .clone()) 1026 | } 1027 | } 1028 | 1029 | impl From for Object { 1030 | fn from(value: String) -> Self { 1031 | Value::Str(value).into() 1032 | } 1033 | } 1034 | 1035 | impl From for Object { 1036 | fn from(value: FrameInfo) -> Self { 1037 | let mut obj = Object::new( 1038 | get_type("FrameInfo").expect("FrameInfo type must exist"), 1039 | Value::Null, 1040 | ); 1041 | obj.property_map.insert("file".to_owned(), value.file.spl()); 1042 | obj.property_map 1043 | .insert("function".to_owned(), value.function.spl()); 1044 | obj 1045 | } 1046 | } 1047 | 1048 | impl From> for Object 1049 | where 1050 | T: Into, 1051 | { 1052 | fn from(value: Vec) -> Self { 1053 | Value::Array(value.into_iter().map(|x| x.spl()).collect()).into() 1054 | } 1055 | } 1056 | 1057 | impl From for Object { 1058 | fn from(value: Value) -> Self { 1059 | Object::new( 1060 | runtime(|x| { 1061 | match value { 1062 | Value::Null => x.get_type_by_id(0), 1063 | Value::Int(_) => x.get_type_by_id(1), 1064 | Value::Long(_) => x.get_type_by_id(2), 1065 | Value::Mega(_) => x.get_type_by_id(3), 1066 | Value::Float(_) => x.get_type_by_id(4), 1067 | Value::Double(_) => x.get_type_by_id(5), 1068 | Value::Func(_) => x.get_type_by_id(6), 1069 | Value::Array(_) => x.get_type_by_id(7), 1070 | Value::Str(_) => x.get_type_by_id(8), 1071 | } 1072 | .expect("runtime uninitialized: default types not set.") 1073 | }), 1074 | value, 1075 | ) 1076 | } 1077 | } 1078 | 1079 | /// Trait for converting things to SPL Objects. 1080 | pub trait SPL { 1081 | fn spl(self) -> AMObject; 1082 | } 1083 | 1084 | impl SPL for T 1085 | where 1086 | T: Into, 1087 | { 1088 | fn spl(self) -> AMObject { 1089 | Arc::new(Mut::new(self.into())) 1090 | } 1091 | } 1092 | 1093 | macro_rules! impl_to_object { 1094 | ($kind:ident, $type:ty) => { 1095 | impl From<$type> for Object { 1096 | fn from(value: $type) -> Object { 1097 | Value::$kind(value).into() 1098 | } 1099 | } 1100 | }; 1101 | } 1102 | 1103 | impl_to_object!(Int, i32); 1104 | impl_to_object!(Long, i64); 1105 | impl_to_object!(Mega, i128); 1106 | impl_to_object!(Float, f32); 1107 | impl_to_object!(Double, f64); 1108 | 1109 | /// Finds a file in the SPL_PATH, or returns the internal [stdlib] version of it. 1110 | pub fn find_in_splpath(path: &str) -> Result { 1111 | if Path::new(path).exists() { 1112 | return Ok(path.to_owned()); 1113 | } 1114 | let s = var("SPL_PATH").unwrap_or("/usr/lib/spl".to_owned()) + "/" + path; 1115 | if Path::new(&s).exists() { 1116 | Ok(s) 1117 | } else { 1118 | runtime(|x| { 1119 | for (&p, &data) in &x.embedded_files { 1120 | if path == p { 1121 | return Err(data.to_owned()); 1122 | } 1123 | } 1124 | Ok(path.to_owned()) // fails later 1125 | }) 1126 | } 1127 | } 1128 | 1129 | impl Words { 1130 | /// Executes the words. This does *not* create a new frame on the stack. Use [Stack::call] to 1131 | /// call and create a new frame. 1132 | pub fn exec(&self, stack: &mut Stack) -> OError { 1133 | for word in self.words.clone() { 1134 | match word { 1135 | Word::Key(x) => match x { 1136 | Keyword::Dump => println!("{stack}"), 1137 | Keyword::Def(x) => stack.define_var(x), 1138 | Keyword::Func(name, rem, words) => stack.define_func( 1139 | name.clone(), 1140 | Arc::new(Func { 1141 | ret_count: rem, 1142 | to_call: FuncImpl::SPL(words), 1143 | origin: stack.get_frame(), 1144 | run_as_base: false, 1145 | fname: None, 1146 | name, 1147 | }), 1148 | ), 1149 | Keyword::Construct(name, fields, methods, is_namespace) => { 1150 | let origin = stack.get_frame(); 1151 | if !name.contains(':') { 1152 | stack.define_var(name.clone()); 1153 | } 1154 | let t = runtime_mut(|mut rt| { 1155 | rt.make_type(name.clone(), |mut t| { 1156 | for field in fields { 1157 | t.add_property(field, origin.clone())?; 1158 | } 1159 | t.functions.extend(methods.into_iter().map(|(k, v)| { 1160 | ( 1161 | k.clone(), 1162 | Arc::new(Func { 1163 | ret_count: v.0, 1164 | to_call: FuncImpl::SPL(v.1), 1165 | origin: origin.clone(), 1166 | run_as_base: false, 1167 | fname: None, 1168 | name: name.clone() + ":" + &k, 1169 | }), 1170 | ) 1171 | })); 1172 | Ok(t) 1173 | }) 1174 | })?; 1175 | 1176 | let to_set: Object = if is_namespace { 1177 | let mut obj: Object = Value::Null.into(); 1178 | obj.kind = t.clone(); 1179 | t.lock_ro().write_into(&mut obj); 1180 | obj 1181 | } else { 1182 | Value::Str(t.lock_ro().get_name()).into() 1183 | }; 1184 | if name.contains(':') { 1185 | let Some((a, mut name)) = name.split_once(':') else { unreachable!() }; 1186 | let mut f = stack.get_var(a.to_owned())?; 1187 | while let Some((a, b)) = name.split_once(':') { 1188 | name = b; 1189 | let o = f.lock_ro(); 1190 | let nf = o.field(a, stack)?; 1191 | mem::drop(o); 1192 | f = nf; 1193 | } 1194 | *f.lock_ro().field(name, stack)?.lock() = to_set; 1195 | } else { 1196 | stack.set_var(name.clone(), to_set.spl())?; 1197 | } 1198 | } 1199 | Keyword::Include(ta, tb) => { 1200 | let rstack = &stack; 1201 | runtime(move |rt| { 1202 | rt.get_type_by_name(&tb) 1203 | .ok_or_else(|| rstack.error(ErrorKind::TypeNotFound(tb)))? 1204 | .lock() 1205 | .parents 1206 | .push( 1207 | rt.get_type_by_name(&ta) 1208 | .ok_or_else(|| rstack.error(ErrorKind::TypeNotFound(ta)))?, 1209 | ); 1210 | Ok(()) 1211 | })?; 1212 | } 1213 | Keyword::Use(item) => { 1214 | if let Some((a, mut name)) = item.split_once(':') { 1215 | let mut f = stack.get_var(a.to_owned())?; 1216 | while let Some((a, b)) = name.split_once(':') { 1217 | name = b; 1218 | let o = f.lock_ro(); 1219 | let nf = o.field(a, stack)?; 1220 | mem::drop(o); 1221 | f = nf; 1222 | } 1223 | stack.define_var(name.to_owned()); 1224 | let o = f.lock_ro().field(name, stack)?.clone(); 1225 | stack.set_var(name.to_owned(), o)?; 1226 | } 1227 | } 1228 | Keyword::While(cond, blk) => loop { 1229 | cond.exec(stack)?; 1230 | if !stack.pop().lock_ro().is_truthy() { 1231 | break; 1232 | } 1233 | blk.exec(stack)?; 1234 | if stack.return_accumultor > 0 { 1235 | stack.return_accumultor -= 1; 1236 | break; 1237 | } 1238 | }, 1239 | Keyword::If(blk) => { 1240 | if stack.pop().lock_ro().is_truthy() { 1241 | blk.exec(stack)?; 1242 | } 1243 | } 1244 | Keyword::Catch(types, blk, ctch) => { 1245 | if let Err(e) = blk.exec(stack) { 1246 | if types.is_empty() || types.contains(&e.kind.to_string()) { 1247 | stack.push(e.spl()); 1248 | ctch.exec(stack)?; 1249 | } else { 1250 | return Err(e); 1251 | } 1252 | } 1253 | } 1254 | Keyword::With(vars) => { 1255 | for var in vars.into_iter().rev() { 1256 | stack.define_var(var.clone()); 1257 | let obj = stack.pop(); 1258 | stack.set_var(var, obj)?; 1259 | } 1260 | } 1261 | Keyword::ObjPush => { 1262 | let o = stack.pop(); 1263 | stack.objcall_stack.push(o); 1264 | } 1265 | Keyword::ObjPop => { 1266 | let o = stack 1267 | .objcall_stack 1268 | .pop() 1269 | .expect("invalid word generation. objpop without objpush!"); 1270 | stack.push(o); 1271 | } 1272 | Keyword::FuncOf(name, _, _) => runtime(|x| { 1273 | let f = x.load_native_function(&name); 1274 | stack.define_func( 1275 | name.to_owned(), 1276 | Arc::new(Func { 1277 | ret_count: f.0, 1278 | to_call: f.1.clone(), 1279 | origin: stack.get_frame(), 1280 | run_as_base: false, 1281 | fname: None, 1282 | name, 1283 | }), 1284 | ) 1285 | }), 1286 | }, 1287 | Word::Const(x) => { 1288 | if option_env!("SPLDEBUG").is_some() { 1289 | println!("CNST({}) {x:?}", stack.len()); 1290 | } 1291 | stack.push(x.clone().ensure_init(stack).spl()) 1292 | } 1293 | Word::Call(x, rem, ra) => { 1294 | if option_env!("SPLDEBUG").is_some() { 1295 | println!("CALL({}) {x}", stack.len()); 1296 | } 1297 | let f = stack.get_func(x.clone())?; 1298 | if ra != 0 { 1299 | let mut f = Value::Func(f); 1300 | for n in 1..ra { 1301 | let mut s = String::new(); 1302 | for _ in 0..n { 1303 | s += "&"; 1304 | } 1305 | let ftmp = f; 1306 | f = Value::Func(AFunc::new(Func { 1307 | ret_count: 1, 1308 | to_call: FuncImpl::NativeDyn(Arc::new(Box::new(move |stack| { 1309 | stack.push(ftmp.clone().spl()); 1310 | Ok(()) 1311 | }))), 1312 | origin: stack.get_frame(), 1313 | run_as_base: false, 1314 | fname: None, 1315 | name: s + &x, 1316 | })); 1317 | } 1318 | stack.push(f.spl()); 1319 | } else { 1320 | stack.call(&f)?; 1321 | if rem { 1322 | for _ in 0..f.ret_count { 1323 | stack.pop(); 1324 | } 1325 | } 1326 | } 1327 | } 1328 | Word::ObjCall(x, rem, ra) => { 1329 | let o = stack.peek(); 1330 | let o = o.lock_ro(); 1331 | let f0 = o.kind.lock_ro(); 1332 | let f = f0 1333 | .get_fn(x.clone()) 1334 | .ok_or_else(|| { 1335 | stack.error(ErrorKind::MethodNotFound(f0.name.clone(), x.clone())) 1336 | })? 1337 | .clone(); 1338 | if option_env!("SPLDEBUG").is_some() { 1339 | println!("CALL({}) {}:{x}", stack.len(), &o.kind.lock_ro().name); 1340 | } 1341 | mem::drop(f0); 1342 | mem::drop(o); 1343 | if ra != 0 { 1344 | let mut f = Value::Func(f.clone()); 1345 | for n in 1..ra { 1346 | let mut s = String::new(); 1347 | for _ in 0..n { 1348 | s += "&"; 1349 | } 1350 | let ftmp = f; 1351 | f = Value::Func(AFunc::new(Func { 1352 | ret_count: 1, 1353 | to_call: FuncImpl::NativeDyn(Arc::new(Box::new(move |stack| { 1354 | stack.push(ftmp.clone().spl()); 1355 | Ok(()) 1356 | }))), 1357 | origin: stack.get_frame(), 1358 | run_as_base: false, 1359 | fname: None, 1360 | name: s + &x, 1361 | })); 1362 | } 1363 | stack.pop(); 1364 | stack.push(f.spl()) 1365 | } else { 1366 | stack.call(&f)?; 1367 | if rem { 1368 | for _ in 0..f.ret_count { 1369 | stack.pop(); 1370 | } 1371 | } 1372 | } 1373 | } 1374 | } 1375 | if stack.return_accumultor > 0 { 1376 | stack.return_accumultor -= 1; 1377 | return Ok(()); 1378 | } 1379 | } 1380 | Ok(()) 1381 | } 1382 | } 1383 | 1384 | /// Any error SPL can handle and throw. 1385 | #[derive(Debug, PartialEq, Eq)] 1386 | pub enum ErrorKind { 1387 | Parse(String, String), 1388 | InvalidCall(String), 1389 | InvalidType(String, String), 1390 | VariableNotFound(String), 1391 | FuncNotFound(String), 1392 | MethodNotFound(String, String), 1393 | PropertyNotFound(String, String), 1394 | TypeNotFound(String), 1395 | LexError(String), 1396 | IO(String), 1397 | Custom(String), 1398 | CustomObject(AMObject), 1399 | } 1400 | 1401 | impl Display for ErrorKind { 1402 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 1403 | match self { 1404 | ErrorKind::Parse(_, _) => f.write_str("Parse"), 1405 | ErrorKind::InvalidCall(_) => f.write_str("InvalidCall"), 1406 | ErrorKind::InvalidType(_, _) => f.write_str("InvalidType"), 1407 | ErrorKind::VariableNotFound(_) => f.write_str("VariableNotFound"), 1408 | ErrorKind::FuncNotFound(_) => f.write_str("FuncNotFound"), 1409 | ErrorKind::MethodNotFound(_, _) => f.write_str("MethodNotFound"), 1410 | ErrorKind::PropertyNotFound(_, _) => f.write_str("PropertyNotFound"), 1411 | ErrorKind::TypeNotFound(_) => f.write_str("TypeNotFound"), 1412 | ErrorKind::LexError(_) => f.write_str("LexError"), 1413 | ErrorKind::IO(_) => f.write_str("IO"), 1414 | ErrorKind::Custom(_) => f.write_str("Custom"), 1415 | ErrorKind::CustomObject(_) => f.write_str("CustomObject"), 1416 | } 1417 | } 1418 | } 1419 | 1420 | /// Wrapper for ErrorKind with the stack trace. 1421 | #[derive(PartialEq, Eq)] 1422 | pub struct Error { 1423 | pub kind: ErrorKind, 1424 | pub stack: Vec, 1425 | pub mr_stack: Vec>, 1426 | } 1427 | 1428 | impl Debug for Error { 1429 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 1430 | f.write_str("\n\nSPL PANIC DUE TO UNCAUGHT ERROR:\n")?; 1431 | f.write_str(format!("Error: {:?}", self.kind).as_str())?; 1432 | f.write_str("\n")?; 1433 | f.write_str(self.stack.join("\n").as_str())?; 1434 | f.write_str("\n\n")?; 1435 | Ok(()) 1436 | } 1437 | } 1438 | 1439 | impl From for Object { 1440 | fn from(value: Error) -> Self { 1441 | let mut obj = Object::new( 1442 | get_type("error").expect("error type must exist"), 1443 | Value::Null, 1444 | ); 1445 | obj.property_map 1446 | .insert("kind".to_owned(), value.kind.to_string().spl()); 1447 | obj.property_map 1448 | .insert("message".to_owned(), format!("{:?}", value.kind).spl()); 1449 | if let ErrorKind::CustomObject(ref o) = value.kind { 1450 | obj.property_map.insert("object".to_owned(), o.clone()); 1451 | } 1452 | if let ErrorKind::Custom(ref s) = value.kind { 1453 | obj.property_map 1454 | .insert("message".to_owned(), s.clone().spl()); 1455 | } 1456 | obj.property_map 1457 | .insert("trace".to_owned(), value.stack.spl()); 1458 | obj.property_map 1459 | .insert("mr-trace".to_owned(), value.mr_stack.spl()); 1460 | obj 1461 | } 1462 | } 1463 | --------------------------------------------------------------------------------