Compare commits
19 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57e868b1e4 | |||
| 9657502261 | |||
| 473ad86d9e | |||
| 80252e4335 | |||
| 5b501c7a8c | |||
| a8d5ab8bda | |||
| b6949a2860 | |||
| 0ffa0a1432 | |||
| 5aa29427ed | |||
| e64e80eb44 | |||
| 805a5f3e74 | |||
| c71dd4d637 | |||
| a597a54f35 | |||
| c4782fec5d | |||
| b1722ed788 | |||
| 6f2de9a704 | |||
| 06c26c910e | |||
| 12d91bcd10 | |||
| 7214d8797a |
24 changed files with 762 additions and 58 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
use nix
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
/target
|
||||
/.direnv
|
||||
|
|
|
|||
9
Cargo.lock
generated
9
Cargo.lock
generated
|
|
@ -1,6 +1,12 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ident_concat"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d8a30341e754d424ef08dbc16d24564ed3438dc6ffcf72cdbd91529496e362e"
|
||||
|
||||
[[package]]
|
||||
name = "multicall"
|
||||
|
|
@ -18,6 +24,7 @@ checksum = "b03f7fbd470aa8b3ad163c85cce8bccfc11cc9c44ef12da0a4eddd98bd307352"
|
|||
name = "spl"
|
||||
version = "0.4.2"
|
||||
dependencies = [
|
||||
"ident_concat",
|
||||
"multicall",
|
||||
"readformat",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "spl"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
edition = "2021"
|
||||
description = "Stack Pogramming Language: A simple, concise scripting language."
|
||||
license = "MIT"
|
||||
|
|
@ -10,3 +10,4 @@ authors = ["TudbuT"]
|
|||
[dependencies]
|
||||
readformat = "0.1"
|
||||
multicall = "0.1"
|
||||
ident_concat = "0.3"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# "Stack Programming Language" =SPL
|
||||
|
||||

|
||||
|
||||
SPL is a simple, concise, concatenative scripting language.
|
||||
|
||||
Example:
|
||||
|
|
|
|||
11
inblock.spl
Normal file
11
inblock.spl
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
func main { exitcode | with args ;
|
||||
|
||||
"Hello, World" in {
|
||||
= :readf1<"Hello, {}!"> => &nop
|
||||
= :readf1<"Hello, {}"> => &nop
|
||||
} if {
|
||||
"name is " print println
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
BIN
logo.png
Normal file
BIN
logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
BIN
logo.xcf
Normal file
BIN
logo.xcf
Normal file
Binary file not shown.
9
shell.nix
Normal file
9
shell.nix
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
cargo
|
||||
helix
|
||||
rust-analyzer
|
||||
cargo-watch
|
||||
];
|
||||
}
|
||||
|
|
@ -71,5 +71,11 @@ construct HashMap {
|
|||
this:size ++ this:=size
|
||||
}
|
||||
}
|
||||
to-array { [[k,v]] | with this ;
|
||||
this:buckets:iter:flatten:collect
|
||||
}
|
||||
iter { Iterator[[k,v]] | with this ;
|
||||
this:buckets:iter:flatten
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ construct net:http:server:_static_ext_server {
|
|||
}
|
||||
|
||||
construct net:http:server:_static_ext_Request {
|
||||
bufsize
|
||||
client-cache
|
||||
;
|
||||
get-bufsize { bufsize | with this ;
|
||||
this:bufsize net:http:server:bufsize or
|
||||
|
|
|
|||
38
spl/iter.spl
38
spl/iter.spl
|
|
@ -23,6 +23,9 @@ construct _Iter {
|
|||
map { MapIter | with map-function this ;
|
||||
map-function this MapIter:new
|
||||
}
|
||||
flatten { FlatIter | with this ;
|
||||
this FlatIter:new
|
||||
}
|
||||
reduce { ReduceIter | with reduce-function this ;
|
||||
reduce-function this ReduceIter:new
|
||||
}
|
||||
|
|
@ -227,3 +230,38 @@ construct EnumerationIter {
|
|||
}
|
||||
|
||||
include _Iter in EnumerationIter
|
||||
|
||||
construct FlatIter {
|
||||
origin
|
||||
subiter
|
||||
;
|
||||
construct { this | with origin this ;
|
||||
origin this:=origin
|
||||
this
|
||||
}
|
||||
cont { | with this ;
|
||||
this:origin:next dup null eq if {
|
||||
this:=origin
|
||||
1 2 stop
|
||||
} :iter this:=subiter
|
||||
0
|
||||
}
|
||||
next { item/null | with this ;
|
||||
this:origin not if {
|
||||
null 2 stop
|
||||
}
|
||||
this:subiter not if {
|
||||
this:cont if {
|
||||
null 3 stop
|
||||
}
|
||||
}
|
||||
while { this:subiter:next dup null eq } {
|
||||
pop
|
||||
this:cont if {
|
||||
null 4 stop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include _Iter in FlatIter
|
||||
|
|
|
|||
272
spl/json.spl
272
spl/json.spl
|
|
@ -1,6 +1,11 @@
|
|||
|
||||
"linkedlist.spl" import
|
||||
"hashmap.spl" import
|
||||
|
||||
construct json namespace {
|
||||
_StringyJSON
|
||||
Object
|
||||
Array
|
||||
;
|
||||
props-to-sjson { s | with spaces props this ;
|
||||
def value, comma
|
||||
|
|
@ -40,8 +45,275 @@ construct json namespace {
|
|||
:join<comma> concat
|
||||
"}" concat
|
||||
}
|
||||
stringy { s | with object spaces this ;
|
||||
object gettype "str" eq if {
|
||||
object:escape
|
||||
2 stop
|
||||
}
|
||||
object gettype json:Array eq if {
|
||||
"[" spaces if { " " concat }
|
||||
object:map
|
||||
:iter
|
||||
:map<| json:stringy<spaces> >
|
||||
:join<"," spaces if { " " concat }> concat
|
||||
spaces if { " " concat } "]" concat
|
||||
2 stop
|
||||
}
|
||||
object gettype json:Object eq if {
|
||||
"{" spaces if { " " concat }
|
||||
object:map
|
||||
:iter
|
||||
:map<| =>! [ def key, value ]
|
||||
key:escape
|
||||
":" spaces if { " " concat } concat
|
||||
value json:stringy<spaces> concat
|
||||
>
|
||||
:join<"," spaces if { " " concat }>
|
||||
concat
|
||||
spaces if { " " concat } "}" concat
|
||||
2 stop
|
||||
}
|
||||
null
|
||||
}
|
||||
parse { [^ok,json:Object/json:Array]/[^err,error] | with input this ;
|
||||
input this:parse-stringy-raw =>? [ ^ok def mode, raw ] if {
|
||||
def ty
|
||||
mode ^obj eq if { json:Object =ty }
|
||||
mode ^arr eq if { json:Array =ty }
|
||||
[
|
||||
^ok
|
||||
raw ty:new:from
|
||||
]
|
||||
}
|
||||
}
|
||||
parse-stringy-raw { [^ok,^obj/^arr,[[k,v]]/[v]]/[^err,error] | with input this ;
|
||||
def error
|
||||
[ ^err ^no-object ]
|
||||
def mode
|
||||
|
||||
def cdquot, cbackslash, cn, ct, cr, cspace, ctab, clf, ccol, ccomma, ccbo, ccbc, csbo, csbc
|
||||
"\"" :_char =cdquot
|
||||
"\\" :_char =cbackslash
|
||||
"n" :_char =cn
|
||||
"t" :_char =ct
|
||||
"r" :_char =cr
|
||||
" " :_char =cspace
|
||||
"\t" :_char =ctab
|
||||
"\n" :_char =clf
|
||||
":" :_char =ccol
|
||||
"," :_char =ccomma
|
||||
"{" :_char =ccbo
|
||||
"}" :_char =ccbc
|
||||
"[" :_char =csbo
|
||||
"]" :_char =csbc
|
||||
|
||||
input:readf1<"{{}}"> dup if { ^obj =mode }
|
||||
input:readf1<"[{}]"> dup if { ^arr =mode } or => def items if {
|
||||
def in-string, in-escape, exists
|
||||
pop [ ^ok mode [
|
||||
mode LinkedList:new dup =mode :push-front
|
||||
mode:value ^obj eq if { [ } ""
|
||||
items _array :foreach<| with char ;
|
||||
error if { 2 stop } "fail fast";
|
||||
|
||||
in-escape not char cdquot eq and if {
|
||||
in-string not =in-string
|
||||
2 stop
|
||||
}
|
||||
in-escape if {
|
||||
char cdquot eq
|
||||
char cbackslash eq or if {
|
||||
char awrap _str concat
|
||||
}
|
||||
char cn eq if {
|
||||
"\n" concat
|
||||
}
|
||||
char ct eq if {
|
||||
"\t" concat
|
||||
}
|
||||
char cr eq if {
|
||||
"\r" concat
|
||||
}
|
||||
0 =in-escape
|
||||
2 stop
|
||||
}
|
||||
in-string char cbackslash eq and if {
|
||||
1 =in-escape
|
||||
2 stop
|
||||
}
|
||||
in-string not if {
|
||||
char cspace eq
|
||||
char ctab eq or
|
||||
char clf eq or if {
|
||||
3 stop
|
||||
}
|
||||
char ccol eq if {
|
||||
1 =exists
|
||||
""
|
||||
3 stop
|
||||
}
|
||||
char ccomma eq if {
|
||||
mode:value ^obj eq if {
|
||||
] [ ""
|
||||
}
|
||||
mode:value ^arr eq if {
|
||||
""
|
||||
}
|
||||
0 =exists
|
||||
3 stop
|
||||
}
|
||||
char ccbo eq if {
|
||||
pop [ [ ""
|
||||
^obj mode:push-front
|
||||
3 stop
|
||||
}
|
||||
char ccbc eq if {
|
||||
exists not if { pop pop } "remove the empty KV and string";
|
||||
exists if { ] } "close KV";
|
||||
]
|
||||
mode:pop-front;
|
||||
1 =exists
|
||||
3 stop
|
||||
}
|
||||
char csbo eq if {
|
||||
pop [ ""
|
||||
^arr mode:push-front
|
||||
3 stop
|
||||
}
|
||||
char csbc eq if {
|
||||
exists not if { pop } "remove the empty string";
|
||||
]
|
||||
mode:pop-front;
|
||||
1 =exists
|
||||
3 stop
|
||||
}
|
||||
}
|
||||
1 =exists
|
||||
char awrap _str concat
|
||||
>
|
||||
exists not if { pop pop }
|
||||
exists if { mode:value ^obj eq if { ] } }
|
||||
] ]
|
||||
}
|
||||
error if {
|
||||
pop [ ^err error ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
construct json:Object {
|
||||
map
|
||||
;
|
||||
construct { this | with this ;
|
||||
HashMap:new this:=map
|
||||
this
|
||||
}
|
||||
from { this | with rawdata this ;
|
||||
rawdata:foreach<| with item ;
|
||||
item:1 item:0 this:map:set;
|
||||
>
|
||||
this
|
||||
}
|
||||
get-object { json:Object/null | with key this ;
|
||||
this:map:get<key> with object ;
|
||||
object gettype json:Object eq not if {
|
||||
object gettype "array" eq if {
|
||||
object json:Object:new:from dup =object this:map:set<key>;
|
||||
2 stop
|
||||
}
|
||||
null =object
|
||||
}
|
||||
object
|
||||
}
|
||||
get-array { json:Array/null | with key this ;
|
||||
this:map:get<key> with object ;
|
||||
object gettype json:Array eq not if {
|
||||
object gettype "array" eq if {
|
||||
object json:Array:new:from dup =object this:map:set<key>;
|
||||
2 stop
|
||||
}
|
||||
null =object
|
||||
}
|
||||
object
|
||||
}
|
||||
get-str { str/null | with key this ;
|
||||
this:map:get<key> with object ;
|
||||
object gettype "str" eq not if {
|
||||
null =object
|
||||
}
|
||||
object
|
||||
}
|
||||
get-int { mega/null | with key this ;
|
||||
this:map:get<key> with object ;
|
||||
null
|
||||
object gettype "str" eq if {
|
||||
pop object _mega
|
||||
}
|
||||
}
|
||||
get-double { double/null | with key this ;
|
||||
this:map:get<key> with object ;
|
||||
null
|
||||
object gettype "str" eq if {
|
||||
pop object _double
|
||||
}
|
||||
}
|
||||
set-object { | with value key this ;
|
||||
value gettype json:Object eq not if {
|
||||
"not a json:Object" throw
|
||||
}
|
||||
value this:map:set<key>;
|
||||
}
|
||||
set-array { | with value key this ;
|
||||
value gettype json:Array eq not if {
|
||||
"not a json:Array" throw
|
||||
}
|
||||
value this:map:set<key>;
|
||||
}
|
||||
set-str { | with value key this ;
|
||||
value gettype "str" eq not if {
|
||||
"not a string" throw
|
||||
}
|
||||
value this:map:set<key>;
|
||||
}
|
||||
set-int { | with value key this ;
|
||||
value gettype "mega" eq not if {
|
||||
"not a mega" throw
|
||||
}
|
||||
value _str this:map:set<key>;
|
||||
}
|
||||
set-double { | with value key this ;
|
||||
value gettype "double" eq not if {
|
||||
"not a double" throw
|
||||
}
|
||||
value _str this:map:set<key>;
|
||||
}
|
||||
}
|
||||
|
||||
construct json:Array {
|
||||
;
|
||||
array { array | with this ;
|
||||
this:map
|
||||
}
|
||||
construct { this | with this ;
|
||||
List:new this:=map
|
||||
this
|
||||
}
|
||||
from { this | with rawdata this ;
|
||||
rawdata this:=map
|
||||
this
|
||||
}
|
||||
add { index this | with this ;
|
||||
this:map:len
|
||||
null this:map:push;
|
||||
this
|
||||
}
|
||||
len { length | with this ;
|
||||
this:map:len
|
||||
}
|
||||
}
|
||||
|
||||
include json:Object in json:Array
|
||||
|
||||
construct json:_StringyJSON {
|
||||
;
|
||||
sjson { s | with spaces this ;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ construct LinkedList {
|
|||
;
|
||||
construct { this | }
|
||||
len { mega | with this ;
|
||||
def i this:value null eq not =i
|
||||
def i this:value null eq not _mega =i
|
||||
while { this:next null eq not } {
|
||||
this:next =this
|
||||
i ++ =i
|
||||
|
|
@ -115,6 +115,30 @@ construct LinkedList {
|
|||
iter { LinkedListIter | with this ;
|
||||
this LinkedListIter:new
|
||||
}
|
||||
append { this | with array this ;
|
||||
def i 0 =i
|
||||
def e this:last-entry =e
|
||||
this:value null eq if {
|
||||
0 array:get this:=value;
|
||||
i ++ =i
|
||||
}
|
||||
while { i array:len lt } {
|
||||
LinkedList:new dup e:=next =e
|
||||
i array:get e:=value
|
||||
i ++ =i
|
||||
}
|
||||
this
|
||||
}
|
||||
to-array { array | with this ;
|
||||
def i 0 =i
|
||||
def a this:len anew =a
|
||||
while { i a:len lt } {
|
||||
this:value i a:set;
|
||||
this:next =this
|
||||
i ++ =i
|
||||
}
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
construct LinkedListIter {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ construct sort namespace {
|
|||
insertion { array | with array this ;
|
||||
"for nearly-sorted";
|
||||
def i, j, len
|
||||
0 =i
|
||||
1 =i
|
||||
array:len =len
|
||||
|
||||
while { i len lt } {
|
||||
|
|
|
|||
61
spl/std.spl
61
spl/std.spl
|
|
@ -35,6 +35,9 @@ construct _str_ext {
|
|||
null clone this settype
|
||||
}
|
||||
to-bytes { [int] | str-to-bytes }
|
||||
len { mega | with this ;
|
||||
this array-len
|
||||
}
|
||||
split { str | with splitter this ;
|
||||
splitter:to-bytes this:to-bytes:split:map<{ | bytes-to-str }>
|
||||
}
|
||||
|
|
@ -84,6 +87,28 @@ construct _str_ext {
|
|||
_mega_radix { mega | with radix this ;
|
||||
this radix str-to-mega-radix
|
||||
}
|
||||
leftpad { str | with length char this ;
|
||||
this
|
||||
this:len length lt if {
|
||||
pop
|
||||
char gettype "str" eq if {
|
||||
char :_char =char
|
||||
}
|
||||
def a length this:len - anew =a
|
||||
a:cmap<| pop char > this _array aadd _str
|
||||
}
|
||||
}
|
||||
rightpad { str | with length char this ;
|
||||
this
|
||||
this:len length lt if {
|
||||
pop
|
||||
char gettype "str" eq if {
|
||||
char :_char =char
|
||||
}
|
||||
def a length this:len - anew =a
|
||||
this _array a:cmap<| pop char > aadd _str
|
||||
}
|
||||
}
|
||||
} include _str_ext in str
|
||||
|
||||
construct _num {
|
||||
|
|
@ -603,7 +628,7 @@ func aadd { array | with arr1 arr2 ;
|
|||
}
|
||||
|
||||
func concat { str | with a b ;
|
||||
a _array b _array aadd _str
|
||||
a _barray b _barray aadd _str
|
||||
}
|
||||
|
||||
func nconcat { str | with amt ;
|
||||
|
|
@ -812,7 +837,19 @@ func _ { |
|
|||
}
|
||||
}
|
||||
|
||||
func nop { | }
|
||||
|
||||
func call-main-on-file { | with file ;
|
||||
file "--help" eq if {
|
||||
"spl [default]: spl [options] <file> [args]" println;
|
||||
"" println;
|
||||
"options:" println;
|
||||
" --help prints this" println;
|
||||
" --build build an SPL with given natives file" println;
|
||||
" --run run with natives" println;
|
||||
" --buildrun run with natives and keep the built SPL" println;
|
||||
0 exit
|
||||
}
|
||||
catch {
|
||||
"@" (0 (file _array):get 0 ("#" _array):get eq) if { pop "" } file concat
|
||||
import
|
||||
|
|
@ -837,6 +874,11 @@ construct unwrapping extension {
|
|||
"null cannot be unwrapped." panic
|
||||
}
|
||||
}
|
||||
expect { this | swap with message ;
|
||||
dup null eq if {
|
||||
pop message panic
|
||||
}
|
||||
}
|
||||
unwrap-or { this | swap with callable ;
|
||||
dup null eq if {
|
||||
pop callable call
|
||||
|
|
@ -863,3 +905,20 @@ func register-field { | with field-name namespace-name ;
|
|||
iter:next dyn-call
|
||||
iter:foreach<&dyn-objcall>
|
||||
}
|
||||
|
||||
|
||||
construct Synchronizer {
|
||||
sync
|
||||
;
|
||||
construct { this | with this ;
|
||||
0 this:=sync
|
||||
this
|
||||
}
|
||||
do { | with callable this ;
|
||||
"will increment the sync field if and only if it is zero. when it was incremented (returned zero), the loop will exit";
|
||||
while { this "sync" atomic-get-inc-if-zero } { yield }
|
||||
callable:call
|
||||
0 this:=sync
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -235,6 +235,34 @@ fn read_block_dyn(
|
|||
}
|
||||
i += 3;
|
||||
}
|
||||
"in" if !compat => {
|
||||
let mut blk = read_block(&str_words[i + 2..], false, compat)?;
|
||||
i += 2 + blk.2;
|
||||
let mut pre = Words {
|
||||
words: vec![
|
||||
Word::Key(Keyword::With(vec!["__in-item".to_owned()])),
|
||||
Word::Key(Keyword::Func(
|
||||
"=".to_string(),
|
||||
1,
|
||||
Words {
|
||||
words: vec![
|
||||
Word::Key(Keyword::If(Words {
|
||||
words: vec![
|
||||
Word::Const(Value::Mega(1)),
|
||||
Word::Const(Value::Mega(3)),
|
||||
Word::Call("stop".to_owned(), false, 0),
|
||||
],
|
||||
})),
|
||||
Word::Call("__in-item".to_owned(), false, 0),
|
||||
],
|
||||
},
|
||||
)),
|
||||
Word::Const(Value::Mega(0)),
|
||||
],
|
||||
};
|
||||
pre.words.append(&mut blk.1.words);
|
||||
words.push(Word::Key(Keyword::In(pre)));
|
||||
}
|
||||
"use" => {
|
||||
let item = str_words[i + 1].to_owned();
|
||||
i += 1;
|
||||
|
|
@ -292,23 +320,36 @@ fn read_block_dyn(
|
|||
let throwing = cword.contains('!');
|
||||
if str_words[i + 1] == "[" {
|
||||
i += 1;
|
||||
let mut block =
|
||||
let block =
|
||||
read_block_dyn(&str_words[i + 1..], false, "]".to_owned(), compat)?;
|
||||
i += block.2 + 1;
|
||||
words.push(Word::Call("[".to_owned(), false, 0));
|
||||
words.append(&mut block.1.words);
|
||||
for word in block.1.words {
|
||||
if let Word::Key(Keyword::Def(ref varname)) = &word {
|
||||
words.push(Word::Key(Keyword::Def(varname.clone())));
|
||||
words.push(Word::Call("=".to_owned() + &varname, false, 1));
|
||||
} else {
|
||||
words.push(word);
|
||||
}
|
||||
}
|
||||
words.push(Word::Call("]".to_owned(), false, 0));
|
||||
} else {
|
||||
words.append(
|
||||
&mut read_block_dyn(
|
||||
&[str_words[i + 1].clone()],
|
||||
false,
|
||||
"".to_owned(),
|
||||
false,
|
||||
)?
|
||||
.1
|
||||
.words,
|
||||
);
|
||||
if str_words[i + 1] == "def" {
|
||||
i += 1;
|
||||
words.push(Word::Key(Keyword::Def(str_words[i + 1].clone())));
|
||||
words.push(Word::Call("=".to_owned() + &str_words[i + 1], false, 1));
|
||||
} else {
|
||||
words.append(
|
||||
&mut read_block_dyn(
|
||||
&[str_words[i + 1].clone()],
|
||||
false,
|
||||
"".to_owned(),
|
||||
false,
|
||||
)?
|
||||
.1
|
||||
.words,
|
||||
);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
words.push(Word::Call("match".to_owned(), false, 0));
|
||||
|
|
@ -413,6 +454,11 @@ pub fn parse(mut input: String) -> Vec<String> {
|
|||
let mut raw = 0;
|
||||
|
||||
for line in input.split('\n') {
|
||||
let line = line.trim();
|
||||
if line.starts_with("//") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut in_string = false;
|
||||
let mut escaping = false;
|
||||
let mut was_in_string = false;
|
||||
|
|
@ -429,6 +475,12 @@ pub fn parse(mut input: String) -> Vec<String> {
|
|||
if c == 'r' {
|
||||
s += "\r";
|
||||
}
|
||||
if c == 't' {
|
||||
s += "\t";
|
||||
}
|
||||
if c == 'e' {
|
||||
s += "\u{1b}";
|
||||
}
|
||||
if c == '"' {
|
||||
s += "\"";
|
||||
}
|
||||
|
|
|
|||
17
src/lib.rs
17
src/lib.rs
|
|
@ -39,6 +39,7 @@ pub mod stream;
|
|||
pub use lexer::*;
|
||||
pub use runtime::*;
|
||||
|
||||
use ident_concat::ident;
|
||||
use std::fs;
|
||||
|
||||
/// Creates a runtime, lexes and executes some SPL code from a file, returning the stack that was
|
||||
|
|
@ -104,8 +105,8 @@ nofmt! {
|
|||
#[macro_export]
|
||||
macro_rules! require_mut_on_stack {
|
||||
($name:tt, $type:tt, $stack:expr, $fn:literal) => {
|
||||
let binding = $stack.pop();
|
||||
let Value::$type(ref mut $name) = binding.lock().native else {
|
||||
let ident!($name binding) = $stack.pop();
|
||||
let Value::$type(ref mut $name) = ident!($name binding).lock().native else {
|
||||
return $stack.err(ErrorKind::InvalidCall($fn.to_owned()))
|
||||
};
|
||||
};
|
||||
|
|
@ -146,15 +147,15 @@ nofmt! {
|
|||
#[macro_export]
|
||||
macro_rules! require_array_on_stack {
|
||||
($name:tt, $stack:expr, $fn:literal) => {
|
||||
let binding = $stack.pop();
|
||||
require_array!($name, binding, $stack, $fn)
|
||||
let ident!($name binding) = $stack.pop();
|
||||
require_array!($name, ident!($name binding), $stack, $fn)
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! require_byte_array_on_stack {
|
||||
($name:tt, $stack:expr, $fn:literal) => {
|
||||
let binding = $stack.pop();
|
||||
let $name = match binding.lock_ro().native {
|
||||
let ident!($name binding) = $stack.pop();
|
||||
let $name = match ident!($name binding).lock_ro().native {
|
||||
Value::Array(ref x) => x
|
||||
.iter()
|
||||
.cloned()
|
||||
|
|
@ -178,8 +179,8 @@ nofmt! {
|
|||
#[macro_export]
|
||||
macro_rules! require_mut_array_on_stack {
|
||||
($name:tt, $stack:expr, $fn:literal) => {
|
||||
let binding = $stack.pop();
|
||||
require_mut_array!($name, binding, $stack, $fn)
|
||||
let ident!($name binding) = $stack.pop();
|
||||
require_mut_array!($name, ident!($name binding), $stack, $fn)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,11 @@ impl RustAppBuilder {
|
|||
.arg(format!("spl@{}", env!("CARGO_PKG_VERSION")))
|
||||
.current_dir(format!("{tmp}/spl-{name}"))
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.stderr(if output {
|
||||
Stdio::inherit()
|
||||
} else {
|
||||
Stdio::null()
|
||||
})
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()?;
|
||||
|
|
@ -160,7 +164,7 @@ impl RustAppBuilder {
|
|||
fs::write(
|
||||
format!("{tmp}/spl-{name}/src/main.rs"),
|
||||
stringify! {
|
||||
use spl::{runtime::*, *};
|
||||
use ::spl::{runtime::*, *};
|
||||
|
||||
use std::env::args;
|
||||
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ pub struct FrameInfo {
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct Frame {
|
||||
parent: Option<Arc<Frame>>,
|
||||
pub variables: Mut<HashMap<String, AMObject>>,
|
||||
pub variables: Mut<HashMap<String, Arc<Mut<AMObject>>>>,
|
||||
pub functions: Mut<HashMap<String, AFunc>>,
|
||||
pub origin: FrameInfo,
|
||||
pub redirect_to_base: bool,
|
||||
|
|
@ -364,8 +364,8 @@ impl Frame {
|
|||
pub fn set_var(&self, name: String, obj: AMObject, stack: &Stack) -> OError {
|
||||
let mut frame = self;
|
||||
loop {
|
||||
if let Some(x) = frame.variables.lock().get_mut(&name) {
|
||||
*x = obj;
|
||||
if let Some(x) = frame.variables.lock_ro().get(&name) {
|
||||
*(x.lock()) = obj;
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(ref x) = frame.parent {
|
||||
|
|
@ -380,7 +380,7 @@ impl Frame {
|
|||
let mut frame = self;
|
||||
loop {
|
||||
if let Some(x) = frame.variables.lock_ro().get(&name) {
|
||||
return Ok(x.clone());
|
||||
return Ok(x.lock_ro().clone());
|
||||
}
|
||||
if let Some(ref x) = frame.parent {
|
||||
frame = x;
|
||||
|
|
@ -436,8 +436,8 @@ impl Frame {
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct Stack {
|
||||
frames: Vec<Arc<Frame>>,
|
||||
object_stack: Vec<AMObject>,
|
||||
objcall_stack: Vec<AMObject>,
|
||||
object_stack: LinkedList<AMObject>,
|
||||
objcall_stack: LinkedList<AMObject>,
|
||||
files: Vec<String>,
|
||||
pub return_accumultor: u32,
|
||||
}
|
||||
|
|
@ -472,8 +472,8 @@ impl Stack {
|
|||
let o = Arc::new(Frame::root());
|
||||
let mut r = Stack {
|
||||
frames: vec![o.clone()],
|
||||
object_stack: Vec::new(),
|
||||
objcall_stack: Vec::new(),
|
||||
object_stack: LinkedList::new(),
|
||||
objcall_stack: LinkedList::new(),
|
||||
files: Vec::new(),
|
||||
return_accumultor: 0,
|
||||
};
|
||||
|
|
@ -489,8 +489,8 @@ impl Stack {
|
|||
let o = Arc::new(Frame::root_in(frame_info));
|
||||
let mut r = Stack {
|
||||
frames: vec![o.clone()],
|
||||
object_stack: Vec::new(),
|
||||
objcall_stack: Vec::new(),
|
||||
object_stack: LinkedList::new(),
|
||||
objcall_stack: LinkedList::new(),
|
||||
files: Vec::new(),
|
||||
return_accumultor: 0,
|
||||
};
|
||||
|
|
@ -555,14 +555,15 @@ impl Stack {
|
|||
if frame.redirect_to_base {
|
||||
frame = self.frames.first().unwrap().clone();
|
||||
}
|
||||
let tmpname = name.clone();
|
||||
let variable = Arc::new(Mut::new(Value::Null.spl()));
|
||||
let var = variable.clone();
|
||||
frame.functions.lock().insert(
|
||||
name.clone(),
|
||||
Arc::new(Func {
|
||||
ret_count: 1,
|
||||
origin: Arc::new(Frame::dummy()),
|
||||
to_call: FuncImpl::NativeDyn(Arc::new(Box::new(move |stack| {
|
||||
stack.push(stack.get_var(tmpname.clone())?);
|
||||
stack.push(var.lock_ro().clone());
|
||||
Ok(())
|
||||
}))),
|
||||
run_as_base: false,
|
||||
|
|
@ -570,7 +571,7 @@ impl Stack {
|
|||
name: name.clone(),
|
||||
}),
|
||||
);
|
||||
let tmpname = name.clone();
|
||||
let var = variable.clone();
|
||||
frame.functions.lock().insert(
|
||||
"=".to_owned() + &name,
|
||||
Arc::new(Func {
|
||||
|
|
@ -578,27 +579,26 @@ impl Stack {
|
|||
origin: Arc::new(Frame::dummy()),
|
||||
to_call: FuncImpl::NativeDyn(Arc::new(Box::new(move |stack| {
|
||||
let v = stack.pop();
|
||||
stack.set_var(tmpname.clone(), v)
|
||||
*(var.lock()) = v;
|
||||
Ok(())
|
||||
}))),
|
||||
run_as_base: false,
|
||||
fname: Some("RUNTIME".to_owned()),
|
||||
name: "=".to_owned() + &name,
|
||||
}),
|
||||
);
|
||||
frame.variables.lock().insert(name, Value::Null.spl());
|
||||
frame.variables.lock().insert(name, variable);
|
||||
}
|
||||
|
||||
pub fn pop_until(&mut self, obj: AMObject) -> Vec<AMObject> {
|
||||
let Some((idx, ..)) = self
|
||||
.object_stack
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rfind(|o| *o.1.lock_ro() == *obj.lock_ro())
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
let items = self.object_stack[idx + 1..].to_vec();
|
||||
self.object_stack = self.object_stack[0..idx].to_vec();
|
||||
let mut items = Vec::new();
|
||||
while let Some(item) = self.object_stack.pop_back() {
|
||||
if obj == item {
|
||||
break;
|
||||
}
|
||||
items.push(item);
|
||||
}
|
||||
items.reverse();
|
||||
items
|
||||
}
|
||||
|
||||
|
|
@ -637,18 +637,18 @@ impl Stack {
|
|||
}
|
||||
|
||||
pub fn push(&mut self, obj: AMObject) {
|
||||
self.object_stack.push(obj)
|
||||
self.object_stack.push_back(obj)
|
||||
}
|
||||
|
||||
pub fn peek(&mut self) -> AMObject {
|
||||
self.object_stack
|
||||
.last()
|
||||
.back()
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null.spl())
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> AMObject {
|
||||
self.object_stack.pop().unwrap_or(Value::Null.spl())
|
||||
self.object_stack.pop_back().unwrap_or(Value::Null.spl())
|
||||
}
|
||||
|
||||
pub fn get_origin(&self) -> FrameInfo {
|
||||
|
|
@ -829,6 +829,8 @@ pub enum Keyword {
|
|||
/// Defines function <name> with <type> impl type
|
||||
/// equivalent to "<content>" "<name>" "<type>" dyn-func-of
|
||||
FuncOf(String, String, FuncImplType),
|
||||
/// in { = ... = ... = ... }
|
||||
In(Words),
|
||||
}
|
||||
|
||||
/// Any SPL value that is not a construct.
|
||||
|
|
@ -1475,12 +1477,12 @@ impl Words {
|
|||
}
|
||||
Keyword::ObjPush => {
|
||||
let o = stack.pop();
|
||||
stack.objcall_stack.push(o);
|
||||
stack.objcall_stack.push_back(o);
|
||||
}
|
||||
Keyword::ObjPop => {
|
||||
let o = stack
|
||||
.objcall_stack
|
||||
.pop()
|
||||
.pop_back()
|
||||
.expect("invalid word generation. objpop without objpush!");
|
||||
stack.push(o);
|
||||
}
|
||||
|
|
@ -1498,6 +1500,16 @@ impl Words {
|
|||
}),
|
||||
)
|
||||
}),
|
||||
Keyword::In(blk) => {
|
||||
stack.call(&Arc::new(Func {
|
||||
ret_count: 0,
|
||||
to_call: FuncImpl::SPL(blk.clone()),
|
||||
origin: stack.get_frame(),
|
||||
fname: None,
|
||||
name: "in-block".to_owned(),
|
||||
run_as_base: false,
|
||||
}))?;
|
||||
}
|
||||
},
|
||||
Word::Const(x) => {
|
||||
if option_env!("SPLDEBUG").is_some() {
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ fn sasm_parse<'a>(line: &str, words: &mut Vec<Word>, lines: &mut impl Iterator<I
|
|||
sasm_read_func(lines),
|
||||
))),
|
||||
"if" => words.push(Word::Key(Keyword::If(sasm_read_func(lines)))),
|
||||
"in" => words.push(Word::Key(Keyword::In(sasm_read_func(lines)))),
|
||||
"with" => words.push(Word::Key(Keyword::With(
|
||||
line.into_iter().skip(1).map(ToOwned::to_owned).collect(),
|
||||
))),
|
||||
|
|
@ -310,6 +311,11 @@ fn sasm_write_func(words: Words) -> String {
|
|||
output += &sasm_write_func(blk).replace("\n", "\n\t").trim_end();
|
||||
output += "\nend\n";
|
||||
}
|
||||
Keyword::In(blk) => {
|
||||
output += "in\n\t";
|
||||
output += &sasm_write_func(blk).replace("\n", "\n\t").trim_end();
|
||||
output += "\nend\n";
|
||||
}
|
||||
Keyword::With(items) => {
|
||||
output += "with";
|
||||
for item in items {
|
||||
|
|
|
|||
122
src/std_fns.rs
122
src/std_fns.rs
|
|
@ -155,6 +155,7 @@ pub fn array_new(stack: &mut Stack) -> OError {
|
|||
pub fn array_len(stack: &mut Stack) -> OError {
|
||||
let binding = stack.pop();
|
||||
let len = match binding.lock_ro().native {
|
||||
Value::Str(ref a) => a.chars().count(),
|
||||
Value::Array(ref a) => a.len(),
|
||||
Value::ByteArray(ref a) => a.len(),
|
||||
_ => return stack.err(ErrorKind::InvalidCall("array-len".to_owned())),
|
||||
|
|
@ -1013,6 +1014,11 @@ pub fn time(stack: &mut Stack) -> OError {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn thread_yield(_stack: &mut Stack) -> OError {
|
||||
thread::yield_now();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn str_readf(stack: &mut Stack) -> OError {
|
||||
require_on_stack!(string, Str, stack, "str-readf");
|
||||
require_on_stack!(pat, Str, stack, "str-readf");
|
||||
|
|
@ -1176,9 +1182,120 @@ pub fn chdir(stack: &mut Stack) -> OError {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn json_parse(_stack: &mut Stack) -> OError {
|
||||
/*
|
||||
require_on_stack!(input, Str, stack, "json_parse");
|
||||
let objstr = readf1("{{}}", &input);
|
||||
let arrstr = readf1("[{}]", &input);
|
||||
enum Mode {
|
||||
Obj,
|
||||
Arr,
|
||||
}
|
||||
use Mode::*;
|
||||
let primary_mode = if arrstr.is_some() { Arr } else { Obj };
|
||||
let Some(input) = (if arrstr.is_some() { arrstr } else { objstr }) else {
|
||||
let errarr = vec![
|
||||
Value::Str("err".to_owned()).spl(),
|
||||
Value::Str("no-obj".to_owned()).spl(),
|
||||
];
|
||||
stack.push(Value::Array(errarr).spl());
|
||||
return Ok(());
|
||||
};
|
||||
let mut mode = LinkedList::new();
|
||||
mode.push_back(primary_mode);
|
||||
let mut values = Vec::new();
|
||||
|
||||
let mut in_string = false;
|
||||
let mut in_escape = false;
|
||||
let mut exists = false;
|
||||
let mut key = String::new();
|
||||
let mut string = String::new();
|
||||
let mut object = None;
|
||||
for c in input.chars() {
|
||||
if !in_escape && c != '"' {
|
||||
in_string = !in_string;
|
||||
continue;
|
||||
}
|
||||
if in_escape {
|
||||
if c == '"' || c == '\\' {
|
||||
string.push(c);
|
||||
}
|
||||
match c {
|
||||
'n' => string.push('\n'),
|
||||
't' => string.push('\t'),
|
||||
'r' => string.push('\r'),
|
||||
_ => (),
|
||||
}
|
||||
in_escape = false;
|
||||
continue;
|
||||
}
|
||||
if in_string && c == '\\' {
|
||||
in_escape = true;
|
||||
continue;
|
||||
}
|
||||
if !in_string {
|
||||
if c.is_whitespace() {
|
||||
continue;
|
||||
}
|
||||
if c == ':' {
|
||||
exists = true;
|
||||
(key, string) = (string, String::new());
|
||||
continue;
|
||||
}
|
||||
if c == ',' {
|
||||
let o = if let Some(o) = object {
|
||||
o
|
||||
} else {
|
||||
Value::Str(string).spl()
|
||||
};
|
||||
match mode.back() {
|
||||
Some(Obj) => {
|
||||
values.push(
|
||||
Value::Array(vec![Value::Str(key).spl(), Value::Str(string).spl()])
|
||||
.spl(),
|
||||
);
|
||||
}
|
||||
Some(Arr) => values.push(),
|
||||
None => stack.err(ErrorKind::Custom("json-parse"))?,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stack.push(
|
||||
Value::Array(vec![
|
||||
Value::Str("ok".to_owned()).spl(),
|
||||
Value::Str(
|
||||
match primary_mode {
|
||||
Obj => "obj",
|
||||
Arr => "arr",
|
||||
}
|
||||
.to_owned(),
|
||||
)
|
||||
.spl(),
|
||||
values,
|
||||
])
|
||||
.spl(),
|
||||
);*/
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn atomic_get_inc_if_zero(stack: &mut Stack) -> OError {
|
||||
require_on_stack!(field, Str, stack, "atomic-get-inc-if-zero");
|
||||
let o = stack.pop();
|
||||
let o = o.lock_ro();
|
||||
let f = o.field(&field, stack)?;
|
||||
let mut f = f.lock();
|
||||
stack.push(f.clone().spl());
|
||||
if f.native == Value::Mega(0) {
|
||||
f.native = Value::Mega(1);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn register(r: &mut Stack, o: Arc<Frame>) {
|
||||
type Fn = fn(&mut Stack) -> OError;
|
||||
let fns: [(&str, Fn, u32); 71] = [
|
||||
let fns: [(&str, Fn, u32); 73] = [
|
||||
("pop", pop, 0),
|
||||
("dup", dup, 2),
|
||||
("dup2", dup2, 3),
|
||||
|
|
@ -1241,6 +1358,7 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
|
|||
("write-file-sasm", write_file_sasm, 1),
|
||||
("fork", fork, 0),
|
||||
("sleeptime", time, 0),
|
||||
("yield", thread_yield, 0),
|
||||
("str-readf", str_readf, 1),
|
||||
("str-to-mega-radix", str_to_mega_radix, 1),
|
||||
("mega-to-str-radix", mega_to_str_radix, 1),
|
||||
|
|
@ -1250,6 +1368,8 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
|
|||
("delete-file", delete_file, 1),
|
||||
("delete-dir", delete_dir, 1),
|
||||
("chdir", chdir, 0),
|
||||
("atomic-get-inc-if-zero", atomic_get_inc_if_zero, 1),
|
||||
// ("json-parse", json_parse, 1),
|
||||
];
|
||||
for f in fns {
|
||||
r.define_func(
|
||||
|
|
|
|||
75
test.spl
75
test.spl
|
|
@ -417,6 +417,81 @@ func main { int | with args ;
|
|||
pop
|
||||
"result should be 493 538 503 (arbitrary numbers)" println
|
||||
|
||||
"" println
|
||||
"testing json:parse" println
|
||||
|
||||
"object:" println
|
||||
!{{ "hello": "world", "another": "property", "obj": { "hi":"lol", "abcdefg": "hijklmnop" }, "arr": [ "a", "b", "c" ] }} dup println json:parse-stringy-raw :sjson<0> println
|
||||
"array:" println
|
||||
!{[ "hello", "world", "another", "property", { "hi": "lol", "abcdefg": "hijklmnop" }, [ "a", "b", "c" ] ]} dup println json:parse-stringy-raw :sjson<0> println
|
||||
|
||||
"object object:" println
|
||||
def object, subobject
|
||||
|
||||
json:Object:new =object
|
||||
|
||||
"world" "hello" object:set-str;
|
||||
5 "megaproperty" object:set-int;
|
||||
|
||||
object json:stringy<0> println
|
||||
|
||||
json:Object:new =subobject
|
||||
subobject "subobj" object:set-object;
|
||||
"world" "hi" subobject:set-str;
|
||||
|
||||
json:Array:new =subobject
|
||||
subobject "arr" object:set-array;
|
||||
"good morning!" subobject:add:set-str;
|
||||
"may the network of evil rise" subobject:add:set-str;
|
||||
|
||||
object json:stringy<0> dup =object println
|
||||
|
||||
object json:parse =>? [ ^ok &=object ] not if {
|
||||
:sjson<0> println
|
||||
}
|
||||
|
||||
object:get-object<"subobj">:get-str<"hi"> println;
|
||||
|
||||
|
||||
"" println;
|
||||
"matching Hello, world using in block" println;
|
||||
|
||||
"Hello, world" in {
|
||||
= "match against Hello, {}!" println :readf1<"Hello, {}!"> => &nop
|
||||
= "match against Hello, {}" println :readf1<"Hello, {}"> => &nop
|
||||
= "fallback" println pop "Unknown" 1
|
||||
} if {
|
||||
println
|
||||
}
|
||||
|
||||
|
||||
"" println;
|
||||
"testing sync (should be 5)" println;
|
||||
|
||||
def x 5 =x
|
||||
def sync Synchronizer:new =sync
|
||||
|
||||
10 :foreach<|
|
||||
fork<| 10 :foreach<| sync:do<| x ++ =x > > >
|
||||
fork<| 10 :foreach<| sync:do<| x -- =x > > >
|
||||
>
|
||||
|
||||
1000 time:sleep;
|
||||
|
||||
x println
|
||||
|
||||
"nonsynced for comparison:" println
|
||||
|
||||
def x 5 =x
|
||||
|
||||
10 :foreach<|
|
||||
fork<| 10 :foreach<| x ++ =x > >
|
||||
fork<| 10 :foreach<| x -- =x > >
|
||||
>
|
||||
|
||||
1000 time:sleep;
|
||||
|
||||
x println
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
1
test.txt
Normal file
1
test.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
hi
|
||||
Loading…
Add table
Reference in a new issue