Compare commits

...

46 commits
v0.4.0 ... main

Author SHA1 Message Date
d43a7ab384
bump version 2024-11-22 21:37:14 +01:00
4357a16523
add command-wait-silent 2024-11-22 13:16:09 +01:00
8c2b6724da
dont inherit stdio in command function 2024-11-22 13:07:34 +01:00
e55a619862
return PID from command function 2024-11-22 13:06:16 +01:00
f6faf029cd
add an overridable panic handler 2024-11-20 15:21:27 +01:00
dd02cc1fc4 better readme 2024-11-19 07:16:28 +01:00
6a4566c223
make equality less strange 2024-11-18 09:34:16 +01:00
78ec4e066f
make pure.spl's or behave like std 2024-11-16 00:42:52 +01:00
9e936277c7
reject nulls in match inputs entirely 2024-11-16 00:21:05 +01:00
7e674f0ad7
include StringyJSON in some types by default 2024-11-15 22:43:42 +01:00
200a9375ce
cannot match null 2024-11-15 22:28:25 +01:00
0c5dedb44e
implement maths for floats 2024-11-15 19:14:45 +01:00
13e32ed4f2
add array contains 2024-11-15 19:13:41 +01:00
4c09cfe929
fix readf1 2024-11-15 18:17:48 +01:00
700fb1e266
better control word checking for matches 2024-11-15 17:19:30 +01:00
901246abac
fix an http bug 2024-11-15 17:13:38 +01:00
92858f6baf
fix match causing stack garbage when array lengths mismatch 2024-11-15 15:31:31 +01:00
fd0aed81fd
fix json arrays 2024-11-15 15:19:59 +01:00
c33b25f260
add json.spl, fix messed up =>? operator 2024-11-15 15:01:20 +01:00
baa981c224
matching now even less awkward 2024-11-15 14:42:19 +01:00
c3e2dbc1b8
matching now less awkward 2024-11-15 14:34:23 +01:00
215b6f2748
matching 2024-11-15 11:54:54 +01:00
3445b27fa9
allow shutting down parts of streams 2024-11-05 14:31:42 +01:00
1b1855fe5a
fast.spl improvements 2024-10-29 21:12:10 +01:00
5c8875eb55
add fast.spl, improving repl 2024-10-23 15:52:47 +02:00
a8a2616a41
add mega iter, fix division 2024-10-23 13:50:04 +02:00
2bbeaebd7c
add escape fn to string, add some ways to write callables 2024-10-23 11:28:16 +02:00
35639cb2c8
repl.spl: fix typo 2024-10-22 13:01:26 +02:00
c45c538952
improve repl 2024-10-22 13:00:35 +02:00
c3fffb1e75
improve repl 2024-10-21 12:53:11 +02:00
290630adbe
add delete-dir, delete-file, list-files 2024-10-14 21:10:27 +02:00
e72baba154
httpserver/static.spl: add individual settings for bufsize and client-cache 2024-10-14 17:10:46 +02:00
210eaade3a
add cache for static http server 2024-10-14 16:39:38 +02:00
51212e139a
add a todo 2024-10-14 05:26:54 +02:00
95afcf9940
add linkedlist.spl to stdlib 2024-10-14 05:21:22 +02:00
a8b0a1bb53
make linked lists fully mutable 2024-10-14 05:15:09 +02:00
2e126a6652
fix read-to-end popping too much 2024-10-14 01:05:11 +02:00
fccce8b705
add settypeid 2024-10-13 23:56:57 +02:00
eb1335ade4
add gettypeid 2024-10-13 23:24:45 +02:00
ea8d3f5a6f
add micromap and list clear 2024-10-13 23:18:36 +02:00
c1475cc153
fix memory leaks, add bufsize option for httpserver/static.spl 2024-10-13 23:10:16 +02:00
456d39399e
add bytearray type for faster access to bytes (API identical to array type) 2024-10-13 21:52:06 +02:00
4749c142fa
httpserver/static.spl: write body as bytes when serving 'file' 2024-10-13 19:28:32 +02:00
c9463c6938
support \n line endings on http head 2024-10-13 19:09:41 +02:00
a9894d8a03 fix a performance issue 2024-10-11 23:14:46 +02:00
1c1f9a4566 add linked list 2024-10-05 15:16:56 +02:00
22 changed files with 1235 additions and 147 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "spl"
version = "0.4.0"
version = "0.4.1"
edition = "2021"
description = "Stack Pogramming Language: A simple, concise scripting language."
license = "MIT"

View file

@ -11,6 +11,9 @@ func main { mega | with args ;
&print swap:foreach
"" println
println<"and with that, we're done">
[ ^hello ^pattern-matching ] => [ ^hello &=args ] if {
args println "prints pattern-matching";
}
0
}
```
@ -262,3 +265,42 @@ As you can see, it's relatively straight-forward to do; but there are some major
The second one is easy to fix, but I intend to fix the first one first. Sadly, fixing it requires
compiling the code as a dynamic library and also getting it to work with the program its running in.
If anyone knows how to do this properly, I'd REALLY appreciate a PR or issue explaining it.
## Syntactic sugar
There's a lot of things that look kinda tedious to do in SPL, syntactically speaking. So over time,
I regularly add syntactic sugar:
- `a => b` -> `a b match`
- `a =>? b` -> `a dup b match _'match-else-push` (match that will push the offending value on error)
- `a =>! b` -> `a b match _'match-else-error` (match that throws an error when unable to match)
- `a<| b c d e>` -> `a<{ | b c d e }>` -> `{ | b c d e } a`
- `def a, b, c` -> `def a def b def c`
## Matching
The match function `match` and its accompanying sugar take in two values and essentially do a special
compare for equality on them. Unlike everything else, which is just compared normally, arrays are
iterated through and their items checked individually, recursively. When a callable (`{ ... | ... }`)
is found in b, any value in a is accepted for it, and the function is called *if and only if* every
other part of the match also succeeds.
The function returns 1 on success, otherwise 0.
Example:
```js
def a, val
[ ^ok "hey matcher" ] =a
a =>! [ ^ok &=val ]
val println
a =>? [ ^ok &=val ] not if {
"error: " swap concat panic
}
val println
a => [ ^ok &=val ] not if {
"error" panic
}
val println
```

89
spl/fast.spl Normal file
View file

@ -0,0 +1,89 @@
"#stream.spl" import
"#http.spl" import
func ls { files | "." list-files }
func ls@ { files | list-files }
func cd { | chdir }
func cat { | read-file }
func output { | with thing ;
thing gettype "array" eq
thing gettype:ends-with<^Iter> or
dup if {
thing:foreach<&println>
}
not if {
thing _str println
}
}
func *m { | swap :ends-with }
func m* { | swap :starts-with }
func # { | swap :filter }
func ? { | swap :map }
func times { | :iter }
func \ { | pop }
func ~@ { | env:get<"HOME"> "/" concat swap concat }
func ~ { | env:get<"HOME"> }
func $ { v | env:get }
func . { | output }
func ex { | with s ; [ ^sh ^-c s ] command-wait; }
func ex% { | command-wait; }
func ex. { | with s ; [ ^sh ^-c s ] StreamTypes:cmd:create:read-to-end<1024 16 *> }
func ex%. { | StreamTypes:cmd:create:read-to-end<1024 16 *> }
func into { | with input file ;
input gettype any<[ { | "array" eq } { | "bytearray" eq } ]> not if { input _str:to-bytes =input }
file 1 StreamTypes:file:create
dup :write-exact;<input>
:close;
}
func tcp { stream |
StreamTypes:tcp:create
}
func curl { s | bcurl:to-str }
func bcurl { bytes | with url ;
1 if {
url:readf<"{}:{}/{}"> dup if {
=url
url:0 url:1 _int "GET" "/" url:2 concat net:http:Request:new
2 stop
} pop
url:readf<"{}:{}"> dup if {
=url
url:0 url:1 _int "GET" "/" net:http:Request:new
2 stop
} pop
url:readf<"{}/{}"> dup if {
=url
url:0 80 "GET" "/" url:1 concat net:http:Request:new
2 stop
} pop
url:readf<"{}"> dup if {
=url
url:0 80 "GET" "/" net:http:Request:new
2 stop
} pop
"invalid url" panic
} :send:body
}
construct _shell_array_ext {
idx
;
next { any | with this ;
this:idx 0 or dup ++ this:=idx this:sget
}
}
include _shell_array_ext in array
include _Iter in array
func any { bool | with o checks ;
0 checks:foreach<{ | o swap call if { pop 1 } }>
}

