Compare commits

...

45 commits
v0.3.1 ... main

Author SHA1 Message Date
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
e48f06a8b5 bump version to v0.4.0 2024-09-29 21:08:53 +02:00
535e4816f8 add urlencoding 2024-09-29 20:53:01 +02:00
b454fe9dee improve httpserver, add httpserver/static.spl 2024-09-23 15:45:52 +02:00
416a310072 [http/server] add query handling, [std] make or operator smarter 2024-09-20 12:43:25 +02:00
706c4f023b
allow stdlib files to use local paths for eachother 2024-09-19 21:30:46 +02:00
d4dc588c35 allow servers to know who their client is 2024-09-12 19:18:20 +02:00
89d14146be allow getting peer in tcp 2024-09-12 16:27:44 +02:00
2446272181 add http server 2024-09-12 15:17:42 +02:00
6f2777b83a fix and improve some stdlib functions, add readf 2024-09-12 15:17:32 +02:00
fd41c46cdd some speed improvements, make fforeach more useful 2024-09-10 11:01:21 +02:00
15ae8622e1 fix string ops, add string find, fix server.spl not being embedded, move stdlib to spl/, fix a null identity loss 2024-09-09 19:58:59 +02:00
bb4d163b40 delay so other clients can connect to tcp server in test.spl 2024-09-09 17:04:35 +02:00
f86c55dd83 add tcp server 2024-09-09 16:31:35 +02:00
a1f5941c1a improve speed, add time.spl, improve isbpl.spl, add inlining support 2024-09-09 14:37:56 +02:00
6d78b276d4 fix pure.spl 2024-09-08 14:37:32 +02:00
0fb01cfb20 add pure.spl to stdlib 2024-09-08 14:29:01 +02:00
2f8c50e3f0 add threads, add pure.spl, which reimplements some of std_fns.rs in pure spl 2024-09-08 14:27:46 +02:00
79ee784a5b update readme 2024-09-08 12:52:23 +02:00
2024677225 update version 2024-09-08 12:52:23 +02:00
89e6eff198 replace function <{ args } with function<args> 2024-09-08 12:52:23 +02:00
2574165cd1 Add LICENSE 2024-09-06 22:40:06 +02:00
36 changed files with 2161 additions and 364 deletions

9
Cargo.lock generated
View file

@ -8,12 +8,6 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "428f6ba17d0c927e57c15a86cf5d7d07a2f35b3fbf15b1eb36b7075459e150a3"
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "readformat"
version = "0.1.2"
@ -22,9 +16,8 @@ checksum = "b03f7fbd470aa8b3ad163c85cce8bccfc11cc9c44ef12da0a4eddd98bd307352"
[[package]]
name = "spl"
version = "0.3.0"
version = "0.4.0"
dependencies = [
"multicall",
"once_cell",
"readformat",
]

View file

@ -1,6 +1,6 @@
[package]
name = "spl"
version = "0.3.1"
version = "0.4.0"
edition = "2021"
description = "Stack Pogramming Language: A simple, concise scripting language."
license = "MIT"
@ -9,5 +9,4 @@ authors = ["TudbuT"]
[dependencies]
readformat = "0.1"
once_cell = "1.17"
multicall = "0.1"

7
LICENSE Normal file
View file

@ -0,0 +1,7 @@
Copyright 2024 TudbuT <legal@mail.tudbut.de>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -10,7 +10,7 @@ func main { mega | with args ;
{ str | " " concat } swap:map
&print swap:foreach
"" println
println <{ "and with that, we're done" }
println<"and with that, we're done">
0
}
```
@ -177,19 +177,19 @@ func main { mega | with args ;
```
- SPL actually isn't fully concatenative. It supports postfix arguments as well:
```js
println <{ "and with that, we're done" }
println<"and with that, we're done">
```
This is actually not a special interpreter feature, more so is it a special
lexer feature. This is 100% equivalent with the non-postfix version, where the
string is right before the `println`.
The same can be done for object calls. Let's rewrite the previous code with
postfix:
prefix notation:
```js
Range:new <{ 0 5 }
Range:new<0 5>
:iter
:map <{ { | 5 * } }
:foreach <{ { | _str println } }
:map<{ | 5 * }>
:foreach<{ | _str println }>
```
I lied. This is now no longer 100% equivalent. Let's look at what happens
@ -261,4 +261,4 @@ 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.
If anyone knows how to do this properly, I'd REALLY appreciate a PR or issue explaining it.

61
benchmark.spl Normal file
View file

@ -0,0 +1,61 @@
"#time.spl" import
func main { mega | with args ;
def begin
def end
"[-] spl benchmark v0.1.0" println
"[0] benchmarking while loop with variable 0..100000" println
"==> bgin at " print time:unixms dup =begin println
def i 0 =i
while { i 100000 lt } {
i ++ =i
}
"==> done at " print time:unixms dup =end println
"==> in " print end begin - println
"[1] benchmarking foreach on 100000" println
"==> bgin at " print time:unixms dup =begin println
{ | pop } 100000 :foreach
"==> done at " print time:unixms dup =end println
"==> in " print end begin - println
"[2] benchmarking fast foreach on 100000" println
"==> bgin at " print time:unixms dup =begin println
{ | pop } 100000 :fforeach
"==> done at " print time:unixms dup =end println
"==> in " print end begin - println
"[3] benchmarking foreach on Range 100000..200000" println
"==> bgin at " print time:unixms dup =begin println
100000 200000 Range:new
:iter
:foreach <{ | with i ; }>
"==> done at " print time:unixms dup =end println
"==> in " print end begin - println
"[4] benchmarking manual multiply of 100000 x 5" println
"==> bgin at " print time:unixms dup =begin println
def i 0 =i
def n 0 =n
while { i 100000 lt } {
i ++ =i
n 5 + =n
}
" -> n = " print n println
"==> done at " print time:unixms dup =end println
"==> in " print end begin - println
"[5] benchmarking 10000 array adds" println
"==> bgin at " print time:unixms dup =begin println
def i 0 =i
def arr 0 anew =arr
while { i 10000 lt } {
i awrap arr aadd =arr
i ++ =i
}
"==> done at " print time:unixms dup =end println
"==> in " print end begin - println
0
}

View file

