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] [package]
name = "spl" name = "spl"
version = "0.4.0" version = "0.4.1"
edition = "2021" edition = "2021"
description = "Stack Pogramming Language: A simple, concise scripting language." description = "Stack Pogramming Language: A simple, concise scripting language."
license = "MIT" license = "MIT"

View file

@ -11,6 +11,9 @@ func main { mega | with args ;
&print swap:foreach &print swap:foreach
"" println "" println
println<"and with that, we're done"> println<"and with that, we're done">
[ ^hello ^pattern-matching ] => [ ^hello &=args ] if {
args println "prints pattern-matching";
}
0 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 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. 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. 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 ; construct { this | with server stream this ;
server this:=server server this:=server
stream this:=stream stream this:=stream
0 anew this:=head 0 banew this:=head
0 anew this:=body 0 banew this:=body
this this
} }
read-head { this | with this ; read-head { this | with this ;
def read def read
def buf 1024 anew =buf def buf 1024 banew =buf
def found def found
while { while {
buf this:stream:read pop =read buf this:stream:read pop dup =read
"\r\n\r\n" :to-bytes buf:find dup =found not read and "\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 buf:sub<found 4 + buf:len> this:=body
this this
} }
parse-head { this | with 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 def iter this:head:iter =iter
iter:next:readf<"{} {} HTTP/{}"> dup if { iter:next:readf<"{} {} HTTP/{}"> dup if {
dup:to-stack this:=version this:=raw-path this:=method dup:to-stack this:=version this:=raw-path this:=method
@ -100,7 +108,7 @@ construct net:http:server:Request {
read-body { this | with this ; read-body { this | with this ;
this:headers:get<"content-length"> dup if { _mega with content-length ; this:headers:get<"content-length"> dup if { _mega with content-length ;
def read def read
def buf 1024 anew =buf def buf 1024 banew =buf
while { while {
this:body:len content-length lt read and this:body:len content-length lt read and
} { } {
@ -146,7 +154,7 @@ construct net:http:server:Request {
} }
finish { | with this ; finish { | with this ;
this:wrote-body not if { this:wrote-body not if {
0 anew this:write-body; 0 banew this:write-body;
} }
this:stream:close; this:stream:close;
} }

View file

@ -1,9 +1,15 @@
"base.spl" import "base.spl" import
"../stream.spl" import
"_static_ext_server" net:http:server:register "_static_ext_server" net:http:server:register
"_static_ext_Request" 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 { construct net:http:server:_static_ext_server {
bufsize
client-cache
cached-files cached-files
; ;
get-cached-files { cached-files | with this ; 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 { 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 ; serve-file { this | with filepath path type this ;
this:path path eq if { 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 this
} }
serve-file-cached { this | with filepath path type this ; serve-file-cached { this | with filepath path type this ;
this:path path eq if { this:path path eq if {
filepath this:server:get-cached-files:get dup not 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 this
} }

View file

@ -13,12 +13,12 @@ construct _Iter {
foreach { | with callable this ; foreach { | with callable this ;
!!- !!-
while { !!- this:next dup null eq not } { while { !!- this:next dup null eq not } {
!!- callable call !!- callable callp
} }
pop pop
} }
collect { array | with this ; collect { array | with this ;
[ { any | } this:foreach ] [ { | } this:foreach ]
} }
map { MapIter | with map-function this ; map { MapIter | with map-function this ;
map-function this MapIter:new 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 ; func not { !x | with x ;
1 1
x if { pop 0 } x if { pop 0 }
} }
@ -23,9 +23,8 @@ func and { a&&b | with a b ;
} }
func or { a||b | with a b ; func or { a||b | with a b ;
0 b
a if { pop 1 } a if { pop a }
b if { pop 1 }
} }
func alit-end { array | with type ; func alit-end { array | with type ;

View file

@ -1,12 +1,31 @@
"fast.spl" import
func main { mega | with args ; func main { mega | with args ;
"Welcome to the SPL REPL!" println "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 "REPL" =program-name
while { 1 } { while { 1 } {
catch { 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 ; { with err ;
err:message dup null eq if { 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 replacee:to-bytes replacement:to-bytes this:to-bytes:replace:to-str
} }
find { idx | with search this ; 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 ; starts-with { bool | with beginning this ;
beginning:to-bytes this:to-bytes:starts-with 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 ; _char { int | with this ;
0 (this _array):get 0 (this _array):get
} }
@ -54,7 +63,7 @@ construct _str_ext {
!!- pat this str-readf !!- pat this str-readf
} }
readf1 { str | with pat this ; readf1 { str | with pat this ;
!!- pat this:readf 0:get !!- pat this:readf dup null eq not if { :0 }
} }
uppercase { str | with this ; uppercase { str | with this ;
this _array :cmap<{ | with chr ; this _array :cmap<{ | with chr ;
@ -89,15 +98,27 @@ construct _mega-ext {
while { !!- i this lt } { !!- i callable call i 1 + =i } while { !!- i this lt } { !!- i callable call i 1 + =i }
} }
fforeach { | with callable this ; 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 ; _str_radix { str | with radix this ;
this radix mega-to-str-radix this radix mega-to-str-radix
} }
iter { MegaIter | with this ;
this MegaIter:new
}
} include _mega-ext in mega } include _mega-ext in mega
construct _array-ext { construct _array-ext {
; ;
another { array | with this ;
this gettype =this
this "array" eq if {
anew
}
this "bytearray" eq if {
banew
}
}
get { any | array-get } get { any | array-get }
sget { any|null | with idx this ; sget { any|null | with idx this ;
idx this:len lt idx -1 gt and dup if { idx this:len lt idx -1 gt and dup if {
@ -135,7 +156,7 @@ construct _array-ext {
} }
to-str { str | bytes-to-str } to-str { str | bytes-to-str }
sub { [any] | with begin end this ; 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 ; split { arr | with splitter this ;
def i 0 =i def i 0 =i
@ -197,6 +218,9 @@ construct _array-ext {
} }
null null
} }
contains { bool | with search this ;
search awrap this:find null eq not
}
starts-with { bool | with beginning this ; starts-with { bool | with beginning this ;
this:len beginning:len lt if { this:len beginning:len lt if {
0 0
@ -204,6 +228,13 @@ construct _array-ext {
} }
0 beginning:len this:sub beginning eq 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 ; last { | with this ;
this:len -- this:get this:len -- this:get
} }
@ -240,7 +271,9 @@ construct _array-ext {
=4 { | with this ; =4 { | with this ;
4 this:set; 4 this:set;
} }
} include _array-ext in array }
include _array-ext in array
include _array-ext in bytearray
construct _func-ext { construct _func-ext {
args args
@ -288,6 +321,7 @@ construct List {
to-stack { .. | :array:to-stack } to-stack { .. | :array:to-stack }
to-str { str | :array:to-str } to-str { str | :array:to-str }
sub { [any] | :array:sub } sub { [any] | :array:sub }
clear { | :=array<0 anew> }
} }
construct _GrowingArray { construct _GrowingArray {
; ;
@ -345,7 +379,7 @@ construct ArrayIter {
construct _IterableArray { construct _IterableArray {
; ;
iter { ArrayIter | with this ; iter { ArrayIter | with this ;
this gettype "array" eq dup if { this gettype:ends-with<"array"> dup if {
pop pop
this ArrayIter:new this ArrayIter:new
2 stop 2 stop
@ -357,6 +391,7 @@ construct _IterableArray {
include _Iter in ArrayIter include _Iter in ArrayIter
include _IterableArray in List include _IterableArray in List
include _IterableArray in array include _IterableArray in array
include _IterableArray in bytearray
construct MicroMap { construct MicroMap {
pairs pairs
@ -401,6 +436,20 @@ construct MicroMap {
foreach { | with callable this ; foreach { | with callable this ;
callable this:pairs:foreach 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 { construct Range {
@ -455,11 +504,27 @@ construct RangeIter {
include _Iter in 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 { } construct shadow { }
func aadd { array | with arr1 arr2 ; 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; arr1 newarr 0 0 arr1:len acopy;
arr2 newarr 0 arr1:len arr2:len acopy; arr2 newarr 0 arr1:len arr2:len acopy;
@ -494,6 +559,8 @@ func cache { ... | with arg-amt id body ;
result result
} }
func panic-handler { | "to be overridden"; }
def do-not-dump 0 =do-not-dump def do-not-dump 0 =do-not-dump
func handle-panic { | with msg trace ; func handle-panic { | with msg trace ;
program-name dup if { program-name dup if {
@ -504,6 +571,7 @@ func handle-panic { | with msg trace ;
&println trace:foreach &println trace:foreach
"\nPanic message:" println "\nPanic message:" println
" " print msg println " " print msg println
panic-handler
def map env =map def map env =map
"SPL_PANIC_DUMP" env:get dup if { "SPL_PANIC_DUMP" env:get dup if {
"Dumping because SPL_PANIC_DUMP is set." println "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 def _'has-been-called 0 =_'has-been-called
func _ { | func _ { |
_'has-been-called not if { _'has-been-called not if {

View file

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

View file

@ -78,6 +78,10 @@ fn read_block_dyn(
); );
} }
"def" => { "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()))); words.push(Word::Key(Keyword::Def(str_words[i + 1].to_owned())));
i += 1; i += 1;
} }
@ -121,7 +125,20 @@ fn read_block_dyn(
run_as_base: false, 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()))); 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))); 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" => { "inline-callable" => {
words.push(Word::Key(Keyword::InlineCallable)); words.push(Word::Key(Keyword::InlineCallable));
} }
@ -255,6 +320,9 @@ fn read_block_dyn(
x if x.starts_with('\"') => { x if x.starts_with('\"') => {
words.push(Word::Const(Value::Str(x[1..].to_owned()))); 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 if x.chars().all(|c| c.is_numeric() || c == '_' || c == '-')
&& !x.starts_with('_') && !x.starts_with('_')
&& x.contains(char::is_numeric) => && 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_export]
macro_rules! require_int_on_stack { macro_rules! require_int_on_stack {
($name:tt, $stack:expr, $fn:literal) => { ($name:tt, $stack:expr, $fn:literal) => {
@ -133,6 +151,31 @@ nofmt! {
}; };
} }
#[macro_export] #[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 { macro_rules! require_mut_array_on_stack {
($name:tt, $stack:expr, $fn:literal) => { ($name:tt, $stack:expr, $fn:literal) => {
let binding = $stack.pop(); 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("func".to_owned(), Ok); // infallible
let _ = rt.make_type("array".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("str".to_owned(), Ok); // infallible
let _ = rt.make_type("bytearray".to_owned(), Ok); // infallible
stdlib::register(&mut rt); stdlib::register(&mut rt);
rt rt
} }
@ -473,6 +474,9 @@ impl Stack {
} }
pub fn call(&mut self, func: &AFunc) -> OError { 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 { let f = if let Some(ref cname) = func.fname {
Frame::new_in( Frame::new_in(
func.origin.clone(), func.origin.clone(),
@ -515,14 +519,13 @@ impl Stack {
frame = self.frames.first().unwrap().clone(); frame = self.frames.first().unwrap().clone();
} }
let tmpname = name.clone(); let tmpname = name.clone();
let tmpframe = frame.clone();
frame.functions.lock().insert( frame.functions.lock().insert(
name.clone(), name.clone(),
Arc::new(Func { Arc::new(Func {
ret_count: 1, ret_count: 1,
origin: frame.clone(), origin: Arc::new(Frame::dummy()),
to_call: FuncImpl::NativeDyn(Arc::new(Box::new(move |stack| { 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(()) Ok(())
}))), }))),
run_as_base: false, run_as_base: false,
@ -531,15 +534,14 @@ impl Stack {
}), }),
); );
let tmpname = name.clone(); let tmpname = name.clone();
let tmpframe = frame.clone();
frame.functions.lock().insert( frame.functions.lock().insert(
"=".to_owned() + &name, "=".to_owned() + &name,
Arc::new(Func { Arc::new(Func {
ret_count: 0, ret_count: 0,
origin: frame.clone(), origin: Arc::new(Frame::dummy()),
to_call: FuncImpl::NativeDyn(Arc::new(Box::new(move |stack| { to_call: FuncImpl::NativeDyn(Arc::new(Box::new(move |stack| {
let v = stack.pop(); let v = stack.pop();
tmpframe.set_var(tmpname.clone(), v, stack) stack.set_var(tmpname.clone(), v)
}))), }))),
run_as_base: false, run_as_base: false,
fname: Some("RUNTIME".to_owned()), fname: Some("RUNTIME".to_owned()),
@ -568,11 +570,33 @@ impl Stack {
} }
pub fn set_var(&self, name: String, obj: AMObject) -> OError { 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> { 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) { pub fn push(&mut self, obj: AMObject) {
@ -771,6 +795,7 @@ pub enum Value {
Double(f64), Double(f64),
Func(AFunc), Func(AFunc),
Array(Vec<AMObject>), Array(Vec<AMObject>),
ByteArray(Vec<u8>),
Str(String), Str(String),
} }
@ -1058,14 +1083,15 @@ impl Object {
pub fn is_truthy(&self) -> bool { pub fn is_truthy(&self) -> bool {
match &self.native { match &self.native {
Value::Null => self.kind.lock_ro().id != 0, Value::Null => self.kind.lock_ro().id != 0,
Value::Int(x) => x > &0, Value::Int(x) => *x > 0,
Value::Long(x) => x > &0, Value::Long(x) => *x > 0,
Value::Mega(x) => x > &0, Value::Mega(x) => *x > 0,
Value::Float(_) => true, Value::Float(x) => x.is_finite(),
Value::Double(_) => true, Value::Double(x) => x.is_finite(),
Value::Func(_) => true, Value::Func(_) => true,
Value::Array(_) => true, Value::Array(_) => true,
Value::Str(x) => !x.is_empty(), 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::Func(_) => x.get_type_by_id(6),
Value::Array(_) => x.get_type_by_id(7), Value::Array(_) => x.get_type_by_id(7),
Value::Str(_) => x.get_type_by_id(8), Value::Str(_) => x.get_type_by_id(8),
Value::ByteArray(_) => x.get_type_by_id(9),
} }
.expect("runtime uninitialized: default types not set.") .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)), "null" => words.push(Word::Const(Value::Null)),
"array" => panic!("invalid sasm const: array - not all Values can be consts!"), "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]), _ => panic!("invalid sasm const: {}", line[1]),
}, },
"call" => { "call" => {
@ -356,6 +375,25 @@ fn sasm_write_func(words: Words) -> String {
text.replace("\0", "\0\x01").replace("\n", "\0\0") 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) => { Word::Call(name, rem, ra) => {
output += "call "; output += "call ";

View file

@ -1,9 +1,8 @@
use std::{ use std::{
collections::VecDeque, collections::{HashMap, VecDeque},
env::{args, vars}, env::{self, args, vars},
fs, fs,
io::{stdin, stdout, Write}, io::{stdin, stdout, Write},
mem,
ops::{Add, Div, Mul, Rem, Sub}, ops::{Add, Div, Mul, Rem, Sub},
process::{self, Stdio}, process::{self, Stdio},
sync::Arc, sync::Arc,
@ -96,12 +95,27 @@ pub fn settype(stack: &mut Stack) -> OError {
let o = stack.pop(); let o = stack.pop();
let kind = runtime(|rt| rt.get_type_by_name(&s)) let kind = runtime(|rt| rt.get_type_by_name(&s))
.ok_or_else(|| stack.error(ErrorKind::TypeNotFound(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(); let mut obj = o.lock();
kind.lock_ro().write_into(&mut obj); kind.lock_ro().write_into(&mut obj);
obj.kind = kind; obj.kind = kind;
mem::drop(obj);
stack.push(o);
Ok(())
} }
pub fn gettype(stack: &mut Stack) -> OError { pub fn gettype(stack: &mut Stack) -> OError {
@ -110,6 +124,23 @@ pub fn gettype(stack: &mut Stack) -> OError {
Ok(()) 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 { pub fn array_new(stack: &mut Stack) -> OError {
let Value::Mega(i) = stack.pop().lock_ro().native.clone() else { let Value::Mega(i) = stack.pop().lock_ro().native.clone() else {
return stack.err(ErrorKind::InvalidCall("anew".to_owned())); 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 { pub fn array_len(stack: &mut Stack) -> OError {
let binding = stack.pop(); let binding = stack.pop();
let Value::Array(ref a) = binding.lock_ro().native else { let len = match binding.lock_ro().native {
return stack.err(ErrorKind::InvalidCall("array-len".to_owned())); 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(()) Ok(())
} }
pub fn array_get(stack: &mut Stack) -> OError { pub fn array_get(stack: &mut Stack) -> OError {
let binding = stack.pop(); 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())); return stack.err(ErrorKind::InvalidCall("array-get".to_owned()));
}; };
let Value::Mega(i) = stack.pop().lock_ro().native.clone() else { let o = match binding.lock_ro().native {
return stack.err(ErrorKind::InvalidCall("array-get".to_owned())); 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(()) Ok(())
} }
pub fn array_set(stack: &mut Stack) -> OError { pub fn array_set(stack: &mut Stack) -> OError {
let binding = stack.pop(); let binding = &stack.pop();
let Value::Array(ref mut a) = binding.lock().native else { let binding = &mut binding.lock().native;
return stack.err(ErrorKind::InvalidCall("array-set".to_owned())); require_on_stack!(i, Mega, stack, "array-set");
};
let Value::Mega(i) = stack.pop().lock_ro().native.clone() else {
return stack.err(ErrorKind::InvalidCall("array-set".to_owned()));
};
let o = stack.pop(); let o = stack.pop();
stack.push(a.get(i as usize).ok_or_else(array!(stack, i))?.clone()); if let Value::Array(ref mut a) = binding {
*a.get_mut(i as usize).ok_or_else(array!(stack, i))? = o; 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(()) Ok(())
} }
@ -227,6 +268,8 @@ pub fn plus(stack: &mut Stack) -> OError {
Mega, Mega,
Long, Long,
Int, Int,
Float,
Double,
) )
.spl(), .spl(),
); );
@ -245,6 +288,8 @@ pub fn minus(stack: &mut Stack) -> OError {
Mega, Mega,
Long, Long,
Int, Int,
Float,
Double,
) )
.spl(), .spl(),
); );
@ -263,6 +308,8 @@ pub fn slash(stack: &mut Stack) -> OError {
Mega, Mega,
Long, Long,
Int, Int,
Float,
Double,
) )
.spl(), .spl(),
); );
@ -281,6 +328,8 @@ pub fn star(stack: &mut Stack) -> OError {
Mega, Mega,
Long, Long,
Int, Int,
Float,
Double,
) )
.spl(), .spl(),
); );
@ -295,7 +344,7 @@ pub fn percent(stack: &mut Stack) -> OError {
a, a,
b, b,
rem, rem,
stack.err(ErrorKind::InvalidCall("star".to_owned())), stack.err(ErrorKind::InvalidCall("percent".to_owned())),
Mega, Mega,
Long, Long,
Int, Int,
@ -320,6 +369,7 @@ pub fn to_int(stack: &mut Stack) -> OError {
Value::Str(x) => x Value::Str(x) => x
.parse() .parse()
.map_err(|_| stack.error(ErrorKind::Parse(x, "int".to_owned())))?, .map_err(|_| stack.error(ErrorKind::Parse(x, "int".to_owned())))?,
Value::ByteArray(x) => x.len() as i32,
}) })
.spl(), .spl(),
); );
@ -341,6 +391,7 @@ pub fn to_long(stack: &mut Stack) -> OError {
Value::Str(x) => x Value::Str(x) => x
.parse() .parse()
.map_err(|_| stack.error(ErrorKind::Parse(x, "long".to_owned())))?, .map_err(|_| stack.error(ErrorKind::Parse(x, "long".to_owned())))?,
Value::ByteArray(x) => x.len() as i64,
}) })
.spl(), .spl(),
); );
@ -362,6 +413,7 @@ pub fn to_mega(stack: &mut Stack) -> OError {
Value::Str(x) => x Value::Str(x) => x
.parse() .parse()
.map_err(|_| stack.error(ErrorKind::Parse(x, "mega".to_owned())))?, .map_err(|_| stack.error(ErrorKind::Parse(x, "mega".to_owned())))?,
Value::ByteArray(x) => x.len() as i128,
}) })
.spl(), .spl(),
); );
@ -383,6 +435,7 @@ pub fn to_float(stack: &mut Stack) -> OError {
Value::Str(x) => x Value::Str(x) => x
.parse() .parse()
.map_err(|_| stack.error(ErrorKind::Parse(x, "float".to_owned())))?, .map_err(|_| stack.error(ErrorKind::Parse(x, "float".to_owned())))?,
Value::ByteArray(_) => type_err!(stack, "bytearray", "float"),
}) })
.spl(), .spl(),
); );
@ -404,6 +457,7 @@ pub fn to_double(stack: &mut Stack) -> OError {
Value::Str(x) => x Value::Str(x) => x
.parse() .parse()
.map_err(|_| stack.error(ErrorKind::Parse(x, "double".to_owned())))?, .map_err(|_| stack.error(ErrorKind::Parse(x, "double".to_owned())))?,
Value::ByteArray(_) => type_err!(stack, "bytearray", "double"),
}) })
.spl(), .spl(),
); );
@ -426,6 +480,11 @@ pub fn to_array(stack: &mut Stack) -> OError {
.chars() .chars()
.map(|x| Value::Int(x as u32 as i32).spl()) .map(|x| Value::Int(x as u32 as i32).spl())
.collect(), .collect(),
Value::ByteArray(x) => x
.iter()
.cloned()
.map(|x| Value::Int(x as i32).spl())
.collect(),
}) })
.spl(), .spl(),
); );
@ -466,6 +525,46 @@ pub fn to_str(stack: &mut Stack) -> OError {
fixed fixed
} }
Value::Str(x) => x, 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(), .spl(),
); );
@ -605,6 +704,7 @@ pub fn alit_end(stack: &mut Stack) -> OError {
Ok(()) Ok(())
} }
// TODO: rewrite
pub fn import(stack: &mut Stack) -> OError { pub fn import(stack: &mut Stack) -> OError {
let Value::Str(mut s) = stack.pop().lock_ro().native.clone() else { let Value::Str(mut s) = stack.pop().lock_ro().native.clone() else {
return stack.err(ErrorKind::InvalidCall("import".to_owned())); return stack.err(ErrorKind::InvalidCall("import".to_owned()));
@ -712,17 +812,31 @@ pub fn command(stack: &mut Stack) -> OError {
if args.is_empty() { if args.is_empty() {
return stack.err(ErrorKind::InvalidCall("command".to_owned())); return stack.err(ErrorKind::InvalidCall("command".to_owned()));
} }
process::Command::new(&args[0]) stack.push(
.args(&args[1..]) Value::Long(
.stdin(Stdio::inherit()) process::Command::new(&args[0])
.stdout(Stdio::inherit()) .args(&args[1..])
.stderr(Stdio::inherit()) .stdin(Stdio::null())
.spawn() .stdout(Stdio::null())
.map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?; .stderr(Stdio::null())
.spawn()
.map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?
.id() as i64,
)
.spl(),
);
Ok(()) Ok(())
} }
pub fn command_wait(stack: &mut Stack) -> OError { 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 binding = stack.pop();
let Value::Array(ref a) = binding.lock_ro().native else { let Value::Array(ref a) = binding.lock_ro().native else {
return stack.err(ErrorKind::InvalidCall("command".to_owned())); return stack.err(ErrorKind::InvalidCall("command".to_owned()));
@ -742,9 +856,9 @@ pub fn command_wait(stack: &mut Stack) -> OError {
Value::Int( Value::Int(
process::Command::new(&args[0]) process::Command::new(&args[0])
.args(&args[1..]) .args(&args[1..])
.stdin(Stdio::inherit()) .stdin(stdio())
.stdout(Stdio::inherit()) .stdout(stdio())
.stderr(Stdio::inherit()) .stderr(stdio())
.spawn() .spawn()
.map_err(|x| stack.error(ErrorKind::IO(x.to_string())))? .map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?
.wait() .wait()
@ -759,29 +873,18 @@ pub fn command_wait(stack: &mut Stack) -> OError {
pub fn str_to_bytes(stack: &mut Stack) -> OError { pub fn str_to_bytes(stack: &mut Stack) -> OError {
require_on_stack!(s, Str, stack, "str-to-bytes"); require_on_stack!(s, Str, stack, "str-to-bytes");
stack.push( stack.push(Value::ByteArray(s.bytes().collect()).spl());
Value::Array(
s.bytes()
.into_iter()
.map(|x| Value::Int(x as i32).spl())
.collect(),
)
.spl(),
);
Ok(()) Ok(())
} }
pub fn bytes_to_str(stack: &mut Stack) -> OError { pub fn bytes_to_str(stack: &mut Stack) -> OError {
require_array_on_stack!(a, stack, "str-to-bytes"); if stack.peek().lock_ro().kind.lock_ro().get_name() == "bytearray" {
let mut chars = Vec::new(); require_on_stack!(a, ByteArray, stack, "bytes-to-str");
for item in a.iter() { stack.push(Value::Str(String::from_utf8_lossy(&a[..]).into_owned()).spl());
if let Value::Int(x) = item.lock_ro().native.clone().try_mega_to_int() { return Ok(());
chars.push(x as u8);
} else {
return stack.err(ErrorKind::InvalidCall("command".to_owned()));
}
} }
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(()) Ok(())
} }
@ -790,7 +893,8 @@ pub fn acopy(stack: &mut Stack) -> OError {
require_on_stack!(idx_dest, Mega, stack, "acopy"); require_on_stack!(idx_dest, Mega, stack, "acopy");
require_on_stack!(idx_src, Mega, stack, "acopy"); require_on_stack!(idx_src, Mega, stack, "acopy");
let dest_array = stack.pop(); 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_mut_array!(dest, dest_array, stack, "acopy");
require_array_on_stack!(src, stack, "acopy"); require_array_on_stack!(src, stack, "acopy");
let offset = idx_dest - idx_src; 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(); *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); stack.push(dest_array);
Ok(()) 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 { pub fn mega_to_str_radix(stack: &mut Stack) -> OError {
require_int_on_stack!(radix, stack, "mega-to-str-radix"); require_int_on_stack!(radix, stack, "mega-to-str-radix");
require_on_stack!(mega, Mega, 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 neg = mega < 0;
let mut mega = mega; let mut mega = mega;
if neg { if neg {
@ -930,9 +1043,122 @@ pub fn mega_to_str_radix(stack: &mut Stack) -> OError {
return Ok(()); 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>) { pub fn register(r: &mut Stack, o: Arc<Frame>) {
type Fn = fn(&mut Stack) -> OError; type Fn = fn(&mut Stack) -> OError;
let fns: [(&str, Fn, u32); 59] = [ let fns: [(&str, Fn, u32); 70] = [
("pop", pop, 0), ("pop", pop, 0),
("dup", dup, 2), ("dup", dup, 2),
("dup2", dup2, 3), ("dup2", dup2, 3),
@ -941,7 +1167,10 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
("mswap", mswap, 2), ("mswap", mswap, 2),
("print", print, 0), ("print", print, 0),
("gettype", gettype, 1), ("gettype", gettype, 1),
("gettypeid", gettypeid, 1),
("settype", settype, 1), ("settype", settype, 1),
("settypeid", settypeid, 1),
("banew", barray_new, 1),
("anew", array_new, 1), ("anew", array_new, 1),
("array-len", array_len, 1), ("array-len", array_len, 1),
("array-get", array_get, 1), ("array-get", array_get, 1),
@ -964,6 +1193,7 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
("_double", to_double, 1), ("_double", to_double, 1),
("_array", to_array, 1), ("_array", to_array, 1),
("_str", to_str, 1), ("_str", to_str, 1),
("_barray", to_bytearray, 1),
("call", call, 0), ("call", call, 0),
("callp", callp, 0), ("callp", callp, 0),
("trace", trace, 1), ("trace", trace, 1),
@ -979,8 +1209,9 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
("alit-end", alit_end, 1), ("alit-end", alit_end, 1),
("import", import, 0), ("import", import, 0),
("readln", readln, 1), ("readln", readln, 1),
("command", command, 0), ("command", command, 1),
("command-wait", command_wait, 1), ("command-wait", command_wait, 1),
("command-wait-silent", command_wait_silent, 1),
("str-to-bytes", str_to_bytes, 1), ("str-to-bytes", str_to_bytes, 1),
("bytes-to-str", bytes_to_str, 1), ("bytes-to-str", bytes_to_str, 1),
("acopy", acopy, 1), ("acopy", acopy, 1),
@ -992,6 +1223,12 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
("str-readf", str_readf, 1), ("str-readf", str_readf, 1),
("str-to-mega-radix", str_to_mega_radix, 1), ("str-to-mega-radix", str_to_mega_radix, 1),
("mega-to-str-radix", mega_to_str_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 { for f in fns {
r.define_func( 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 ASSEMBLE: &str = include_str!("../spl/assemble.spl");
pub const ISBPL: &str = include_str!("../spl/isbpl.spl"); pub const ISBPL: &str = include_str!("../spl/isbpl.spl");
pub const REPL: &str = include_str!("../spl/repl.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 PURE: &str = include_str!("../spl/pure.spl");
pub const TIME: &str = include_str!("../spl/time.spl"); pub const TIME: &str = include_str!("../spl/time.spl");
pub const SERVER: &str = include_str!("../spl/server.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: &str = include_str!("../spl/httpserver/base.spl");
pub const HTTP_SERVER_STATIC: &str = include_str!("../spl/httpserver/static.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 const NOP: &str = "";
pub fn register(runtime: &mut Runtime) { pub fn register(runtime: &mut Runtime) {
@ -29,11 +32,14 @@ pub fn register(runtime: &mut Runtime) {
insert("assemble.spl", ASSEMBLE); insert("assemble.spl", ASSEMBLE);
insert("isbpl.spl", ISBPL); insert("isbpl.spl", ISBPL);
insert("repl.spl", REPL); insert("repl.spl", REPL);
insert("fast.spl", FAST);
insert("pure.spl", PURE); insert("pure.spl", PURE);
insert("time.spl", TIME); insert("time.spl", TIME);
insert("server.spl", SERVER); insert("server.spl", SERVER);
insert("httpserver/base.spl", HTTP_SERVER); insert("httpserver/base.spl", HTTP_SERVER);
insert("httpserver/static.spl", HTTP_SERVER_STATIC); insert("httpserver/static.spl", HTTP_SERVER_STATIC);
insert("linkedlist.spl", LINKEDLIST);
insert("json.spl", JSON);
insert("nop.spl", NOP); 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; type Fn = fn(&mut Stack) -> OError;
let fns: [(&str, Fn, u32); 11] = [ let fns: [(&str, Fn, u32); 12] = [
("new-stream", new_stream, 1), ("new-stream", new_stream, 1),
("write-stream", write_stream, 1), ("write-stream", write_stream, 1),
("write-all-stream", write_all_stream, 0), ("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), ("accept-server-stream", accept_server_stream, 1),
("close-server-stream", close_server_stream, 0), ("close-server-stream", close_server_stream, 0),
("get-stream-peer", get_stream_peer, 2), ("get-stream-peer", get_stream_peer, 2),
("shutdown-input-stream", shutdown_input_stream, 0),
]; ];
for f in fns { for f in fns {
r.define_func( r.define_func(

View file

@ -86,6 +86,16 @@ impl Stream {
f(&mut self.extra); f(&mut self.extra);
self 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 { 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 impl<T> From<T> for StreamType
where where
T: Fn(&mut Stack) -> Result<Stream, Error> + Sync + Send + 'static, 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 { pub fn write_stream(stack: &mut Stack) -> OError {
require_on_stack!(id, Mega, stack, "write-stream"); 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| { let stream = runtime(|rt| {
rt.get_stream(id as u128) rt.get_stream(id as u128)
.ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}")))) .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( stack.push(
Value::Mega( Value::Mega(
stream stream
.lock() .lock()
.write(&fixed[..]) .write(&a)
.map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))? as i128, .map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))? as i128,
) )
.spl(), .spl(),
@ -182,21 +196,14 @@ pub fn write_stream(stack: &mut Stack) -> OError {
pub fn write_all_stream(stack: &mut Stack) -> OError { pub fn write_all_stream(stack: &mut Stack) -> OError {
require_on_stack!(id, Mega, stack, "write-all-stream"); 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| { let stream = runtime(|rt| {
rt.get_stream(id as u128) rt.get_stream(id as u128)
.ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}")))) .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 stream
.lock() .lock()
.write_all(&fixed[..]) .write_all(&a)
.map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))?; .map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))?;
black_box(&stream.lock_ro()._writer_storage); black_box(&stream.lock_ro()._writer_storage);
Ok(()) Ok(())
@ -219,12 +226,13 @@ pub fn flush_stream(stack: &mut Stack) -> OError {
pub fn read_stream(stack: &mut Stack) -> OError { pub fn read_stream(stack: &mut Stack) -> OError {
require_on_stack!(id, Mega, stack, "read-stream"); require_on_stack!(id, Mega, stack, "read-stream");
let array = stack.pop(); 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"); 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()]; let mut vec = vec![0; a.len()];
stack.push( stack.push(
Value::Mega( Value::Mega(
@ -242,6 +250,19 @@ pub fn read_stream(stack: &mut Stack) -> OError {
.collect::<Vec<_>>(), .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); stack.push(array);
Ok(()) Ok(())
} }
@ -249,12 +270,13 @@ pub fn read_stream(stack: &mut Stack) -> OError {
pub fn read_all_stream(stack: &mut Stack) -> OError { pub fn read_all_stream(stack: &mut Stack) -> OError {
require_on_stack!(id, Mega, stack, "read-all-stream"); require_on_stack!(id, Mega, stack, "read-all-stream");
let array = stack.pop(); 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"); 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()]; let mut vec = vec![0; a.len()];
stream stream
.lock() .lock()
@ -266,6 +288,13 @@ pub fn read_all_stream(stack: &mut Stack) -> OError {
.collect::<Vec<_>>(), .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); stack.push(array);
Ok(()) Ok(())
} }
@ -276,6 +305,16 @@ pub fn close_stream(stack: &mut Stack) -> OError {
Ok(()) 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> { pub(super) fn stream_file(stack: &mut Stack) -> Result<Stream, Error> {
let truncate = stack.pop().lock_ro().is_truthy(); let truncate = stack.pop().lock_ro().is_truthy();
require_on_stack!(path, Str, stack, "FILE new-stream"); 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() { 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]) let mut command = process::Command::new(&args[0])
.args(&args[1..]) .args(&args[1..])

120
test.spl
View file

@ -1,10 +1,12 @@
[ [
"#stream.spl" import "spl/stream.spl" import
"#http.spl" import "spl/http.spl" import
"#messaging.spl" import "spl/messaging.spl" import
"#server.spl" import "spl/server.spl" import
"#time.spl" import "spl/time.spl" import
"#httpserver/base.spl" import "spl/httpserver/base.spl" import
"spl/linkedlist.spl" import
"spl/json.spl" import
"SPL tester" =program-name "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; "hello! this is a test of URL encoding!" net:http:urlencode dup println;
net:http:urldecode 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 { ] dup :len 0 eq not if {
"" println "" println
"!! something went wrong somewhere. the stack is not empty." println "!! something went wrong somewhere. the stack is not empty." println
dyn-__dump dyn-__dump
} }
"you now have a chance to connect too: localhost :4075 :4076 - stopping in 5 seconds..." println;
5000 time:sleep;
100 100
} }

View file

@ -1 +0,0 @@
hi