View file

@ -42,26 +42,34 @@ construct net:http:server:Request {
construct { this | with server stream this ;
server this:=server
stream this:=stream
0 anew this:=head
0 anew this:=body
0 banew this:=head
0 banew this:=body
this
}
read-head { this | with this ;
def read
def buf 1024 anew =buf
def buf 1024 banew =buf
def found
while {
buf this:stream:read pop =read
"\r\n\r\n" :to-bytes buf:find dup =found not read and
buf this:stream:read pop dup =read
"\r\n\r\n" :to-bytes buf:find dup =found null eq and
} {
this:head buf:sub<0 read> aadd this:=head
this:head buf:sub<0 read>:replace<"\r" :to-bytes 0 banew> aadd this:=head
"\n\n" :to-bytes this:head:find dup null eq not if {
=found
this:head:sub<0 found>:to-str this:=head
1 ( buf:0 "\r" eq if { pop 2 } ) buf:len buf:sub =buf
buf this:=body
this
4 stop
0 } pop
}
this:head buf:sub<0 found> aadd:to-str this:=head
this:head buf:sub<0 found>:replace<"\r" :to-bytes 0 banew> aadd:to-str this:=head
buf:sub<found 4 + buf:len> this:=body
this
}
parse-head { this | with this ;
this:head:split<"\r\n"> this:=head
this:head:split<"\n"> this:=head
def iter this:head:iter =iter
iter:next:readf<"{} {} HTTP/{}"> dup if {
dup:to-stack this:=version this:=raw-path this:=method
@ -100,7 +108,7 @@ construct net:http:server:Request {
read-body { this | with this ;
this:headers:get<"content-length"> dup if { _mega with content-length ;
def read
def buf 1024 anew =buf
def buf 1024 banew =buf
while {
this:body:len content-length lt read and
} {
@ -146,7 +154,7 @@ construct net:http:server:Request {
}
finish { | with this ;
this:wrote-body not if {
0 anew this:write-body;
0 banew this:write-body;
}
this:stream:close;
}

View file

@ -1,9 +1,15 @@
"base.spl" import
"../stream.spl" import
"_static_ext_server" net:http:server:register
"_static_ext_Request" net:http:server:register
"bufsize" net:http:server:register
"client-cache" net:http:server:register
1024 net:http:server:=bufsize
construct net:http:server:_static_ext_server {
bufsize
client-cache
cached-files
;
get-cached-files { cached-files | with this ;
@ -13,18 +19,25 @@ construct net:http:server:_static_ext_server {
construct net:http:server:_static_ext_Request {
;
get-bufsize { bufsize | with this ;
this:bufsize net:http:server:bufsize or
}
get-client-cache { client-cache | with this ;
this:client-cache net:http:server:client-cache or
}
serve-file { this | with filepath path type this ;
this:path path eq if {
filepath read-file this:write-ok:write-content-type<type>:write-str-body:finish;
filepath StreamTypes:file:create<0>:read-to-end<this:get-bufsize> this:write-ok:write-content-type<type>:write-body:finish;
}
this
}
serve-file-cached { this | with filepath path type this ;
this:path path eq if {
filepath this:server:get-cached-files:get dup not if {
pop filepath read-file dup this:server:cached-files:set;<filepath>
pop filepath StreamTypes:file:create<0>:read-to-end<this:get-bufsize> dup this:server:cached-files:set;<filepath>
}
this:write-ok:write-content-type<type>:write-str-body:finish;
def cache this:get-client-cache =cache
this:write-ok cache if { :write-header<"Cache-Control" "public, max-age=" cache _str concat> } :write-content-type<type>:write-body:finish;
}
this
}

View file

@ -13,12 +13,12 @@ construct _Iter {
foreach { | with callable this ;
!!-
while { !!- this:next dup null eq not } {
!!- callable call
!!- callable callp
}
pop
}
collect { array | with this ;
[ { any | } this:foreach ]
[ { | } this:foreach ]
}
map { MapIter | with map-function this ;
map-function this MapIter:new

49
spl/json.spl Normal file
View file

@ -0,0 +1,49 @@
construct json namespace {
_StringyJSON
;
props-to-sjson { s | with spaces props this ;
def value, comma
"," spaces if { " " concat } =comma
props:last:get<1> =value
value null eq not if {
value gettype "array" eq if {
"[" value
:iter
:map<| 0 swap properties this:props-to-sjson>
:join<comma> concat
"]" concat
3 stop
}
"\""
value _str :replace<"\\" "\\\\">:replace<"\"" "\\\""> concat
"\"" concat
2 stop
}
"{"
props
:iter
:filter<{ b | :to-stack pop with key ; key ":" eq not key ";" eq not and }>
:map<{ s |
:to-stack with key value ;
"\""
key :replace<"\\" "\\\\">:replace<"\"" "\\\""> concat
"\":" concat spaces if { " " concat }
spaces value properties this:props-to-sjson concat
}>
:join<comma> concat
"}" concat
}
}
construct json:_StringyJSON {
;
sjson { s | with spaces this ;
spaces this properties json:props-to-sjson
}
}
include json:_StringyJSON in array
include json:_StringyJSON in str

134
spl/linkedlist.spl Normal file
View file

@ -0,0 +1,134 @@
construct LinkedList {
next
value
;
construct { this | }
len { mega | with this ;
def i this:value null eq not =i
while { this:next null eq not } {
this:next =this
i ++ =i
}
i
}
push { | with item this ;
item:unwrap;
this:value null eq if {
item this:=value;
2 stop
}
LinkedList:new
dup :=value;<item>
this:last-entry:=next;
}
last-entry { list | with this ;
while { this:next null eq not } {
this:next =this
}
this
}
peek { value | with this ;
this:last-entry:value
}
pop { item | with this ;
def item this =item
null
while { item:next null eq not } {
pop item
item:next =item
}
"if theres no next item in this";
dup null eq if {
pop
this:value (null this:=value;) 2 stop
}
:=next;<null>
item:value
}
push-front { | with item this ;
item:unwrap;
this:value null eq if {
item this:=value;
2 stop
}
"append new next identical to this, then make this contain new value";
def new LinkedList:new =new
this:next new:=next;
this:value new:=value;
item this:=value;
new this:=next;
}
insert { | with item index this ;
item:unwrap;
index 0 eq if {
item this:push-front 2 stop
}
def list this =list
while { index 1 gt } {
list:next =list
index -- =index
}
item list:next:push-front
}
pop-front { item | with this ;
this:value
this:next null eq dup if {
null this:=value
}
not if {
this:next:value this:=value
this:next:next this:=next
}
}
remove { item | with index this ;
index 0 eq if {
this:pop-front 2 stop
}
def list this =list
while { index 1 gt } {
list:next =list
index -- =index
}
list:next:pop-front
}
get { item | with index this ;
while { index 0 gt } {
this:next =this
index -- =index
}
this:value
}
set { item | with value index this ;
while { index 0 gt } {
this:next =this
index -- =index
}
this:value value this:=value
}
foreach { | with callable this ;
while { this null eq not } {
this:value dup null eq not if { callable:call; }
this:next =this
}
}
iter { LinkedListIter | with this ;
this LinkedListIter:new
}
}
construct LinkedListIter {
cur-list
;
construct { this | with list this ;
list this:=cur-list
this
}
next { item | with this ;
this:cur-list dup null eq if { 2 stop }
:value
this:cur-list:next this:=cur-list;
}
}
include _Iter in LinkedListIter

View file

@ -13,7 +13,7 @@ func swap { b a | with a b ;
}
func not { !x | with x ;
1
1
x if { pop 0 }
}
@ -23,9 +23,8 @@ func and { a&&b | with a b ;
}
func or { a||b | with a b ;
0
a if { pop 1 }
b if { pop 1 }
b
a if { pop a }
}
func alit-end { array | with type ;

View file

@ -1,12 +1,31 @@
"fast.spl" import
func main { mega | with args ;
"Welcome to the SPL REPL!" println
"Enter any code after the cursor to execute it.\n" println
"Enter any code after the cursor to execute it." println
"fast.spl (for shell-like functions) is included.\n" println
"REPL" =program-name
while { 1 } {
catch {
" > " print readln dyn-read exec2 "\n" print
def line "" =line
while { line repl-is-complete not } {
def s
line " > " print readln =s
s "\n" concat concat =line
}
line _barray =line
line:sub<0 line:len 1 -> _str =line
"!!-end" line:contains if {
2 stop
}
"\n" line:contains if {
"" println
line println
"." println
}
line dyn-read exec2 "\n" print
}
{ with err ;
err:message dup null eq if {
@ -27,3 +46,25 @@ func main { mega | with args ;
}
}
}
func repl-is-complete { bool | with line ;
"!!-end" line:contains
0 line _array :foreach<{ | with char ;
char "{" :_char eq
char "(" :_char eq or
char "<" :_char eq or
char "[" :_char eq or
if {
++
}
char "}" :_char eq
char ")" :_char eq or
char ">" :_char eq or
char "]" :_char eq or
if {
--
}
}> not
or
"" line eq not and
}

View file

@ -42,11 +42,20 @@ construct _str_ext {
replacee:to-bytes replacement:to-bytes this:to-bytes:replace:to-str
}
find { idx | with search this ;
search _array this _array :find
search _barray this _barray :find
}
contains { bool | with search this ;
search this:find null eq not
}
starts-with { bool | with beginning this ;
beginning:to-bytes this:to-bytes:starts-with
}
ends-with { bool | with ending this ;
ending:to-bytes this:to-bytes:ends-with
}
escape { formatted | with this ;
"\"" this:replace<"\\" "\\\\">:replace<"\"" "\\\"">:replace<"\n" "\\n">:replace<"\r" "\\r"> concat "\"" concat
}
_char { int | with this ;
0 (this _array):get
}
@ -54,7 +63,7 @@ construct _str_ext {
!!- pat this str-readf
}
readf1 { str | with pat this ;
!!- pat this:readf 0:get
!!- pat this:readf dup null eq not if { :0 }
}
uppercase { str | with this ;
this _array :cmap<{ | with chr ;
@ -89,15 +98,27 @@ construct _mega-ext {
while { !!- i this lt } { !!- i callable call i 1 + =i }
}
fforeach { | with callable this ;
0 while { !!- dup2 this lt } { !!- callable call 1 + }
0 while { !!- dup2 this lt } { !!- callable callp 1 + }
}
_str_radix { str | with radix this ;
this radix mega-to-str-radix
}
iter { MegaIter | with this ;
this MegaIter:new
}
} include _mega-ext in mega
construct _array-ext {
;
another { array | with this ;
this gettype =this
this "array" eq if {
anew
}
this "bytearray" eq if {
banew
}
}
get { any | array-get }
sget { any|null | with idx this ;
idx this:len lt idx -1 gt and dup if {
@ -135,7 +156,7 @@ construct _array-ext {
}
to-str { str | bytes-to-str }
sub { [any] | with begin end this ;
this (end begin - anew) begin 0 (end begin -) acopy
this (end begin - this:another) begin 0 (end begin -) acopy
}
split { arr | with splitter this ;
def i 0 =i
@ -197,6 +218,9 @@ construct _array-ext {
}
null
}
contains { bool | with search this ;
search awrap this:find null eq not
}
starts-with { bool | with beginning this ;
this:len beginning:len lt if {
0
@ -204,6 +228,13 @@ construct _array-ext {
}
0 beginning:len this:sub beginning eq
}
ends-with { bool | with ending this ;
this:len ending:len lt if {
0
2 stop
}
this:len ending:len - this:len this:sub ending eq
}
last { | with this ;
this:len -- this:get
}
@ -240,7 +271,9 @@ construct _array-ext {
=4 { | with this ;
4 this:set;
}
} include _array-ext in array
}
include _array-ext in array
include _array-ext in bytearray
construct _func-ext {
args
@ -288,6 +321,7 @@ construct List {
to-stack { .. | :array:to-stack }
to-str { str | :array:to-str }
sub { [any] | :array:sub }
clear { | :=array<0 anew> }
}
construct _GrowingArray {
;
@ -345,7 +379,7 @@ construct ArrayIter {
construct _IterableArray {
;
iter { ArrayIter | with this ;
this gettype "array" eq dup if {
this gettype:ends-with<"array"> dup if {
pop
this ArrayIter:new
2 stop
@ -357,6 +391,7 @@ construct _IterableArray {
include _Iter in ArrayIter
include _IterableArray in List
include _IterableArray in array
include _IterableArray in bytearray
construct MicroMap {
pairs
@ -401,6 +436,20 @@ construct MicroMap {
foreach { | with callable this ;
callable this:pairs:foreach
}
clear { | with this ;
this:pairs:clear;
}
to-str { str | with this ;
"{ "
{ | with item ;
"'" concat
0 item:get dup null eq if { "key is null" panic } _str concat
"': '" concat
1 item:get dup null eq if { "value is null" panic } _str concat
"', " concat
} this:foreach
"}" concat
}
}
construct Range {
@ -455,11 +504,27 @@ construct RangeIter {
include _Iter in RangeIter
construct MegaIter {
i d
;
construct { this | with d this ;
d this:=d
0 this:=i
this
}
next { i | with this ;
this:i dup ++ this:=i
dup this:d lt not if { pop null }
}
}
include _Iter in MegaIter
construct shadow { }
func aadd { array | with arr1 arr2 ;
def newarr arr1:len arr2:len + anew =newarr
def newarr arr1:len arr2:len + arr1:another =newarr
arr1 newarr 0 0 arr1:len acopy;
arr2 newarr 0 arr1:len arr2:len acopy;
@ -494,6 +559,8 @@ func cache { ... | with arg-amt id body ;
result
}
func panic-handler { | "to be overridden"; }
def do-not-dump 0 =do-not-dump
func handle-panic { | with msg trace ;
program-name dup if {
@ -504,6 +571,7 @@ func handle-panic { | with msg trace ;
&println trace:foreach
"\nPanic message:" println
" " print msg println
panic-handler
def map env =map
"SPL_PANIC_DUMP" env:get dup if {
"Dumping because SPL_PANIC_DUMP is set." println
@ -582,6 +650,90 @@ func times { | with amount callable ;
}
}
func check-match { bool | with input output ;
input null eq if {
0 2 stop
}
output gettype "func" eq if {
1 2 stop
}
input output eq if {
1 2 stop
}
output gettype "array" eq if {
def i, n
0 =i
output:len =n
input:len n eq not if { 0 3 stop }
1 while { i n lt } {
input:get<i> output:get<i> check-match and
i ++ =i
}
2 stop
}
0
}
func match-unchecked { | with input output ;
output gettype "func" eq if {
input output call
2 stop
}
output gettype "array" eq if {
def i, n
0 =i
output:len =n
input:len n eq not if { 3 stop }
while { i n lt } {
input:get<i> output:get<i> match-unchecked
i ++ =i
}
}
}
func match { bool | with input output ;
input null eq if {
0 2 stop
}
output gettype "func" eq if {
input output call
1 2 stop
}
input output eq if {
1 2 stop
}
output gettype "array" eq if {
def i, n
0 =i
output:len =n
input:len n eq not if { 0 3 stop }
1 while { i n lt } {
input:get<i> output:get<i> check-match and
i ++ =i
} dup if {
0 =i
while { i n lt } {
input:get<i> output:get<i> match-unchecked
i ++ =i
}
}
2 stop
}
0
}
func _'match-else-error { |
not if {
"match unsuccessful" throw
}
}
func _'match-else-push { |
dup if {
swap pop
}
}
def _'has-been-called 0 =_'has-been-called
func _ { |
_'has-been-called not if {

View file

@ -15,28 +15,26 @@ construct Stream {
this
}
read-one { mega | with this ;
def buf 1 anew =buf
def buf 1 banew =buf
while { buf this:id read-stream pop not } { }
0 buf:get _mega
}
"the buffer is written to in-place.";
read { mega [int] | with buf this ;
buf gettype "mega" eq if { buf anew =buf }
buf gettype "mega" eq if { buf banew =buf }
buf this:id read-stream
}
"the buffer is written to in-place.";
read-exact { [int] | with buf this ;
buf gettype "mega" eq if { buf anew =buf }
buf gettype "mega" eq if { buf banew =buf }
buf this:id read-all-stream buf
}
read-to-end { [int] | with buf this ;
def full 0 anew =full
buf gettype "mega" eq if { buf anew =buf }
buf gettype "mega" eq if { buf banew =buf }
def read
while { buf this:id read-stream pop _mega dup =read } {
full (0 read buf:sub) aadd =full
} pop
full
0 banew while { buf this:id read-stream pop _mega dup =read } {
(0 read buf:sub) aadd
}
}
write { mega | with buf this ;
buf this:id write-stream
@ -47,6 +45,9 @@ construct Stream {
flush { | with this ;
this:id flush-stream
}
shutdown-input { | with this ;
this:id shutdown-input-stream
}
close { | with this ;
this:id close-stream
}

View file

@ -78,6 +78,10 @@ fn read_block_dyn(
);
}
"def" => {
while let Some(w) = str_words[i + 1].strip_suffix(',') {
words.push(Word::Key(Keyword::Def(w.to_owned())));
i += 1;
}
words.push(Word::Key(Keyword::Def(str_words[i + 1].to_owned())));
i += 1;
}
@ -121,7 +125,20 @@ fn read_block_dyn(
run_as_base: false,
}))))
}
x if x.len() >= 2 && &x[0..2] == "!{" => {
// <| .. > lambda
"|" => {
let block = read_block_dyn(&str_words[i + 1..], false, ">".to_owned(), compat)?;
i += block.2;
words.push(Word::Const(Value::Func(AFunc::new(Func {
ret_count: 0,
to_call: FuncImpl::SPL(block.1),
origin: Arc::new(Frame::dummy()),
fname: None,
name: "dyn-arg".to_owned(),
run_as_base: false,
}))))
}
x if x.len() >= 2 && x.get(0..2) == Some("!{") => {
words.push(Word::Const(Value::Str(x[2..].to_owned())));
}
"<" => {
@ -240,6 +257,54 @@ fn read_block_dyn(
}
words.push(Word::Key(Keyword::With(vars)));
}
"=" => {
if str_words[i + 1] == ">" {
i += 1;
let cword = &str_words[i + 1];
if cword.contains(|c| c == '?' || c == '!')
&& !cword.contains(|c: char| c == '^' || !c.is_ascii_punctuation())
{
i += 1;
}
let pushing = if cword.contains('?') {
words.push(Word::Call("dup".to_owned(), false, 0));
true
} else {
false
};
let throwing = cword.contains('!');
if str_words[i + 1] == "[" {
i += 1;
let mut 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);
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,
);
i += 1;
}
words.push(Word::Call("match".to_owned(), false, 0));
if throwing {
words.push(Word::Call("_'match-else-error".to_owned(), false, 0));
}
if pushing {
words.push(Word::Call("_'match-else-push".to_owned(), false, 0));
}
} else {
words.push(Word::Call("=".to_owned(), false, 0));
}
}
"inline-callable" => {
words.push(Word::Key(Keyword::InlineCallable));
}
@ -255,6 +320,9 @@ fn read_block_dyn(
x if x.starts_with('\"') => {
words.push(Word::Const(Value::Str(x[1..].to_owned())));
}
x if x.starts_with('^') => {
words.push(Word::Const(Value::Str(x[1..].to_owned())));
}
x if x.chars().all(|c| c.is_numeric() || c == '_' || c == '-')
&& !x.starts_with('_')
&& x.contains(char::is_numeric) =>

View file

@ -101,6 +101,24 @@ 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 {
return $stack.err(ErrorKind::InvalidCall($fn.to_owned()))
};
};
}
#[macro_export]
macro_rules! require_mut {
($name:tt, $type:tt, $binding:expr, $stack:expr, $fn:literal) => {
let Value::$type(ref mut $name) = $binding.lock().native else {
return $stack.err(ErrorKind::InvalidCall($fn.to_owned()))
};
};
}
#[macro_export]
macro_rules! require_int_on_stack {
($name:tt, $stack:expr, $fn:literal) => {
@ -133,6 +151,31 @@ nofmt! {
};
}
#[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 {
Value::Array(ref x) => x
.iter()
.cloned()
.map(|x| {
Ok(match &x.lock_ro().native {
Value::Int(x) => *x as u8,
Value::Long(x) => *x as u8,
Value::Mega(x) => *x as u8,
_ => $stack.err(ErrorKind::InvalidType(
x.lock_ro().kind.lock_ro().get_name(),
"byte".to_owned(),
))?,
})
})
.collect::<Result<Vec<_>, _>>()?,
Value::ByteArray(ref x) => x.clone(),
_ => return $stack.err(ErrorKind::InvalidCall($fn.to_owned())),
};
};
}
#[macro_export]
macro_rules! require_mut_array_on_stack {
($name:tt, $stack:expr, $fn:literal) => {
let binding = $stack.pop();

View file

@ -115,6 +115,7 @@ impl Runtime {
let _ = rt.make_type("func".to_owned(), Ok); // infallible
let _ = rt.make_type("array".to_owned(), Ok); // infallible
let _ = rt.make_type("str".to_owned(), Ok); // infallible
let _ = rt.make_type("bytearray".to_owned(), Ok); // infallible
stdlib::register(&mut rt);
rt
}
@ -473,6 +474,9 @@ impl Stack {
}
pub fn call(&mut self, func: &AFunc) -> OError {
if func.origin.is_dummy() {
return self.fast_call(func);
}
let f = if let Some(ref cname) = func.fname {
Frame::new_in(
func.origin.clone(),
@ -515,14 +519,13 @@ impl Stack {
frame = self.frames.first().unwrap().clone();
}
let tmpname = name.clone();
let tmpframe = frame.clone();
frame.functions.lock().insert(
name.clone(),
Arc::new(Func {
ret_count: 1,
origin: frame.clone(),
origin: Arc::new(Frame::dummy()),
to_call: FuncImpl::NativeDyn(Arc::new(Box::new(move |stack| {
stack.push(tmpframe.get_var(tmpname.clone(), stack)?);
stack.push(stack.get_var(tmpname.clone())?);
Ok(())
}))),
run_as_base: false,
@ -531,15 +534,14 @@ impl Stack {
}),
);
let tmpname = name.clone();
let tmpframe = frame.clone();
frame.functions.lock().insert(
"=".to_owned() + &name,
Arc::new(Func {
ret_count: 0,
origin: frame.clone(),
origin: Arc::new(Frame::dummy()),
to_call: FuncImpl::NativeDyn(Arc::new(Box::new(move |stack| {
let v = stack.pop();
tmpframe.set_var(tmpname.clone(), v, stack)
stack.set_var(tmpname.clone(), v)
}))),
run_as_base: false,
fname: Some("RUNTIME".to_owned()),
@ -568,11 +570,33 @@ impl Stack {
}
pub fn set_var(&self, name: String, obj: AMObject) -> OError {
self.get_frame().set_var(name, obj, self)
if let Err(x) = self.get_frame().set_var(name.clone(), obj.clone(), self) {
for i in 1..self.frames.len() {
if self
.peek_frame(i)
.set_var(name.clone(), obj.clone(), self)
.is_ok()
{
return Ok(());
}
}
return Err(x);
}
Ok(())
}
pub fn get_var(&self, name: String) -> Result<AMObject, Error> {
self.get_frame().get_var(name, self)
match self.get_frame().get_var(name.clone(), self) {
Err(x) => {
for i in 1..self.frames.len() {
if let Ok(x) = self.peek_frame(i).get_var(name.clone(), self) {
return Ok(x);
}
}
Err(x)
}
Ok(x) => Ok(x),
}
}
pub fn push(&mut self, obj: AMObject) {
@ -771,6 +795,7 @@ pub enum Value {
Double(f64),
Func(AFunc),
Array(Vec<AMObject>),
ByteArray(Vec<u8>),
Str(String),
}
@ -1058,14 +1083,15 @@ impl Object {
pub fn is_truthy(&self) -> bool {
match &self.native {
Value::Null => self.kind.lock_ro().id != 0,
Value::Int(x) => x > &0,
Value::Long(x) => x > &0,
Value::Mega(x) => x > &0,
Value::Float(_) => true,
Value::Double(_) => true,
Value::Int(x) => *x > 0,
Value::Long(x) => *x > 0,
Value::Mega(x) => *x > 0,
Value::Float(x) => x.is_finite(),
Value::Double(x) => x.is_finite(),
Value::Func(_) => true,
Value::Array(_) => true,
Value::Str(x) => !x.is_empty(),
Value::ByteArray(_) => true,
}
}
@ -1125,6 +1151,7 @@ impl From<Value> for Object {
Value::Func(_) => x.get_type_by_id(6),
Value::Array(_) => x.get_type_by_id(7),
Value::Str(_) => x.get_type_by_id(8),
Value::ByteArray(_) => x.get_type_by_id(9),
}
.expect("runtime uninitialized: default types not set.")
}),

View file

@ -175,6 +175,25 @@ fn sasm_parse<'a>(line: &str, words: &mut Vec<Word>, lines: &mut impl Iterator<I
})))),
"null" => words.push(Word::Const(Value::Null)),
"array" => panic!("invalid sasm const: array - not all Values can be consts!"),
"bytearray" => {
let mut array = Vec::new();
let inp = line[2].chars().collect::<Vec<_>>();
fn v(c: char) -> u8 {
if c > '0' && c <= '9' {
c as u8 - '0' as u8
} else if c > 'a' && c <= 'f' {
c as u8 - 'a' as u8
} else {
panic!("invalid sasm const: const bytearray [nonbytearray]")
}
}
for i in (0..inp.len() / 2).map(|x| x * 2) {
let a = inp[i];
let b = inp[i + 1];
array.push(v(a) * 0x10 + v(b));
}
words.push(Word::Const(Value::ByteArray(array)));
}
_ => panic!("invalid sasm const: {}", line[1]),
},
"call" => {
@ -356,6 +375,25 @@ fn sasm_write_func(words: Words) -> String {
text.replace("\0", "\0\x01").replace("\n", "\0\0")
);
}
Value::ByteArray(b) => {
fn c(v: u8) -> char {
if v > 16 {
unreachable!();
}
(if v < 10 {
'0' as u8 + v
} else {
'a' as u8 + v - 10
}) as char
}
let mut out = String::with_capacity(b.len() * 2);
for b in b {
out.push(c(b / 0x10));
out.push(c(b % 0x10));
}
output += &format!("const bytearray {out}\n");
}
},
Word::Call(name, rem, ra) => {
output += "call ";

View file

@ -1,9 +1,8 @@
use std::{
collections::VecDeque,
env::{args, vars},
collections::{HashMap, VecDeque},
env::{self, args, vars},
fs,
io::{stdin, stdout, Write},
mem,
ops::{Add, Div, Mul, Rem, Sub},
process::{self, Stdio},
sync::Arc,
@ -96,12 +95,27 @@ pub fn settype(stack: &mut Stack) -> OError {
let o = stack.pop();
let kind = runtime(|rt| rt.get_type_by_name(&s))
.ok_or_else(|| stack.error(ErrorKind::TypeNotFound(s)))?;
set_type_internal(&o, kind);
stack.push(o);
Ok(())
}
pub fn settypeid(stack: &mut Stack) -> OError {
let Value::Int(i) = stack.pop().lock_ro().native.clone() else {
return stack.err(ErrorKind::InvalidCall("settype".to_owned()));
};
let o = stack.pop();
let kind = runtime(|rt| rt.get_type_by_id(i as u32))
.ok_or_else(|| stack.error(ErrorKind::TypeNotFound(format!(";{i}"))))?;
set_type_internal(&o, kind);
stack.push(o);
Ok(())
}
fn set_type_internal(o: &Arc<Mut<Object>>, kind: Arc<Mut<Type>>) {
let mut obj = o.lock();
kind.lock_ro().write_into(&mut obj);
obj.kind = kind;
mem::drop(obj);
stack.push(o);
Ok(())
}
pub fn gettype(stack: &mut Stack) -> OError {
@ -110,6 +124,23 @@ pub fn gettype(stack: &mut Stack) -> OError {
Ok(())
}
pub fn gettypeid(stack: &mut Stack) -> OError {
let o = stack.pop();
stack.push(Value::Int(o.lock_ro().kind.lock_ro().get_id() as i32).spl());
Ok(())
}
pub fn barray_new(stack: &mut Stack) -> OError {
let Value::Mega(i) = stack.pop().lock_ro().native.clone() else {
return stack.err(ErrorKind::InvalidCall("banew".to_owned()));
};
if i < 0 {
return stack.err(ErrorKind::InvalidCall("banew".to_owned()));
}
stack.push(Value::ByteArray(vec![0u8; i as usize]).spl());
Ok(())
}
pub fn array_new(stack: &mut Stack) -> OError {
let Value::Mega(i) = stack.pop().lock_ro().native.clone() else {
return stack.err(ErrorKind::InvalidCall("anew".to_owned()));
@ -123,36 +154,46 @@ pub fn array_new(stack: &mut Stack) -> OError {
pub fn array_len(stack: &mut Stack) -> OError {
let binding = stack.pop();
let Value::Array(ref a) = binding.lock_ro().native else {
return stack.err(ErrorKind::InvalidCall("array-len".to_owned()));
let len = match binding.lock_ro().native {
Value::Array(ref a) => a.len(),
Value::ByteArray(ref a) => a.len(),
_ => return stack.err(ErrorKind::InvalidCall("array-len".to_owned())),
};
stack.push(Value::Mega(a.len() as i128).spl());
stack.push(Value::Mega(len as i128).spl());
Ok(())
}
pub fn array_get(stack: &mut Stack) -> OError {
let binding = stack.pop();
let Value::Array(ref a) = binding.lock_ro().native else {
let Value::Mega(i) = stack.pop().lock_ro().native else {
return stack.err(ErrorKind::InvalidCall("array-get".to_owned()));
};
let Value::Mega(i) = stack.pop().lock_ro().native.clone() else {
return stack.err(ErrorKind::InvalidCall("array-get".to_owned()));
let o = match binding.lock_ro().native {
Value::Array(ref a) => a.get(i as usize).cloned(),
Value::ByteArray(ref a) => a.get(i as usize).map(|x| Value::Int(*x as i32).spl()),
_ => return stack.err(ErrorKind::InvalidCall("array-get".to_owned())),
};
stack.push(a.get(i as usize).ok_or_else(array!(stack, i))?.clone());
stack.push(o.ok_or_else(array!(stack, i))?);
Ok(())
}
pub fn array_set(stack: &mut Stack) -> OError {
let binding = stack.pop();
let Value::Array(ref mut a) = binding.lock().native else {
return stack.err(ErrorKind::InvalidCall("array-set".to_owned()));
};
let Value::Mega(i) = stack.pop().lock_ro().native.clone() else {
return stack.err(ErrorKind::InvalidCall("array-set".to_owned()));
};
let binding = &stack.pop();
let binding = &mut binding.lock().native;
require_on_stack!(i, Mega, stack, "array-set");
let o = stack.pop();
stack.push(a.get(i as usize).ok_or_else(array!(stack, i))?.clone());
*a.get_mut(i as usize).ok_or_else(array!(stack, i))? = o;
if let Value::Array(ref mut a) = binding {
stack.push(a.get(i as usize).ok_or_else(array!(stack, i))?.clone());
*a.get_mut(i as usize).ok_or_else(array!(stack, i))? = o;
} else if let Value::ByteArray(ref mut a) = binding {
let Value::Int(o) = o.lock_ro().native else {
return stack.err(ErrorKind::InvalidCall("array-set".to_owned()));
};
stack.push(Value::Int(*a.get(i as usize).ok_or_else(array!(stack, i))? as i32).spl());
*a.get_mut(i as usize).ok_or_else(array!(stack, i))? = o as u8;
} else {
return stack.err(ErrorKind::InvalidCall("array-set".to_owned()));
};
Ok(())
}
@ -227,6 +268,8 @@ pub fn plus(stack: &mut Stack) -> OError {
Mega,
Long,
Int,
Float,
Double,
)
.spl(),
);
@ -245,6 +288,8 @@ pub fn minus(stack: &mut Stack) -> OError {
Mega,
Long,
Int,
Float,
Double,
)
.spl(),
);
@ -263,6 +308,8 @@ pub fn slash(stack: &mut Stack) -> OError {
Mega,
Long,
Int,
Float,
Double,
)
.spl(),
);
@ -281,6 +328,8 @@ pub fn star(stack: &mut Stack) -> OError {
Mega,
Long,
Int,
Float,
Double,
)
.spl(),
);
@ -295,7 +344,7 @@ pub fn percent(stack: &mut Stack) -> OError {
a,
b,
rem,
stack.err(ErrorKind::InvalidCall("star".to_owned())),
stack.err(ErrorKind::InvalidCall("percent".to_owned())),
Mega,
Long,
Int,
@ -320,6 +369,7 @@ pub fn to_int(stack: &mut Stack) -> OError {
Value::Str(x) => x
.parse()
.map_err(|_| stack.error(ErrorKind::Parse(x, "int".to_owned())))?,
Value::ByteArray(x) => x.len() as i32,
})
.spl(),
);
@ -341,6 +391,7 @@ pub fn to_long(stack: &mut Stack) -> OError {
Value::Str(x) => x
.parse()
.map_err(|_| stack.error(ErrorKind::Parse(x, "long".to_owned())))?,
Value::ByteArray(x) => x.len() as i64,
})
.spl(),
);
@ -362,6 +413,7 @@ pub fn to_mega(stack: &mut Stack) -> OError {
Value::Str(x) => x
.parse()
.map_err(|_| stack.error(ErrorKind::Parse(x, "mega".to_owned())))?,
Value::ByteArray(x) => x.len() as i128,
})
.spl(),
);
@ -383,6 +435,7 @@ pub fn to_float(stack: &mut Stack) -> OError {
Value::Str(x) => x
.parse()
.map_err(|_| stack.error(ErrorKind::Parse(x, "float".to_owned())))?,
Value::ByteArray(_) => type_err!(stack, "bytearray", "float"),
})
.spl(),
);
@ -404,6 +457,7 @@ pub fn to_double(stack: &mut Stack) -> OError {
Value::Str(x) => x
.parse()
.map_err(|_| stack.error(ErrorKind::Parse(x, "double".to_owned())))?,
Value::ByteArray(_) => type_err!(stack, "bytearray", "double"),
})
.spl(),
);
@ -426,6 +480,11 @@ pub fn to_array(stack: &mut Stack) -> OError {
.chars()
.map(|x| Value::Int(x as u32 as i32).spl())
.collect(),
Value::ByteArray(x) => x
.iter()
.cloned()
.map(|x| Value::Int(x as i32).spl())
.collect(),
})
.spl(),
);
@ -466,6 +525,46 @@ pub fn to_str(stack: &mut Stack) -> OError {
fixed
}
Value::Str(x) => x,
Value::ByteArray(x) => String::from_utf8(x).map_err(|_| {
stack.error(ErrorKind::InvalidType(
"!utf8".to_owned(),
"utf8".to_owned(),
))
})?,
})
.spl(),
);
Ok(())
}
pub fn to_bytearray(stack: &mut Stack) -> OError {
let o = stack.pop().lock_ro().native.clone();
stack.push(
Value::ByteArray(match o {
Value::Null => type_err!(stack, "null", "array"),
Value::Int(_) => type_err!(stack, "int", "array"),
Value::Long(_) => type_err!(stack, "long", "array"),
Value::Mega(_) => type_err!(stack, "mega", "array"),
Value::Float(_) => type_err!(stack, "float", "array"),
Value::Double(_) => type_err!(stack, "double", "array"),
Value::Func(_) => type_err!(stack, "func", "array"),
Value::Array(x) => x
.iter()
.cloned()
.map(|x| {
Ok(match &x.lock_ro().native {
Value::Int(x) => *x as u8,
Value::Long(x) => *x as u8,
Value::Mega(x) => *x as u8,
_ => stack.err(ErrorKind::InvalidType(
x.lock_ro().kind.lock_ro().get_name(),
"byte".to_owned(),
))?,
})
})
.collect::<Result<Vec<_>, _>>()?,
Value::Str(x) => x.into_bytes(),
Value::ByteArray(x) => x,
})
.spl(),
);
@ -605,6 +704,7 @@ pub fn alit_end(stack: &mut Stack) -> OError {
Ok(())
}
// TODO: rewrite
pub fn import(stack: &mut Stack) -> OError {
let Value::Str(mut s) = stack.pop().lock_ro().native.clone() else {
return stack.err(ErrorKind::InvalidCall("import".to_owned()));
@ -712,17 +812,31 @@ pub fn command(stack: &mut Stack) -> OError {
if args.is_empty() {
return stack.err(ErrorKind::InvalidCall("command".to_owned()));
}
process::Command::new(&args[0])
.args(&args[1..])
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?;
stack.push(
Value::Long(
process::Command::new(&args[0])
.args(&args[1..])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?
.id() as i64,
)
.spl(),
);
Ok(())
}
pub fn command_wait(stack: &mut Stack) -> OError {
command_wait_impl(stack, Stdio::inherit)
}
pub fn command_wait_silent(stack: &mut Stack) -> OError {
command_wait_impl(stack, Stdio::null)
}
pub fn command_wait_impl(stack: &mut Stack, stdio: fn() -> Stdio) -> OError {
let binding = stack.pop();
let Value::Array(ref a) = binding.lock_ro().native else {
return stack.err(ErrorKind::InvalidCall("command".to_owned()));
@ -742,9 +856,9 @@ pub fn command_wait(stack: &mut Stack) -> OError {
Value::Int(
process::Command::new(&args[0])
.args(&args[1..])
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.stdin(stdio())
.stdout(stdio())
.stderr(stdio())
.spawn()
.map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?
.wait()
@ -759,29 +873,18 @@ pub fn command_wait(stack: &mut Stack) -> OError {
pub fn str_to_bytes(stack: &mut Stack) -> OError {
require_on_stack!(s, Str, stack, "str-to-bytes");
stack.push(
Value::Array(
s.bytes()
.into_iter()
.map(|x| Value::Int(x as i32).spl())
.collect(),
)
.spl(),
);
stack.push(Value::ByteArray(s.bytes().collect()).spl());
Ok(())
}
pub fn bytes_to_str(stack: &mut Stack) -> OError {
require_array_on_stack!(a, stack, "str-to-bytes");
let mut chars = Vec::new();
for item in a.iter() {
if let Value::Int(x) = item.lock_ro().native.clone().try_mega_to_int() {
chars.push(x as u8);
} else {
return stack.err(ErrorKind::InvalidCall("command".to_owned()));
}
if stack.peek().lock_ro().kind.lock_ro().get_name() == "bytearray" {
require_on_stack!(a, ByteArray, stack, "bytes-to-str");
stack.push(Value::Str(String::from_utf8_lossy(&a[..]).into_owned()).spl());
return Ok(());
}
stack.push(Value::Str(String::from_utf8_lossy(&chars[..]).into_owned()).spl());
require_byte_array_on_stack!(a, stack, "bytes-to-str");
stack.push(Value::Str(String::from_utf8_lossy(&a).into_owned()).spl());
Ok(())
}
@ -790,7 +893,8 @@ pub fn acopy(stack: &mut Stack) -> OError {
require_on_stack!(idx_dest, Mega, stack, "acopy");
require_on_stack!(idx_src, Mega, stack, "acopy");
let dest_array = stack.pop();
{
let kind = dest_array.lock_ro().kind.lock_ro().get_name();
if kind == "array" {
require_mut_array!(dest, dest_array, stack, "acopy");
require_array_on_stack!(src, stack, "acopy");
let offset = idx_dest - idx_src;
@ -805,6 +909,14 @@ pub fn acopy(stack: &mut Stack) -> OError {
*dest.get_mut((i + offset) as usize).unwrap() = src.get(i as usize).unwrap().clone();
}
}
if kind == "bytearray" {
require_mut!(dest, ByteArray, dest_array, stack, "acopy");
require_byte_array_on_stack!(src, stack, "acopy");
let len = len as usize;
let idx_src = idx_src as usize;
let idx_dest = idx_dest as usize;
(&mut dest[idx_dest..idx_dest + len]).clone_from_slice(&src[idx_src..idx_src + len]);
}
stack.push(dest_array);
Ok(())
}
@ -906,7 +1018,8 @@ pub fn str_to_mega_radix(stack: &mut Stack) -> OError {
pub fn mega_to_str_radix(stack: &mut Stack) -> OError {
require_int_on_stack!(radix, stack, "mega-to-str-radix");
require_on_stack!(mega, Mega, stack, "mega-to-str-radix");
let mut result = Vec::new();
// capacity because O(n)
let mut result = Vec::with_capacity((mega as f64).powf(1.0 / radix as f64) as usize + 2);
let neg = mega < 0;
let mut mega = mega;
if neg {
@ -930,9 +1043,122 @@ pub fn mega_to_str_radix(stack: &mut Stack) -> OError {
return Ok(());
}
pub fn properties(stack: &mut Stack) -> OError {
let o = stack.pop();
let o = o.lock_ro();
let additional: Vec<AMObject> = vec![
Value::Array(vec![
":".to_owned().spl(),
o.kind.lock_ro().get_name().spl(),
])
.spl(),
Value::Array(vec![";".to_owned().spl(), o.native.clone().spl()]).spl(),
];
stack.push(
Value::Array(
o.property_map
.iter()
.map(|(k, v)| Value::Array(vec![k.clone().spl(), v.clone()]).spl())
.chain(additional.into_iter())
.collect(),
)
.spl(),
);
Ok(())
}
pub fn from_properties(stack: &mut Stack) -> OError {
require_array_on_stack!(props, stack, "from-properties");
let mut map = HashMap::with_capacity(props.len());
for prop in props {
require_array!(prop, prop, stack, "from-properties");
if prop.len() != 2 {
stack.err(ErrorKind::InvalidCall("from-properties".to_string()))?;
}
let Value::Str(ref s) = prop[0].lock_ro().native else {
return Err(stack.error(ErrorKind::InvalidCall("from-properties".to_string())));
};
map.insert(s.to_owned(), prop[1].clone());
}
let Value::Str(kind) = map
.get(":")
.ok_or(stack.error(ErrorKind::InvalidCall("from-properties".to_string())))?
.lock_ro()
.native
.clone()
else {
return Err(stack.error(ErrorKind::InvalidCall("from-properties".to_string())));
};
let kind = runtime(|rt| rt.get_type_by_name(&kind))
.ok_or(stack.error(ErrorKind::TypeNotFound(kind.to_owned())))?;
let native = map
.get(";")
.ok_or(stack.error(ErrorKind::InvalidCall("from-properties".to_owned())))?
.lock_ro()
.native
.clone();
map.remove(";");
map.remove(":");
stack.push(Arc::new(Mut::new(Object {
kind,
native,
property_map: map,
})));
Ok(())
}
pub fn list_files(stack: &mut Stack) -> OError {
require_on_stack!(dir, Str, stack, "list-files");
stack.push(
match fs::read_dir(&dir)
.map_err(|_| stack.error(ErrorKind::IO(format!("Not a directory: {}", &dir))))
{
Ok(it) => Value::Array(
it.filter(|x| x.is_ok())
.map(|x| {
if let Ok(x) = x {
Value::Str(x.file_name().to_string_lossy().into_owned()).spl()
} else {
unreachable!()
}
})
.collect(),
)
.spl(),
Err(_) => Value::Null.spl(),
},
);
Ok(())
}
pub fn delete_file(stack: &mut Stack) -> OError {
require_on_stack!(file, Str, stack, "delete-file");
stack.push(Value::Int(if fs::remove_file(file).is_ok() { 1 } else { 0 }).spl());
Ok(())
}
pub fn delete_dir(stack: &mut Stack) -> OError {
require_on_stack!(dir, Str, stack, "delete-dir");
stack.push(
Value::Int(if fs::remove_dir_all(dir).is_ok() {
1
} else {
0
})
.spl(),
);
Ok(())
}
pub fn chdir(stack: &mut Stack) -> OError {
require_on_stack!(dir, Str, stack, "chdir");
env::set_current_dir(dir).map_err(|e| stack.error(ErrorKind::IO(e.to_string())))?;
Ok(())
}
pub fn register(r: &mut Stack, o: Arc<Frame>) {
type Fn = fn(&mut Stack) -> OError;
let fns: [(&str, Fn, u32); 59] = [
let fns: [(&str, Fn, u32); 70] = [
("pop", pop, 0),
("dup", dup, 2),
("dup2", dup2, 3),
@ -941,7 +1167,10 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
("mswap", mswap, 2),
("print", print, 0),
("gettype", gettype, 1),
("gettypeid", gettypeid, 1),
("settype", settype, 1),
("settypeid", settypeid, 1),
("banew", barray_new, 1),
("anew", array_new, 1),
("array-len", array_len, 1),
("array-get", array_get, 1),
@ -964,6 +1193,7 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
("_double", to_double, 1),
("_array", to_array, 1),
("_str", to_str, 1),
("_barray", to_bytearray, 1),
("call", call, 0),
("callp", callp, 0),
("trace", trace, 1),
@ -979,8 +1209,9 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
("alit-end", alit_end, 1),
("import", import, 0),
("readln", readln, 1),
("command", command, 0),
("command", command, 1),
("command-wait", command_wait, 1),
("command-wait-silent", command_wait_silent, 1),
("str-to-bytes", str_to_bytes, 1),
("bytes-to-str", bytes_to_str, 1),
("acopy", acopy, 1),
@ -992,6 +1223,12 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
("str-readf", str_readf, 1),
("str-to-mega-radix", str_to_mega_radix, 1),
("mega-to-str-radix", mega_to_str_radix, 1),
("properties", properties, 1),
("from-properties", from_properties, 1),
("list-files", list_files, 1),
("delete-file", delete_file, 1),
("delete-dir", delete_dir, 1),
("chdir", chdir, 0),
];
for f in fns {
r.define_func(

View file

@ -10,11 +10,14 @@ pub const MESSAGING: &str = include_str!("../spl/messaging.spl");
pub const ASSEMBLE: &str = include_str!("../spl/assemble.spl");
pub const ISBPL: &str = include_str!("../spl/isbpl.spl");
pub const REPL: &str = include_str!("../spl/repl.spl");
pub const FAST: &str = include_str!("../spl/fast.spl");
pub const PURE: &str = include_str!("../spl/pure.spl");
pub const TIME: &str = include_str!("../spl/time.spl");
pub const SERVER: &str = include_str!("../spl/server.spl");
pub const HTTP_SERVER: &str = include_str!("../spl/httpserver/base.spl");
pub const HTTP_SERVER_STATIC: &str = include_str!("../spl/httpserver/static.spl");
pub const LINKEDLIST: &str = include_str!("../spl/linkedlist.spl");
pub const JSON: &str = include_str!("../spl/json.spl");
pub const NOP: &str = "";
pub fn register(runtime: &mut Runtime) {
@ -29,11 +32,14 @@ pub fn register(runtime: &mut Runtime) {
insert("assemble.spl", ASSEMBLE);
insert("isbpl.spl", ISBPL);
insert("repl.spl", REPL);
insert("fast.spl", FAST);
insert("pure.spl", PURE);
insert("time.spl", TIME);
insert("server.spl", SERVER);
insert("httpserver/base.spl", HTTP_SERVER);
insert("httpserver/static.spl", HTTP_SERVER_STATIC);
insert("linkedlist.spl", LINKEDLIST);
insert("json.spl", JSON);
insert("nop.spl", NOP);
}
}

View file

@ -25,7 +25,7 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
}
type Fn = fn(&mut Stack) -> OError;
let fns: [(&str, Fn, u32); 11] = [
let fns: [(&str, Fn, u32); 12] = [
("new-stream", new_stream, 1),
("write-stream", write_stream, 1),
("write-all-stream", write_all_stream, 0),
@ -37,6 +37,7 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
("accept-server-stream", accept_server_stream, 1),
("close-server-stream", close_server_stream, 0),
("get-stream-peer", get_stream_peer, 2),
("shutdown-input-stream", shutdown_input_stream, 0),
];
for f in fns {
r.define_func(

View file

@ -86,6 +86,16 @@ impl Stream {
f(&mut self.extra);
self
}
pub fn shutdown_write(&mut self) {
let mut bx = Box::new(IgnoreWrite());
self.writer = unsafe {
(bx.as_mut() as *mut (dyn Write + Send + Sync + 'static))
.as_mut()
.unwrap()
};
self._writer_storage = Some(bx);
}
}
impl Read for Stream {
@ -132,6 +142,17 @@ impl Write for Stream {
}
}
struct IgnoreWrite();
impl Write for IgnoreWrite {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl<T> From<T> for StreamType
where
T: Fn(&mut Stack) -> Result<Stream, Error> + Sync + Send + 'static,
@ -155,23 +176,16 @@ pub fn new_stream(stack: &mut Stack) -> OError {
pub fn write_stream(stack: &mut Stack) -> OError {
require_on_stack!(id, Mega, stack, "write-stream");
require_array_on_stack!(a, stack, "write-stream");
require_byte_array_on_stack!(a, stack, "write-stream");
let stream = runtime(|rt| {
rt.get_stream(id as u128)
.ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}"))))
})?;
let mut fixed = Vec::with_capacity(a.len());
for item in a.iter() {
match item.lock_ro().native {
Value::Int(x) => fixed.push(x as u8),
_ => type_err!(stack, "!int", "int"),
}
}
stack.push(
Value::Mega(
stream
.lock()
.write(&fixed[..])
.write(&a)
.map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))? as i128,
)
.spl(),
@ -182,21 +196,14 @@ pub fn write_stream(stack: &mut Stack) -> OError {
pub fn write_all_stream(stack: &mut Stack) -> OError {
require_on_stack!(id, Mega, stack, "write-all-stream");
require_array_on_stack!(a, stack, "write-all-stream");
require_byte_array_on_stack!(a, stack, "write-all-stream");
let stream = runtime(|rt| {
rt.get_stream(id as u128)
.ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}"))))
})?;
let mut fixed = Vec::with_capacity(a.len());
for item in a.iter() {
match item.lock_ro().native {
Value::Int(x) => fixed.push(x as u8),
_ => type_err!(stack, "!int", "int"),
}
}
stream
.lock()
.write_all(&fixed[..])
.write_all(&a)
.map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))?;
black_box(&stream.lock_ro()._writer_storage);
Ok(())
@ -219,12 +226,13 @@ pub fn flush_stream(stack: &mut Stack) -> OError {
pub fn read_stream(stack: &mut Stack) -> OError {
require_on_stack!(id, Mega, stack, "read-stream");
let array = stack.pop();
{
let kind = array.lock_ro().kind.lock_ro().get_name();
let stream = runtime(|rt| {
rt.get_stream(id as u128)
.ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}"))))
})?;
if kind == "array" {
require_mut_array!(a, array, stack, "read-stream");
let stream = runtime(|rt| {
rt.get_stream(id as u128)
.ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}"))))
})?;
let mut vec = vec![0; a.len()];
stack.push(
Value::Mega(
@ -242,6 +250,19 @@ pub fn read_stream(stack: &mut Stack) -> OError {
.collect::<Vec<_>>(),
);
}
if kind == "bytearray" {
require_mut!(a, ByteArray, array, stack, "read-stream");
stack.push(
Value::Mega(
stream
.lock()
.read(a)
.map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))?
as i128,
)
.spl(),
);
}
stack.push(array);
Ok(())
}
@ -249,12 +270,13 @@ pub fn read_stream(stack: &mut Stack) -> OError {
pub fn read_all_stream(stack: &mut Stack) -> OError {
require_on_stack!(id, Mega, stack, "read-all-stream");
let array = stack.pop();
{
let kind = array.lock_ro().kind.lock_ro().get_name();
let stream = runtime(|rt| {
rt.get_stream(id as u128)
.ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}"))))
})?;
if kind == "array" {
require_mut_array!(a, array, stack, "read-all-stream");
let stream = runtime(|rt| {
rt.get_stream(id as u128)
.ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}"))))
})?;
let mut vec = vec![0; a.len()];
stream
.lock()
@ -266,6 +288,13 @@ pub fn read_all_stream(stack: &mut Stack) -> OError {
.collect::<Vec<_>>(),
);
}
if kind == "bytearray" {
require_mut!(a, ByteArray, array, stack, "read-stream");
stream
.lock()
.read_exact(a)
.map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))?;
}
stack.push(array);
Ok(())
}
@ -276,6 +305,16 @@ pub fn close_stream(stack: &mut Stack) -> OError {
Ok(())
}
pub fn shutdown_input_stream(stack: &mut Stack) -> OError {
require_on_stack!(id, Mega, stack, "shutdown-input-stream");
let stream = runtime(|rt| {
rt.get_stream(id as u128)
.ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}"))))
})?;
stream.lock().shutdown_write();
Ok(())
}
pub(super) fn stream_file(stack: &mut Stack) -> Result<Stream, Error> {
let truncate = stack.pop().lock_ro().is_truthy();
require_on_stack!(path, Str, stack, "FILE new-stream");
@ -335,7 +374,7 @@ pub(super) fn stream_cmd(stack: &mut Stack) -> Result<Stream, Error> {
}
}
if args.is_empty() {
return stack.err(ErrorKind::InvalidCall("command".to_owned()));
return stack.err(ErrorKind::InvalidCall("CMD new-stream".to_owned()));
}
let mut command = process::Command::new(&args[0])
.args(&args[1..])

120
test.spl
View file

@ -1,10 +1,12 @@
[
"#stream.spl" import
"#http.spl" import
"#messaging.spl" import
"#server.spl" import
"#time.spl" import
"#httpserver/base.spl" import
"spl/stream.spl" import
"spl/http.spl" import
"spl/messaging.spl" import
"spl/server.spl" import
"spl/time.spl" import
"spl/httpserver/base.spl" import
"spl/linkedlist.spl" import
"spl/json.spl" import
"SPL tester" =program-name
@ -236,17 +238,117 @@ func main { int | with args ;
"hello! this is a test of URL encoding!" net:http:urlencode dup println;
net:http:urldecode println;
"" println;
"testing linked lists" println;
def list LinkedList:new =list
"=> len of an empty list: " print;
list:len println;
"=> len of a list with one element: " print;
list:push;<"Hello!">
list:len println;
"=> list should not have a next yet... " print;
list:next null eq dup if { "ok" swap } not if { "BAD" } println
"=> element zero should be 'Hello!': " print list:get<0> println;
5 :foreach<{ | "" println }>
"=> iter of list should start with that too: " print list:iter dup:next println;
"=> then should be null: " print :next dup null eq if { pop "ok" } println;
"=> list should contain 'Hello!': " print list:iter:join<", "> println;
"=> with new element after that: " print
list:push;<"One!!">
list:iter:join<", "> println;
"=> pushing numbers 2..10: " print
2 10 Range:new:iter:foreach;<{ | list:push; }>
list:iter:join<", "> println;
"=> popping 9: " print
list:pop;
list:iter:join<", "> println;
"=> removing 5: " print
list:remove;<5>
list:iter:join<", "> println;
"=> popping front: " print
list:pop-front;
list:iter:join<", "> println;
"=> inserting 0 back: " print
0 list:insert;<0>
list:iter:join<", "> println;
"=> inserting 5 back: " print
5 list:insert;<5>
list:iter:join<", "> println;
^ println
"testing match" println
"a -> b (0): " print "a" "b" match _str println
"a -> a (1): " print "a" "a" match _str println
"[a b] -> [a a] (0): " print [ "a" "b" ] [ "a" "a" ] match _str println
"[a b] -> [a b] (1): " print [ "a" "b" ] [ "a" "b" ] match _str println
def mtesta, mtestb
"a => =mtesta (1): " print "a" &=mtesta match _str println
"[a b] => [a =mtesta] (1): " print [ "a" "b" ] [ "a" &=mtesta ] match _str println
"[b a] => [a =mtesta] (0): " print [ "b" "a" ] [ "a" &=mtesta ] match _str println
"[a b] => [=mtesta a] (0): " print [ "b" "a" ] [ "a" &=mtesta ] match _str println
"-> mtesta = (b) " mtesta _str concat println
"^ok => ^ok (1): " print ^ok => ^ok _str println
"^bad => ^ok (0): " print ^bad => ^ok _str println
"[^bad a] => [^ok =mtestb] (0): " print [ ^bad "a" ] => [ ^ok &=mtestb ] _str println
"[^ok b] => [^ok =mtestb] (1): " print [ ^ok "b" ] => [ ^ok &=mtestb ] _str println
"-> mtestb = (b) " mtestb _str concat println
def result, val
[ ^ok "hello, world" ] =result
result =>! [ ^ok &=val ]
val println
[ ^error "bad" ] =result
catch Custom {
result =>! [ ^ok &println ]
} { with e ;
e:message "match unsuccessful" eq if {
"err value: " print
result =>! [ ^error &println ]
}
}
^ok =>? ^error not if { println }
"" println
"json test" println
0 [ 0 1 2 "hi" ] properties json:props-to-sjson println
5 :foreach<{ | pop "" println }>
"you now have a chance to connect too: localhost :4075 :4076 - stopping in 5 seconds..." println;
5000 time:sleep;
] dup :len 0 eq not if {
"" println
"!! something went wrong somewhere. the stack is not empty." println
dyn-__dump
}
"you now have a chance to connect too: localhost :4075 :4076 - stopping in 5 seconds..." println;
5000 time:sleep;
100
}

View file

@ -1 +0,0 @@
hi