@ -2,7 +2,7 @@
func main { mega | with args ;
[ "sudo" "mkdir" "/usr/lib/spl" ] command-wait;
[ "sh" "-c" "sudo cp *.spl /usr/lib/spl" ] command-wait;
[ "sh" "-c" "sudo cp -r spl/* /usr/lib/spl" ] command-wait;
[ "cargo" "build" "--release" ] command-wait;
[ "sudo" "rm" "/bin/spl" ] command-wait;
[ "sudo" "cp" "target/release/spl" "/bin" ] command-wait;

View file

@ -1,29 +0,0 @@
func main { mega | with args ;
"Welcome to the SPL REPL!" println
"Enter any code after the cursor to execute it.\n" println
"REPL" =program-name
while { 1 } {
catch {
" > " print readln dyn-read exec2 "\n" print
}
{ with err ;
err:message dup null eq if {
pop
"Uncaught error."
} err:trace
with msg trace ;
program-name dup if {
program-name print " panicked at:" println
} not if {
"Program panicked at:" println
}
&println trace:foreach
"\nPanic message:" println
" " print msg println
"\nRecovering." 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

@ -1,5 +1,5 @@
"#stream.spl" import
"#net.spl" import
"stream.spl" import
"net.spl" import
"http" net:register
@ -7,6 +7,44 @@ construct net:http namespace {
Request
Response
help
;
register { | with name this ;
name "net:http" register-field
}
urlencode { str | with str this ;
[ { | with x ;
x
x "0" :_char lt not
x "9" :_char gt not and
x "a" :_char lt not
x "z" :_char gt not and or
x "A" :_char lt not
x "Z" :_char gt not and or
not
if {
pop
"%" :_char
x _mega:_str_radix<16> _array =x
x:len 1 eq if { "0" :_char }
x:to-stack
}
} str _array :foreach ] _str
}
urldecode { str | with str this ;
str _array =str
[
def i 0 =i
while { i str:len lt } {
i str:get dup "%" :_char eq if {
pop
i ++ =i
i i 2 + str:sub _str :_mega_radix<16> _int
i ++ =i
}
i ++ =i
}
] _str
}
}
construct net:http:Request {

164
spl/httpserver/base.spl Normal file
View file

@ -0,0 +1,164 @@
"../http.spl" import
"../server.spl" import
"Server" net:http:register
"server" net:http:register
construct net:http:Server {
ifaddr
port
stream
;
construct { this | with ifaddr port this ;
ifaddr this:=ifaddr;
port this:=port;
ifaddr port net:server:Types:tcp:create this:=stream;
this
}
accept { net:http:server:Request | with this ;
this this:stream:accept net:http:server:Request:new
}
close { | with this ; this:stream:close; }
}
construct net:http:server namespace {
Request
;
register { | with name this ;
name "net:http:server" register-field
}
}
construct net:http:server:Request {
server
stream
head
body
method raw-path version
path query
headers
wrote-body
;
construct { this | with server stream this ;
server this:=server
stream this:=stream
0 banew this:=head
0 banew this:=body
this
}
read-head { this | with this ;
def read
def buf 1024 banew =buf
def found
while {
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>: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
buf:sub<found 4 + buf:len> this:=body
this
}
parse-head { this | with this ;
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
} pop
MicroMap:new this:=query
def p "{}?{}" this:raw-path:readf =p
this:raw-path
p if {
{ | with attrib ;
"{}={}" attrib:readf dup if {
dup:to-stack this:query:set;
} not if {
attrib "" this:query:set;
}
} "&" p:1:split:foreach
pop p:0
} this:=path
MicroMap:new this:=headers
iter:foreach<{ | with header ;
header:readf<"{}: {}"> dup if {
dup:to-stack swap:lowercase swap this:headers:set;
} pop
}>
this
}
head-str { str | with this ;
this:method
" " concat
this:raw-path concat
" HTTP/" concat
this:version concat
"\r\n" concat
this:headers:foreach<{ | :to-stack ": " swap concat concat concat "\r\n" concat }>
"\r\n" concat
}
read-body { this | with this ;
this:headers:get<"content-length"> dup if { _mega with content-length ;
def read
def buf 1024 banew =buf
while {
this:body:len content-length lt read and
} {
this:body buf:sub<0 read> aadd this:=body
buf this:stream:read pop =read
}
null
} pop
this
}
read { this | with this ;
this:read-head:parse-head:read-body
}
writeln { this | with line this ;
line "\r\n" concat :to-bytes this:stream:write-exact;
this
}
write-head { this | with status-code status-string this ;
"HTTP/1.0 " status-code _str concat " " concat status-string concat this:writeln
}
write-ok { this | with this ;
200 "OK" this:write-head
}
write-header { this | with header value this ;
header ": " concat value _str concat this:writeln
}
write-content-type { this | with ct this ;
"Content-Type" ct this:write-header
}
write-str-body { this | with body this ;
body:to-bytes this:write-body
}
write-html-body { this | with body this ;
"text/html" this:write-content-type;
body:to-bytes this:write-body
}
write-body { this | with body this ;
"Content-Length" body:len this:write-header;
"" this:writeln;
1 this:=wrote-body;
body this:stream:write-exact;
this
}
finish { | with this ;
this:wrote-body not if {
0 banew this:write-body;
}
this:stream:close;
}
is-open { bool | with this ;
this:wrote-body not
}
}

56
spl/httpserver/static.spl Normal file
View file

@ -0,0 +1,56 @@
"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 ;
this:cached-files dup not if { pop MicroMap:new dup this:=cached-files }
}
}
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 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 StreamTypes:file:create<0>:read-to-end<this:get-bufsize> dup this:server:cached-files:set;<filepath>
}
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
}
serve-string { this | with string path type this ;
this:path path eq if {
string this:write-ok:write-content-type<type>:write-str-body:finish;
}
this
}
serve-html { this | with this ; "text/html" this:serve-file }
serve-html-cached { this | with this ; "text/html" this:serve-file-cached }
serve-html-string { this | with this ; "text/html" this:serve-string }
}
include net:http:server:_static_ext_server in net:http:Server
include net:http:server:_static_ext_Request in net:http:server:Request

View file

@ -1,3 +1,4 @@
"#time.spl" import
native engage-compatibility-mode
func # { pop }
func puts { print }
@ -5,6 +6,8 @@ func fcall { call }
func strconcat { concat }
func aget { swap :get }
func stoi { _mega }
func stol { _mega }
func _int { _mega }
func itos { _str }
def argv'pre-isbplmod &argv =argv'pre-isbplmod
func argv {
@ -13,6 +16,22 @@ func argv {
args nargs 2 0 nargs:len acopy
nargs
}
func eputs {
print
}
def import-transformers List:new =import-transformers
func isbplmod'import-transform { with s ;
"spl: [compat] transforming import " s concat println
s:_char "#" :_char eq if {
"#nop.spl" =s
}
{ with tf ;
s tf:call =s
} import-transformers:foreach
"spl: [compat] transformed to " s concat println
s
}
func inc {
"isbpl.spl: ISBPL code tried to call inc, an untranslatable function" println
pop
@ -21,3 +40,11 @@ func dec {
"isbpl.spl: ISBPL code tried to call inc, an untranslatable function" println
pop
}
func getms {
0 time:unixms
}
func sleep {
time:sleep
}
func main { }

View file

@ -11,13 +11,14 @@ construct _Iter {
arr
}
foreach { | with callable this ;
def itm
while { this:next dup =itm null eq not } {
itm callable call
!!-
while { !!- this:next dup null eq not } {
!!- callable callp
}
pop
}
collect { array | with this ;
[ { any | } this:foreach ]
[ { | } this:foreach ]
}
map { MapIter | with map-function this ;
map-function this MapIter:new
@ -41,7 +42,7 @@ construct _Iter {
join { str | with separator this ;
{ str | with accum item ;
accum _str separator item _str concat concat
} this:reduce:calculate
} this:reduce:calculate "" or
}
filter { FilterIter | with filter this ;
filter this FilterIter:new

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

@ -1,4 +1,4 @@
"messaging bus, aka event bus"
"messaging bus, aka event bus";
construct messaging namespace {
Message

View file

@ -4,6 +4,6 @@
construct net namespace {
;
register { | with name this ;
name "net" this register-field
name "net" register-field
}
}

49
spl/pure.spl Normal file
View file

@ -0,0 +1,49 @@
"stream.spl" import
"uses less native functions where possible";
func pop { | with _ ; }
func dup { x x | with x ;
x x
}
func swap { b a | with a b ;
b a
}
func not { !x | with x ;
1
x if { pop 0 }
}
func and { a&&b | with a b ;
0
a if { b if { pop 1 } }
}
func or { a||b | with a b ;
0
a if { pop 1 }
b if { pop 1 }
}
func alit-end { array | with type ;
def array 0 anew =array
while { dup type eq not } {
awrap array aadd =array
}
pop array
}
func read-file { str | with path ;
def stream path 0 StreamTypes:file:create =stream
1024 stream:read-to-end:to-str
}
func acopy { array | with from to idxsrc idxdst len ;
len:foreach<{ | with i ;
i idxsrc + from:get i idxdst + to:set;
}>
to
}

70
spl/repl.spl Normal file
View file

@ -0,0 +1,70 @@
"fast.spl" import
func main { mega | with args ;
"Welcome to the SPL REPL!" 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 {
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 {
pop
"Uncaught error."
} err:trace
with msg trace ;
program-name dup if {
program-name print " panicked at:" println
} not if {
"Program panicked at:" println
}
&println trace:foreach
"\nPanic message:" println
" " print msg println
"\nRecovering." println
}
}
}
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
}

63
spl/server.spl Normal file
View file

@ -0,0 +1,63 @@
"net.spl" import
"stream.spl" import
"server" net:register
construct net:server namespace {
ServerStream
Type
_Types
types
;
Types { _Types | with this ;
this:_Types:new
}
register-type { | with id this ;
this:types id awrap aadd this:=types
id this:_Types dyn-def-field;
}
}
0 anew net:server:=types
construct net:server:Type {
id
;
construct { this | with id this ;
id this:=id
this
}
create { ServerStream | with this ;
this:id net:server:ServerStream:new
}
}
construct net:server:_Types {
;
construct { this | with this ;
{ | with type ;
"type net:server:Type:new this:=<type>";
(type net:server:Type:new) (this ("=" type concat)) dyn-objcall
} net:server:types:foreach;
this
}
}
"tcp" net:server:register-type
construct net:server:ServerStream {
id
;
construct { this | with type this ;
type new-server-stream this:=id
this
}
accept { Stream | with this ;
def stream Stream:mkinstance =stream
this:id accept-server-stream stream:=id
stream
}
close { | with this ;
this:id close-server-stream;
}
}

View file

@ -8,11 +8,11 @@ def program-name
def print'pre-std &print =print'pre-std
func print { |
_str print'pre-std call
_str !!- print'pre-std inline-callable
}
func println { |
print "\n" print
_str !!- "\n" concat print
}
construct error {
@ -30,58 +30,60 @@ construct FrameInfo {
construct _str_ext {
;
new { any | with this ;
null clone this settype:construct
new { any | :mkinstance:construct }
mkinstance { any | with this ;
null clone this settype
}
to-bytes { [int] | str-to-bytes }
split { str | with splitter this ;
def bytes splitter:to-bytes =bytes
def iter this:to-bytes:iter =iter
def item 0 =item
[ while { item null eq not } {
def match 0 =match
[
while { match bytes:len eq not } {
iter:next =item
item null eq if {
3 stop
}
item dup (match bytes:get) eq dup if {
match ++ =match
} not if {
0 =match
}
}
{ | pop pop } match:foreach
] _str
} ]
splitter:to-bytes this:to-bytes:split:map<{ | bytes-to-str }>
}
replace { str | with replacee replacement this ;
def bytes replacee:to-bytes =bytes
def iter this:to-bytes:iter =iter
def item 0 =item
[ while { item null eq not } {
def match 0 =match
while { match bytes:len eq not } {
iter:next =item
item null eq if {
3 stop
}
item dup (match bytes:get) eq dup if {
match ++ =match
} not if {
0 =match
}
}
{ | pop pop } match:foreach
match bytes:len eq if {
replacement _array :to-stack
}
} ] _str
replacee:to-bytes replacement:to-bytes this:to-bytes:replace:to-str
}
find { idx | with search this ;
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
}
readf { [str] | with pat this ;
!!- pat this str-readf
}
readf1 { str | with pat this ;
!!- pat this:readf 0:get
}
uppercase { str | with this ;
this _array :cmap<{ | with chr ;
chr
chr "a" :_char lt not chr "z" :_char gt not and if {
pop (chr "a" :_char -) "A" :_char +
}
}> _str
}
lowercase { str | with this ;
this _array :cmap<{ | with chr ;
chr
chr "A" :_char lt not chr "Z" :_char gt not and if {
pop (chr "A" :_char -) "a" :_char +
}
}> _str
}
_mega_radix { mega | with radix this ;
this radix str-to-mega-radix
}
} include _str_ext in str
construct _mega-ext {
@ -93,12 +95,30 @@ construct _mega-ext {
mswap { .. | mswap }
foreach { | with callable this ;
def i 0 =i
while { i this lt } { i callable call i ++ =i }
while { !!- i this lt } { !!- i callable call i 1 + =i }
}
fforeach { | with callable this ;
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 {
@ -119,13 +139,84 @@ construct _array-ext {
i ++ =i
}
}
foreach { | with callable this ;
foreach { | with callable this ;
def i 0 =i
while { i this:len lt } { i this:get callable call i ++ =i }
}
map { | with callable this ;
def i 0 =i
while { i this:len lt } { i this:get callable call i this:set; i ++ =i }
this
}
cmap { | with callable this ;
this clone =this
def i 0 =i
while { i this:len lt } { i this:get callable call i this:set; i ++ =i }
this
}
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
[ [ while { i this:len lt } {
def match 0 =match
while { match i + this:len lt match splitter:len lt and } {
i match + this:get (match splitter:get) eq dup if {
match ++ =match
} not if {
0 =match
3 stop
}
}
i this:get
match splitter:len eq if {
pop ] [
i match + 1 - =i
}
i ++ =i
} ] ]
}
replace { arr | with replacee replacement this ;
def i 0 =i
[ while { i this:len lt } {
def match 0 =match
while { match i + this:len lt match replacee:len lt and } {
i match + this:get (match replacee:get) eq dup if {
match ++ =match
} not if {
0 =match
3 stop
}
}
i this:get
match replacee:len eq if {
pop replacement:to-stack
i match + 1 - =i
}
i ++ =i
} ]
}
find { idx | with search this ;
def i 0 =i
while { i this:len lt } {
def match 0 =match
while { match i + this:len lt match search:len lt and } {
i match + this:get (match search:get) eq dup if {
match ++ =match
} not if {
0 =match
3 stop
}
}
match search:len eq if {
i
4 stop
}
i ++ =i
}
null
}
starts-with { bool | with beginning this ;
this:len beginning:len lt if {
@ -134,11 +225,18 @@ 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
}
=last { | with this ;
this:len -- this:set
this:len -- this:set;
}
0 { any | with this ;
0 this:get
@ -170,7 +268,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
@ -218,6 +318,7 @@ construct List {
to-stack { .. | :array:to-stack }
to-str { str | :array:to-str }
sub { [any] | :array:sub }
clear { | :=array<0 anew> }
}
construct _GrowingArray {
;
@ -275,7 +376,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
@ -287,6 +388,7 @@ construct _IterableArray {
include _Iter in ArrayIter
include _IterableArray in List
include _IterableArray in array
include _IterableArray in bytearray
construct MicroMap {
pairs
@ -331,6 +433,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 {
@ -365,26 +481,47 @@ construct Range {
}
construct RangeIter {
range
upper step
idx
;
construct { this | with range this ;
range this:=range
0 this:=idx
range:lower this:=idx
range:upper this:=upper
range:step this:=step
this
}
next { mega | with this ;
this:idx dup ++ this:=idx this:range:item
!!-
this:idx dup this:step + this:=idx
dup this:upper lt not if {
pop null
}
}
}
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;
@ -398,9 +535,9 @@ func concat { str | with a b ;
func nconcat { str | with amt ;
_array
(amt 1 -):foreach <{ { | pop
(amt 1 -):foreach<{ | pop
swap _array swap aadd
} }
}>
_str
}
@ -475,6 +612,10 @@ func assert-eq { any any | with a b ;
a b
}
func awrap { array | with item ;
item 1 anew =item 0 item:set; item
}
func [ { shadow |
"array" "shadow" settype
}
@ -540,8 +681,21 @@ func update-types { |
update-types
"Adds a field to a namespace and initially sets it to the field's name.";
func register-field { | with field-name namespace-name namespace ;
field-name namespace-name dyn-def-field;
namespace namespace-name settype ("=" namespace-name concat) dyn-call
}
func register-field { | with field-name namespace-name ;
def namespace-path
def iter
namespace-name:split<":"> =namespace-path;
namespace-path:iter =iter
field-name namespace-name dyn-def-field; "adds the desired field to the namespace construct";
(
iter:next dyn-call
iter:foreach<&dyn-objcall>
) namespace-name settype "updates and gets the namespace object";
("=" namespace-path:last concat) namespace-path:=last;
namespace-path:iter =iter
iter:next dyn-call
iter:foreach<&dyn-objcall>
}

View file

@ -15,28 +15,26 @@ construct Stream {
this
}
read-one { mega | with this ;
def buf 1 anew =buf
while { buf this:id read-stream not } { }
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 this:id read-stream 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
0 banew while { buf this:id read-stream pop _mega dup =read } {
(0 read buf:sub) aadd
}
full
}
write { mega | with buf this ;
buf this:id write-stream
@ -47,9 +45,29 @@ 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
}
peer { StreamPeer | with this ;
this:id get-stream-peer StreamPeer:new
}
}
construct StreamPeer {
ip
port
;
construct { this | with ip port this ;
ip this:=ip
port this:=port
this
}
_str { str | with this ;
this:ip ":" concat this:port _str concat
}
}
construct StreamType {
@ -66,7 +84,7 @@ construct StreamType {
def stream-types 0 anew =stream-types
construct _StreamType {
construct _StreamTypes {
;
construct { this | with this ;
{ | with type ;
@ -79,7 +97,7 @@ construct _StreamType {
func register-stream-type { | with id ;
stream-types [ id ] aadd =stream-types
id _StreamType dyn-def-field
id _StreamTypes dyn-def-field
}
"tcp" register-stream-type
@ -87,6 +105,6 @@ func register-stream-type { | with id ;
"file" register-stream-type
"cmd" register-stream-type
func StreamTypes { _StreamType |
_StreamType:new
func StreamTypes { _StreamTypes |
_StreamTypes:new
}

9
spl/time.spl Normal file
View file

@ -0,0 +1,9 @@
construct time namespace {
;
unixms { mega | with this ;
0 sleeptime
}
sleep { mega | with this ; _mega sleeptime }
}

View file

@ -3,7 +3,7 @@ mod compat;
use std::{mem, sync::Arc};
use crate::runtime::*;
use compat::match_compat;
use compat::{match_compat, transform_compat};
use readformat::*;
#[derive(Debug, PartialEq, Eq)]
@ -24,6 +24,15 @@ pub fn lex(compat: bool, input: String) -> Result<Words, LexerError> {
fn read_block(
str_words: &[String],
isfn: bool,
compat: bool,
) -> Result<(Option<u32>, Words, usize), LexerError> {
read_block_dyn(str_words, isfn, "}".to_owned(), compat)
}
fn read_block_dyn(
str_words: &[String],
isfn: bool,
endword: String,
mut compat: bool,
) -> Result<(Option<u32>, Words, usize), LexerError> {
if str_words.is_empty() {
@ -48,7 +57,11 @@ fn read_block(
rem = Some(r as u32);
}
while i < str_words.len() {
let word = str_words[i].to_owned();
let word = if !compat {
str_words[i].to_owned()
} else {
transform_compat(&str_words[i])
};
if compat && match_compat(word.to_owned(), str_words, &mut words, &mut i) {
continue;
}
@ -108,11 +121,24 @@ fn read_block(
run_as_base: false,
}))))
}
// <| .. > 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[0..2] == "!{" => {
words.push(Word::Const(Value::Str(x[2..].to_owned())));
}
"<{" => {
let block = read_block(&str_words[i + 1..], false, compat)?;
"<" => {
let block = read_block_dyn(&str_words[i + 1..], false, ">".to_owned(), compat)?;
i += block.2 + 1;
let mut block = block.1.words;
match words.remove(words.len() - 1) {
@ -227,12 +253,24 @@ fn read_block(
}
words.push(Word::Key(Keyword::With(vars)));
}
"}" => {
"inline-callable" => {
words.push(Word::Key(Keyword::InlineCallable));
}
"!!-" => {
words.push(Word::Key(Keyword::InlineStart));
}
"-!!" => {
words.push(Word::Key(Keyword::InlineEnd));
}
x if x == endword => {
break;
}
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) =>
@ -286,7 +324,11 @@ fn read_block(
Ok((rem, Words { words }, i))
}
fn parse(input: String) -> Vec<String> {
pub fn parse(mut input: String) -> Vec<String> {
if input.starts_with("#!") {
input = input.split_off(input.find('\n').expect("cannot have #! without newline"));
}
let mut words = Vec::new();
let mut s = String::new();
@ -346,6 +388,17 @@ fn parse(input: String) -> Vec<String> {
if c == '(' || c == ')' {
continue;
}
if c == '<' || c == '>' {
if s.is_empty() {
words.push(c.to_string());
continue;
}
words.push(s);
s = String::new();
was_in_string = false;
words.push(c.to_string());
continue;
}
if c == ' ' || c == '\t' {
if s.is_empty() {
continue;

View file

@ -1,5 +1,12 @@
use crate::Word;
pub(crate) fn transform_compat(word: &str) -> String {
match word {
"try" => "catch".to_owned(),
_ => word.to_owned(),
}
}
pub(crate) fn match_compat(
word: String,
str_words: &[String],
@ -15,6 +22,7 @@ pub(crate) fn match_compat(
true
}
"dec" => {
eprintln!("spl: [compat] transforming dec call");
eprintln!("spl: [compat] transforming dec call");
words.push(Word::Call("--".to_owned(), false, 0));
words.push(Word::Call("=".to_owned() + &str_words[*i - 1], false, 0));
@ -22,8 +30,9 @@ pub(crate) fn match_compat(
true
}
"include" => {
// TODO: translate some stdlib components?
words.push(Word::Call("isbplmod'import-transform".to_owned(), false, 0));
words.push(Word::Call("import".to_owned(), false, 0));
*i += 1;
true
}
_ => false,

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

@ -1,4 +1,6 @@
use spl::{lex, oxidizer::RustAppBuilder, start_file_in_runtime, Runtime, SetRuntime};
use spl::{
lex, oxidizer::RustAppBuilder, sasm::sasm_write, start_file_in_runtime, Runtime, SetRuntime,
};
use std::{env::args, fs};
@ -6,9 +8,14 @@ fn main() {
Runtime::new().set();
let mut args = args().skip(1);
let arg = &args.next().unwrap_or("#repl.spl".to_owned());
if arg == "--build" || arg == "--run" || arg == "--buildrun" {
if arg == "--build" || arg == "--run" || arg == "--buildrun" || arg == "--debug" {
let file = args.next().unwrap();
let data = fs::read_to_string(file.clone()).expect("unable to read specified file");
if arg == "--debug" {
println!("words: {}", spl::parse(data.clone()).join(" "));
println!("{}", sasm_write(lex(false, data).unwrap()));
return;
}
let build_only = arg == "--build";
if build_only {
println!("Building SPL with specified natives file...");

View file

@ -46,6 +46,15 @@ pub fn runtime_mut<T>(f: impl FnOnce(RwLockWriteGuard<Runtime>) -> T) -> T {
})
}
pub fn fork_runtime() -> Arc<Mut<Runtime>> {
RUNTIME.with(|rt| {
rt.borrow_mut()
.as_mut()
.expect("no runtime (use .set)")
.clone()
})
}
pub fn get_type(name: &str) -> Option<AMType> {
runtime(|rt| rt.get_type_by_name(name))
}
@ -63,6 +72,7 @@ pub struct Runtime {
types_by_id: HashMap<u32, AMType>,
next_stream_id: u128,
streams: HashMap<u128, Arc<Mut<Stream>>>,
server_streams: HashMap<u128, Arc<Mut<ServerStream>>>,
pub embedded_files: HashMap<&'static str, &'static str>,
pub native_functions: HashMap<&'static str, (u32, FuncImpl)>,
}
@ -92,6 +102,7 @@ impl Runtime {
types_by_id: HashMap::new(),
next_stream_id: 0,
streams: HashMap::new(),
server_streams: HashMap::new(),
embedded_files: HashMap::new(),
native_functions: HashMap::new(),
};
@ -104,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
}
@ -151,6 +163,23 @@ impl Runtime {
self.streams.remove(&id);
}
pub fn register_server_stream(
&mut self,
stream: ServerStream,
) -> (u128, Arc<Mut<ServerStream>>) {
let id = (self.next_stream_id, self.next_stream_id += 1).0;
self.server_streams.insert(id, Arc::new(Mut::new(stream)));
(id, self.server_streams.get(&id).unwrap().clone())
}
pub fn get_server_stream(&self, id: u128) -> Option<Arc<Mut<ServerStream>>> {
self.server_streams.get(&id).cloned()
}
pub fn destroy_server_stream(&mut self, id: u128) {
self.server_streams.remove(&id);
}
pub fn load_native_function(&self, name: &str) -> &(u32, FuncImpl) {
self.native_functions.get(name).unwrap_or_else(|| {
panic!(
@ -445,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(),
@ -461,6 +493,11 @@ impl Stack {
r
}
pub fn fast_call(&mut self, func: &AFunc) -> OError {
let r = func.to_call.call(self);
r
}
pub fn get_func(&self, name: String) -> Result<AFunc, Error> {
let mut frame = self.frames.last().unwrap();
loop {
@ -482,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_frame().get_var(tmpname.clone(), stack)?);
Ok(())
}))),
run_as_base: false,
@ -498,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.get_frame().set_var(tmpname.clone(), v, stack)
}))),
run_as_base: false,
fname: Some("RUNTIME".to_owned()),
@ -640,6 +675,21 @@ pub enum Keyword {
/// equivalent to dyn-__dump
/// example: func main { int | "Hello, world!" dyn-__dump pop 0 }
Dump,
/// inline-call
///
/// Inlines a callable into the current function
/// equivalent to call
InlineCallable,
/// !!-
///
/// Makes future calls inline, not adding a new stack frame
/// no equivalent
InlineStart,
/// -!!
///
/// Stops making calls inline
/// no equivalent
InlineEnd,
/// def <name>
///
/// Defines a variable.
@ -723,6 +773,7 @@ pub enum Value {
Double(f64),
Func(AFunc),
Array(Vec<AMObject>),
ByteArray(Vec<u8>),
Str(String),
}
@ -750,6 +801,10 @@ impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(Value::Mega(a), Value::Mega(b)) => a.partial_cmp(b),
(Value::Long(a), Value::Long(b)) => a.partial_cmp(b),
(Value::Int(a), Value::Int(b)) => a.partial_cmp(b),
(Value::Double(a), Value::Double(b)) => a.partial_cmp(b),
(Value::Float(a), Value::Float(b)) => a.partial_cmp(b),
_ => panic!(),
}
}
@ -784,7 +839,7 @@ impl Words {
#[derive(Clone)]
pub enum FuncImpl {
Native(fn(&mut Stack) -> OError),
NativeDyn(Arc<Box<dyn Fn(&mut Stack) -> OError>>),
NativeDyn(Arc<Box<dyn (Fn(&mut Stack) -> OError) + Send + Sync>>),
SPL(Words),
}
@ -1014,6 +1069,7 @@ impl Object {
Value::Func(_) => true,
Value::Array(_) => true,
Value::Str(x) => !x.is_empty(),
Value::ByteArray(_) => true,
}
}
@ -1073,6 +1129,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.")
}),
@ -1135,20 +1192,35 @@ impl Words {
/// Executes the words. This does *not* create a new frame on the stack. Use [Stack::call] to
/// call and create a new frame.
pub fn exec(&self, stack: &mut Stack) -> OError {
for word in self.words.clone() {
let mut inline_calls = false;
for word in self.words.iter() {
match word {
Word::Key(x) => match x {
Keyword::Dump => println!("{stack}"),
Keyword::Def(x) => stack.define_var(x),
Keyword::InlineCallable => {
let Value::Func(f) = stack.pop().lock_ro().native.clone() else {
return Err(
stack.error(ErrorKind::InvalidCall("inline-callable".to_owned()))
);
};
stack.fast_call(&f)?;
}
Keyword::InlineStart => {
inline_calls = true;
}
Keyword::InlineEnd => {
inline_calls = false;
}
Keyword::Def(x) => stack.define_var(x.to_owned()),
Keyword::Func(name, rem, words) => stack.define_func(
name.clone(),
Arc::new(Func {
ret_count: rem,
to_call: FuncImpl::SPL(words),
ret_count: *rem,
to_call: FuncImpl::SPL(words.to_owned()),
origin: stack.get_frame(),
run_as_base: false,
fname: None,
name,
name: name.to_owned(),
}),
),
Keyword::Construct(name, fields, methods, is_namespace) => {
@ -1159,14 +1231,14 @@ impl Words {
let t = runtime_mut(|mut rt| {
rt.make_type(name.clone(), |mut t| {
for field in fields {
t.add_property(field, origin.clone())?;
t.add_property(field.to_owned(), origin.clone())?;
}
t.functions.extend(methods.into_iter().map(|(k, v)| {
(
k.clone(),
Arc::new(Func {
ret_count: v.0,
to_call: FuncImpl::SPL(v.1),
to_call: FuncImpl::SPL(v.1.to_owned()),
origin: origin.clone(),
run_as_base: false,
fname: None,
@ -1178,7 +1250,7 @@ impl Words {
})
})?;
let to_set: Object = if is_namespace {
let to_set: Object = if *is_namespace {
let mut obj: Object = Value::Null.into();
obj.kind = t.clone();
t.lock_ro().write_into(&mut obj);
@ -1207,13 +1279,14 @@ impl Words {
let rstack = &stack;
runtime(move |rt| {
rt.get_type_by_name(&tb)
.ok_or_else(|| rstack.error(ErrorKind::TypeNotFound(tb)))?
.ok_or_else(|| {
rstack.error(ErrorKind::TypeNotFound(tb.to_owned()))
})?
.lock()
.parents
.push(
rt.get_type_by_name(&ta)
.ok_or_else(|| rstack.error(ErrorKind::TypeNotFound(ta)))?,
);
.push(rt.get_type_by_name(&ta).ok_or_else(|| {
rstack.error(ErrorKind::TypeNotFound(ta.to_owned()))
})?);
Ok(())
})?;
}
@ -1262,7 +1335,7 @@ impl Words {
for var in vars.into_iter().rev() {
stack.define_var(var.clone());
let obj = stack.pop();
stack.set_var(var, obj)?;
stack.set_var(var.to_owned(), obj)?;
}
}
Keyword::ObjPush => {
@ -1286,7 +1359,7 @@ impl Words {
origin: stack.get_frame(),
run_as_base: false,
fname: None,
name,
name: name.to_owned(),
}),
)
}),
@ -1298,6 +1371,7 @@ impl Words {
stack.push(x.clone().ensure_init(stack).spl())
}
Word::Call(x, rem, ra) => {
let ra = *ra;
if option_env!("SPLDEBUG").is_some() {
println!("CALL({}) {x}", stack.len());
}
@ -1324,8 +1398,12 @@ impl Words {
}
stack.push(f.spl());
} else {
stack.call(&f)?;
if rem {
if inline_calls {
stack.fast_call(&f)?;
} else {
stack.call(&f)?;
}
if *rem {
for _ in 0..f.ret_count {
stack.pop();
}
@ -1333,6 +1411,7 @@ impl Words {
}
}
Word::ObjCall(x, rem, ra) => {
let ra = *ra;
let o = stack.peek();
let o = o.lock_ro();
let f0 = o.kind.lock_ro();
@ -1370,8 +1449,12 @@ impl Words {
stack.pop();
stack.push(f.spl())
} else {
stack.call(&f)?;
if rem {
if inline_calls {
stack.fast_call(&f)?;
} else {
stack.call(&f)?;
}
if *rem {
for _ in 0..f.ret_count {
stack.pop();
}

View file

@ -24,6 +24,9 @@ fn sasm_parse<'a>(line: &str, words: &mut Vec<Word>, lines: &mut impl Iterator<I
let line: Vec<_> = line.split(" ").collect();
match line[0] {
"dump" => words.push(Word::Key(Keyword::Dump)),
"inline-callable" => words.push(Word::Key(Keyword::InlineCallable)),
"inline-start" => words.push(Word::Key(Keyword::InlineStart)),
"inline-end" => words.push(Word::Key(Keyword::InlineEnd)),
"def" => words.push(Word::Key(Keyword::Def(line[1].to_owned()))),
"func" => words.push(Word::Key(Keyword::Func(
line[1].to_owned(),
@ -172,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" => {
@ -223,6 +245,9 @@ fn sasm_write_func(words: Words) -> String {
Keyword::Dump => {
output += "dump\n";
}
Keyword::InlineCallable => output += "inline-callable\n",
Keyword::InlineStart => output += "inline-start\n",
Keyword::InlineEnd => output += "inline-end\n",
Keyword::Def(x) => {
output += "def ";
output += &x;
@ -350,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,14 +1,17 @@
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,
thread,
time::{Duration, SystemTime},
};
use readformat::readf;
use crate::{dyn_fns, mutex::Mut, runtime::*, sasm::sasm_write, *};
#[macro_export]
@ -50,6 +53,13 @@ pub fn dup(stack: &mut Stack) -> OError {
Ok(())
}
pub fn dup2(stack: &mut Stack) -> OError {
let o = stack.peek();
stack.push(o.clone());
stack.push(o);
Ok(())
}
pub fn pop(stack: &mut Stack) -> OError {
stack.pop();
Ok(())
@ -85,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 {
@ -99,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()));
@ -112,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(())
}
@ -187,16 +239,9 @@ pub fn and(stack: &mut Stack) -> OError {
}
pub fn or(stack: &mut Stack) -> OError {
let a = stack.pop();
let b = stack.pop();
stack.push(
Value::Int(if a.lock_ro().is_truthy() || b.lock_ro().is_truthy() {
1
} else {
0
})
.spl(),
);
let a = stack.pop();
stack.push(if a.lock_ro().is_truthy() { a } else { b });
Ok(())
}
@ -316,6 +361,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(),
);
@ -337,6 +383,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(),
);
@ -358,6 +405,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(),
);
@ -379,6 +427,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(),
);
@ -400,6 +449,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(),
);
@ -422,6 +472,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(),
);
@ -462,6 +517,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(),
);
@ -601,23 +696,11 @@ 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()));
};
let fallback = s
.as_str()
.rsplit_once(|x| x == '/' || x == '#')
.map(|(.., x)| x)
.unwrap_or(s.as_str());
let fallback = runtime(|x| {
for (&p, &data) in &x.embedded_files {
if fallback == p {
return Some(data.to_owned());
}
}
None
});
if let Some(x) = s.strip_prefix('#') {
s = find_in_splpath(x).unwrap_or(x.to_owned());
} else if let Some(x) = s.strip_prefix('@') {
@ -633,7 +716,29 @@ pub fn import(stack: &mut Stack) -> OError {
.to_owned()
+ "/"
+ &s;
s = s.trim_start_matches("./").to_owned();
}
let mut fallback = s
.as_str()
.rsplit_once(|x| x == '#')
.map(|(.., x)| x.to_owned())
.unwrap_or(s.clone());
while fallback.contains("/../") {
let mut fb = readf("{}/../{}", &fallback).unwrap();
if let Some(x) = fb[0].rsplit_once('/') {
fallback = x.0.to_owned() + &fb[1];
} else {
fallback = fb.swap_remove(1);
}
}
let fallback = runtime(|x| {
for (&p, &data) in &x.embedded_files {
if fallback == p {
return Some(data.to_owned());
}
}
None
});
if stack.include_file(
&(*fs::canonicalize(s.clone())
.unwrap_or_else(|_| s.clone().into())
@ -746,29 +851,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(())
}
@ -777,7 +871,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;
@ -792,6 +887,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(())
}
@ -840,17 +943,212 @@ pub fn write_file_sasm(stack: &mut Stack) -> OError {
Ok(())
}
pub fn fork(stack: &mut Stack) -> OError {
require_on_stack!(callable, Func, stack, "fork");
let mut new_stack = stack.clone();
let rt = fork_runtime();
thread::spawn(move || {
rt.set();
if let Some(err) = new_stack.call(&callable).err() {
println!("{err:?}");
};
});
Ok(())
}
pub fn time(stack: &mut Stack) -> OError {
require_on_stack!(sleep_ms, Mega, stack, "time");
if sleep_ms != 0 {
thread::sleep(Duration::from_millis(sleep_ms as u64));
}
stack.push(
(SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis() as i128)
.spl(),
);
Ok(())
}
pub fn str_readf(stack: &mut Stack) -> OError {
require_on_stack!(string, Str, stack, "str-readf");
require_on_stack!(pat, Str, stack, "str-readf");
let Some(result) = readf(&pat, &string) else {
stack.push(Value::Null.spl());
return Ok(());
};
stack.push(Value::Array(result.into_iter().map(<String as SPL>::spl).collect()).spl());
Ok(())
}
pub fn str_to_mega_radix(stack: &mut Stack) -> OError {
require_int_on_stack!(radix, stack, "str-to-mega-radix");
require_on_stack!(str, Str, stack, "str-to-mega-radix");
let Ok(result) = i128::from_str_radix(&str, radix as u32) else {
stack.push(Value::Null.spl());
return Ok(());
};
stack.push(Value::Mega(result).spl());
return Ok(());
}
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");
// 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 {
mega = -mega;
result.push('-' as u32);
}
while mega != 0 {
let i = (mega % radix as i128) as u32;
result.push(if i < 10 { '0' as u32 } else { 'a' as u32 - 10 } + i);
mega = mega / radix as i128;
}
result.reverse();
stack.push(
Value::Str(String::from_iter(
result
.into_iter()
.map(|x| char::from_u32(x).expect("invalid radix")),
))
.spl(),
);
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); 53] = [
let fns: [(&str, Fn, u32); 69] = [
("pop", pop, 0),
("dup", dup, 2),
("dup2", dup2, 3),
("clone", clone, 1),
("swap", swap, 2),
("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),
@ -873,6 +1171,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),
@ -896,6 +1195,17 @@ pub fn register(r: &mut Stack, o: Arc<Frame>) {
("throw", throw, 0),
("write-sasm", write_sasm, 1),
("write-file-sasm", write_file_sasm, 1),
("fork", fork, 0),
("sleeptime", time, 0),
("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

@ -1,15 +1,23 @@
use crate::Runtime;
use multicall::multicall;
pub const STD: &str = include_str!("../std.spl");
pub const NET: &str = include_str!("../net.spl");
pub const ITER: &str = include_str!("../iter.spl");
pub const HTTP: &str = include_str!("../http.spl");
pub const STREAM: &str = include_str!("../stream.spl");
pub const MESSAGING: &str = include_str!("../messaging.spl");
pub const ASSEMBLE: &str = include_str!("../assemble.spl");
pub const ISBPL: &str = include_str!("../isbpl.spl");
pub const REPL: &str = include_str!("../repl.spl");
pub const STD: &str = include_str!("../spl/std.spl");
pub const NET: &str = include_str!("../spl/net.spl");
pub const ITER: &str = include_str!("../spl/iter.spl");
pub const HTTP: &str = include_str!("../spl/http.spl");
pub const STREAM: &str = include_str!("../spl/stream.spl");
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 NOP: &str = "";
pub fn register(runtime: &mut Runtime) {
multicall! {
@ -23,5 +31,13 @@ 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("nop.spl", NOP);
}
}

55
src/stream/mod.rs Normal file
View file

@ -0,0 +1,55 @@
mod rw;
mod server;
pub use rw::*;
pub use server::*;
use std::sync::{Arc, LazyLock};
use crate::{mutex::Mut, runtime::*};
static IS_INITIALIZED: LazyLock<Arc<Mut<bool>>> = LazyLock::new(|| Arc::new(Mut::new(false)));
#[derive(Default)]
pub struct StreamExtraData {
peer: Option<(String, u16)>,
}
pub fn register(r: &mut Stack, o: Arc<Frame>) {
if !*IS_INITIALIZED.lock_ro() {
register_stream_type("file", stream_file);
register_stream_type("tcp", stream_tcp);
register_stream_type("udp", stream_udp);
register_stream_type("cmd", stream_cmd);
register_server_stream_type("tcp", server_stream_tcp);
*IS_INITIALIZED.lock() = true;
}
type Fn = fn(&mut Stack) -> OError;
let fns: [(&str, Fn, u32); 12] = [
("new-stream", new_stream, 1),
("write-stream", write_stream, 1),
("write-all-stream", write_all_stream, 0),
("flush-stream", flush_stream, 0),
("read-stream", read_stream, 1),
("read-all-stream", read_all_stream, 0),
("close-stream", close_stream, 0),
("new-server-stream", new_server_stream, 1),
("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(
f.0.to_owned(),
AFunc::new(Func {
ret_count: f.2,
to_call: FuncImpl::Native(f.1),
run_as_base: false,
origin: o.clone(),
fname: None,
name: f.0.to_owned(),
}),
);
}
}

View file

@ -1,20 +1,20 @@
use std::{
collections::HashMap,
fs::OpenOptions,
hint::black_box,
io::{Read, Write},
mem,
net::{Shutdown, TcpStream, UdpSocket},
net::{TcpStream, UdpSocket},
process::{self, Stdio},
sync::Arc,
sync::{Arc, LazyLock},
};
use once_cell::sync::Lazy;
use crate::*;
use crate::{mutex::Mut, runtime::*, *};
use fs::OpenOptions;
use mutex::Mut;
use stream::StreamExtraData;
static STREAM_TYPES: Lazy<Arc<Mut<HashMap<String, StreamType>>>> =
Lazy::new(|| Arc::new(Mut::new(HashMap::new())));
static IS_INITIALIZED: Lazy<Arc<Mut<bool>>> = Lazy::new(|| Arc::new(Mut::new(false)));
static STREAM_TYPES: LazyLock<Arc<Mut<HashMap<String, StreamType>>>> =
LazyLock::new(|| Arc::new(Mut::new(HashMap::new())));
/// Registers a custom stream type.
pub fn register_stream_type(
@ -45,34 +45,57 @@ impl StreamType {
/// An SPL stream, holding a reader and a writer, and a function to close it.
pub struct Stream {
reader: Box<dyn Read + 'static>,
writer: Box<dyn Write + 'static>,
close: fn(&mut Self),
pub(super) reader: Box<dyn Read + Send + Sync + 'static>,
pub(super) _writer_storage: Option<Box<dyn Write + Send + Sync + 'static>>,
pub(super) writer: &'static mut (dyn Write + Send + Sync + 'static),
pub extra: StreamExtraData,
}
impl Stream {
pub fn new<T: Read + Write + 'static>(main: T, close: fn(&mut Self)) -> Self {
pub fn new<T: Read + Write + Send + Sync + 'static>(main: T) -> Self {
let mut rw = Box::new(main);
Self {
// SAFETY: Because these are both in private fields on one object, they can not be
// written to simultaneously or read from while writing due to the guards put in place
// by the borrow checker on the Stream.
reader: Box::new(unsafe { mem::transmute::<&mut _, &mut T>(rw.as_mut()) }),
writer: rw,
close,
writer: unsafe {
(rw.as_mut() as *mut (dyn Write + Send + Sync + 'static))
.as_mut()
.unwrap()
},
_writer_storage: None,
reader: rw,
extra: StreamExtraData::default(),
}
}
pub fn new_split(
reader: impl Read + 'static,
writer: impl Write + 'static,
close: fn(&mut Self),
reader: impl Read + Send + Sync + 'static,
writer: impl Write + Send + Sync + 'static,
) -> Self {
let mut bx = Box::new(writer);
Self {
reader: Box::new(reader),
writer: Box::new(writer),
close,
writer: unsafe {
(bx.as_mut() as *mut (dyn Write + Send + Sync + 'static))
.as_mut()
.unwrap()
},
_writer_storage: Some(bx),
extra: StreamExtraData::default(),
}
}
pub fn append_extra(mut self, f: impl Fn(&mut StreamExtraData)) -> 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 {
@ -119,19 +142,30 @@ 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,
{
fn from(value: T) -> Self {
StreamType {
Self {
func: Arc::new(Box::new(value)),
}
}
}
pub fn new_stream(stack: &mut Stack) -> OError {
require_on_stack!(s, Str, stack, "write-stream");
require_on_stack!(s, Str, stack, "new-stream");
let stream = get_stream_type(s.clone())
.ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-type-{s}"))))?
.make_stream(stack)?;
@ -142,48 +176,36 @@ 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(),
);
black_box(&stream.lock_ro()._writer_storage);
Ok(())
}
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(())
}
@ -197,18 +219,20 @@ pub fn flush_stream(stack: &mut Stack) -> OError {
.lock()
.flush()
.map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))?;
black_box(&stream.lock_ro()._writer_storage);
Ok(())
}
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(
@ -226,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(())
}
@ -233,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()
@ -250,22 +288,34 @@ 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(())
}
pub fn close_stream(stack: &mut Stack) -> OError {
require_on_stack!(id, Mega, stack, "close-stream");
if let Some(stream) = runtime(|rt| rt.get_stream(id as u128)) {
let mut stream = stream.lock();
(stream.close)(&mut stream);
}
runtime_mut(|mut rt| rt.destroy_stream(id as u128));
Ok(())
}
fn nop(_stream: &mut Stream) {}
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(())
}
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();
require_on_stack!(path, Str, stack, "FILE new-stream");
Ok(Stream::new(
@ -276,34 +326,23 @@ fn stream_file(stack: &mut Stack) -> Result<Stream, Error> {
.truncate(truncate)
.open(path)
.map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?,
nop,
))
}
fn stream_tcp(stack: &mut Stack) -> Result<Stream, Error> {
pub(super) fn stream_tcp(stack: &mut Stack) -> Result<Stream, Error> {
require_int_on_stack!(port, stack, "TCP new-stream");
require_on_stack!(ip, Str, stack, "TCP new-stream");
fn close_tcp(stream: &mut Stream) {
unsafe {
let f = ((stream.reader.as_mut() as *mut dyn Read).cast() as *mut TcpStream)
.as_mut()
.unwrap();
let _ = f.shutdown(Shutdown::Both);
}
}
Ok(Stream::new(
TcpStream::connect((ip, port as u16))
.map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?,
close_tcp,
))
}
fn stream_udp(stack: &mut Stack) -> Result<Stream, Error> {
pub(super) fn stream_udp(stack: &mut Stack) -> Result<Stream, Error> {
require_int_on_stack!(port, stack, "UDP new-stream");
require_on_stack!(ip, Str, stack, "UDP new-stream");
require_int_on_stack!(self_port, stack, "UDP new-stream");
require_on_stack!(self_ip, Str, stack, "UDP new-stream");
fn close_udp(_stream: &mut Stream) {}
let sock = UdpSocket::bind((self_ip, self_port as u16))
.map_err(|x| stack.error(ErrorKind::IO(x.to_string())))?;
sock.connect((ip, port as u16))
@ -323,12 +362,11 @@ fn stream_udp(stack: &mut Stack) -> Result<Stream, Error> {
self.0.recv(buf)
}
}
Ok(Stream::new(UdpRW(sock), close_udp))
Ok(Stream::new(UdpRW(sock)))
}
fn stream_cmd(stack: &mut Stack) -> Result<Stream, Error> {
pub(super) fn stream_cmd(stack: &mut Stack) -> Result<Stream, Error> {
require_on_stack!(a, Array, stack, "CMD new-stream");
fn close_cmd(_stream: &mut Stream) {}
let mut args = Vec::new();
for item in a.iter() {
if let Value::Str(ref s) = item.lock_ro().native {
@ -336,7 +374,7 @@ 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..])
@ -348,40 +386,26 @@ fn stream_cmd(stack: &mut Stack) -> Result<Stream, Error> {
Ok(Stream::new_split(
command.stdout.take().unwrap(),
command.stdin.take().unwrap(),
close_cmd,
))
}
pub fn register(r: &mut Stack, o: Arc<Frame>) {
if !*IS_INITIALIZED.lock_ro() {
register_stream_type("file", stream_file);
register_stream_type("tcp", stream_tcp);
register_stream_type("udp", stream_udp);
register_stream_type("cmd", stream_cmd);
*IS_INITIALIZED.lock() = true;
}
type Fn = fn(&mut Stack) -> OError;
let fns: [(&str, Fn, u32); 7] = [
("new-stream", new_stream, 1),
("write-stream", write_stream, 1),
("write-all-stream", write_all_stream, 0),
("flush-stream", flush_stream, 0),
("read-stream", read_stream, 1),
("read-all-stream", read_all_stream, 0),
("close-stream", close_stream, 0),
];
for f in fns {
r.define_func(
f.0.to_owned(),
AFunc::new(Func {
ret_count: f.2,
to_call: FuncImpl::Native(f.1),
run_as_base: false,
origin: o.clone(),
fname: None,
name: f.0.to_owned(),
}),
);
}
pub(super) fn get_stream_peer(stack: &mut Stack) -> OError {
require_on_stack!(id, Mega, stack, "get-stream-peer");
let Some((addr, port)) = runtime(|rt| -> Result<_, Error> {
Ok(rt
.get_stream(id as u128)
.ok_or(stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}"))))?
.lock_ro()
.extra
.peer
.clone())
})?
else {
stack.push(Value::Null.spl());
stack.push(Value::Null.spl());
return Ok(());
};
stack.push(addr.spl());
stack.push((port as i32).spl());
Ok(())
}

106
src/stream/server.rs Normal file
View file

@ -0,0 +1,106 @@
use std::{
collections::HashMap,
net::TcpListener,
sync::{Arc, LazyLock},
};
use crate::{mutex::Mut, *};
use super::Stream;
static SERVER_STREAM_TYPES: LazyLock<Arc<Mut<HashMap<String, ServerStreamType>>>> =
LazyLock::new(|| Arc::new(Mut::new(HashMap::new())));
/// Registers a custom stream type.
pub fn register_server_stream_type(
name: &str,
supplier: impl Fn(&mut Stack) -> Result<ServerStream, Error> + Sync + Send + 'static,
) {
SERVER_STREAM_TYPES
.lock()
.insert(name.to_owned(), ServerStreamType::from(supplier));
}
/// Gets a stream type by name.
pub fn get_server_stream_type(name: String) -> Option<ServerStreamType> {
SERVER_STREAM_TYPES.lock_ro().get(&name).cloned()
}
/// An SPL stream type.
#[derive(Clone)]
pub struct ServerStreamType {
func: Arc<Box<dyn Fn(&mut Stack) -> Result<ServerStream, Error> + Sync + Send + 'static>>,
}
impl ServerStreamType {
pub fn make_stream(&self, stack: &mut Stack) -> Result<ServerStream, Error> {
(self.func)(stack)
}
}
impl<T> From<T> for ServerStreamType
where
T: Fn(&mut Stack) -> Result<ServerStream, Error> + Sync + Send + 'static,
{
fn from(value: T) -> Self {
Self {
func: Arc::new(Box::new(value)),
}
}
}
/// An SPL server stream, holding an acceptor and a function to close it.
pub struct ServerStream {
pub(super) acceptor: Box<dyn Fn(&mut Stack) -> Result<Stream, Error> + Sync + Send + 'static>,
}
impl ServerStream {
pub fn new(
acceptor: impl Fn(&mut Stack) -> Result<Stream, Error> + Sync + Send + 'static,
) -> Self {
Self {
acceptor: Box::new(acceptor),
}
}
}
pub fn new_server_stream(stack: &mut Stack) -> OError {
require_on_stack!(s, Str, stack, "new-stream");
let stream = get_server_stream_type(s.clone())
.ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-type-{s}"))))?
.make_stream(stack)?;
let stream = runtime_mut(move |mut rt| Ok(rt.register_server_stream(stream)))?;
stack.push(Value::Mega(stream.0 as i128).spl());
Ok(())
}
pub fn accept_server_stream(stack: &mut Stack) -> OError {
require_on_stack!(id, Mega, stack, "accept-server-stream");
let stream = runtime(|rt| {
rt.get_server_stream(id as u128)
.ok_or_else(|| stack.error(ErrorKind::VariableNotFound(format!("__stream-{id}"))))
})?;
let stream = (stream.lock_ro().acceptor)(stack)?;
stack.push((runtime_mut(move |mut rt| rt.register_stream(stream)).0 as i128).spl());
Ok(())
}
pub fn close_server_stream(stack: &mut Stack) -> OError {
require_on_stack!(id, Mega, stack, "close-server-stream");
runtime_mut(|mut rt| rt.destroy_server_stream(id as u128));
Ok(())
}
pub(crate) fn server_stream_tcp(stack: &mut Stack) -> Result<ServerStream, Error> {
require_int_on_stack!(port, stack, "TCP server-stream");
require_on_stack!(addr, Str, stack, "TCP server-stream");
let tcp = TcpListener::bind((addr, port as u16))
.map_err(|e| stack.error(ErrorKind::IO(format!("{e:?}"))))?;
let stream = ServerStream::new(move |stack| {
let socket = tcp
.accept()
.map_err(|e| stack.error(ErrorKind::IO(format!("{e:?}"))))?;
Ok(Stream::new(socket.0)
.append_extra(move |d| d.peer = Some((socket.1.ip().to_string(), socket.1.port()))))
});
Ok(stream)
}

171
test.spl
View file

@ -1,11 +1,17 @@
[
"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
"#stream.spl" import
"#http.spl" import
"#messaging.spl" import
"SPL tester" =program-name
func main { int | with args ;
def thing
1 anew =thing
@ -108,18 +114,18 @@ func main { int | with args ;
def file "test.txt" 1 StreamTypes:file:create =file
"hi\n" :to-bytes file:write-exact;
file:close null =file
file:close; null =file
"" println
"testing split" println
{ | println } (" " "hello how are you" :split):foreach
"" println
use net:http:Request
catch {
use net:http:Request
"testing http" println
"testing http" println;
def req "data.tudbut.de" 80 "GET" "/spltest" Request:new =req
req:send:body _str println
req:send:body _str println;
} { with e ;
e:message println
"it seems the internet is not available" println
@ -142,19 +148,158 @@ func main { int | with args ;
"testing messages" println
def bus messaging:Bus:new =bus
bus:subscribe <{ "testmsg1" { | with message ; message:name print " called1 1" println } }
bus:subscribe <{ "testmsg1" { | with message ; message:name print " called1 2" println } }
bus:subscribe <{ "testmsg2" { | with message ; message:name print " called2 1" println } }
bus:subscribe <{ "testmsg2" { | with message ; message:name print " called2 2" println } }
bus:subscribe<"testmsg1" { | with message ; message:name print " called1 1" println }>
bus:subscribe<"testmsg1" { | with message ; message:name print " called1 2" println }>
bus:subscribe<"testmsg2" { | with message ; message:name print " called2 1" println }>
bus:subscribe<"testmsg2" { | with message ; message:name print " called2 2" println }>
"testmsg1" bus:publish
"testmsg2" bus:publish
"testmsg1" bus:publish
"testmsg3" bus:publish
"" println
"testing threads" println
def other-thread-done 0 =other-thread-done
{ |
"i am in the other thread!!!" println
1 =other-thread-done
} fork
while { other-thread-done not } { "waiting for the other thread..." println }
"" println
"testing tcp server" println
" starting server thread" println
{ |
def server "0.0.0.0" 4075 net:server:Types:tcp:create =server
while { 1 } {
def stream server:accept =stream
"Hello!" :to-bytes stream:write-exact;
stream:close;
}
} fork;
50 time:sleep;
" starting client" println;
def client "localhost" 4075 StreamTypes:tcp:create =client
1024 client:read-to-end:to-str println;
" ^ this should say 'Hello!'" println;
"" println
"testing string replace" println;
"aba" "!!" "ababab" :replace println;
" ^ should be !!bab." println;
"aba" "!!" "aababab" :replace println;
" ^ should be a!!bab." println;
"" println;
"testing string split" println;
"ba" "abaabaabaa" :split:iter:join<", "> println;
" ^ should be a, a, a, a" println;
"ba" "abbaabbaababaa" :split:iter:join<", "> println;
" ^ should be ab, ab, a, , a" println;
"" println;
"testing string find" println;
"abba" "ababba" :find println;
"^ should be 2" println;
"" println;
"testing readf" println;
def array
"Hello dear {}, {}?" "Hello dear friend, how are you?" (dup println;) :readf =array
"Person was " 0 array:get concat println;
"Question was " 1 array:get concat println;
"" println;
"testing http server" println;
def server "0.0.0.0" 4076 net:http:Server:new =server
{ |
while { 1 } {
server
:accept
:read
:write-ok
:write-str-body<"Hello! This was written to HTTP!">
:finish
}
} fork
def req "localhost" 4076 "GET" "/spltest" Request:new =req
req:send:body _str println;
"" println;
"testing urlencoding";
"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;
"=> 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;
5 :foreach<{ | pop "" println }>
] 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
}
func cached-test { mega | 1 "cached-test" cache <{ { mega | with i ;
func cached-test { mega | 1 "cached-test" cache<{ mega | with i ;
i 2 *
"calculated " i _str concat println
} } }
}>}

View file

@ -1 +0,0 @@
hi