Compare commits
18 commits
Author | SHA1 | Date | |
---|---|---|---|
706c4f023b | |||
d4dc588c35 | |||
89d14146be | |||
2446272181 | |||
6f2777b83a | |||
fd41c46cdd | |||
15ae8622e1 | |||
bb4d163b40 | |||
f86c55dd83 | |||
a1f5941c1a | |||
6d78b276d4 | |||
0fb01cfb20 | |||
2f8c50e3f0 | |||
79ee784a5b | |||
2024677225 | |||
89e6eff198 | |||
2574165cd1 | |||
7b3e0129f8 |
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -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.3.2"
|
||||
dependencies = [
|
||||
"multicall",
|
||||
"once_cell",
|
||||
"readformat",
|
||||
]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "spl"
|
||||
version = "0.3.0"
|
||||
version = "0.3.2"
|
||||
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
7
LICENSE
Normal 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.
|
12
README.md
12
README.md
|
@ -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
|
||||
|
|
61
benchmark.spl
Normal file
61
benchmark.spl
Normal 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
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
"#stream.spl" import
|
||||
"#net.spl" import
|
||||
"stream.spl" import
|
||||
"net.spl" import
|
||||
|
||||
"http" net:register
|
||||
|
||||
|
@ -7,6 +7,10 @@ construct net:http namespace {
|
|||
Request
|
||||
Response
|
||||
help
|
||||
;
|
||||
register { | with name this ;
|
||||
name "net:http" register-field
|
||||
}
|
||||
}
|
||||
|
||||
construct net:http:Request {
|
133
spl/http/server.spl
Normal file
133
spl/http/server.spl
Normal file
|
@ -0,0 +1,133 @@
|
|||
"../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:stream:accept net:http:server:Request:new
|
||||
}
|
||||
close { | with this ; this:stream:close; }
|
||||
}
|
||||
|
||||
construct net:http:server namespace {
|
||||
Request
|
||||
}
|
||||
|
||||
construct net:http:server:Request {
|
||||
stream
|
||||
head
|
||||
body
|
||||
method path version
|
||||
headers
|
||||
wrote-body
|
||||
;
|
||||
construct { this | with stream this ;
|
||||
stream this:=stream
|
||||
0 anew this:=head
|
||||
0 anew this:=body
|
||||
this
|
||||
}
|
||||
read-head { this | with this ;
|
||||
def read
|
||||
def buf 1024 anew =buf
|
||||
def found
|
||||
while {
|
||||
buf this:stream:read pop =read
|
||||
"\r\n\r\n" :to-bytes buf:find dup =found not read and
|
||||
} {
|
||||
this:head buf:sub<0 read> aadd this:=head
|
||||
}
|
||||
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<"\r\n"> this:=head
|
||||
def iter this:head:iter =iter
|
||||
iter:next:readf<"{} {} HTTP/{}"> dup if {
|
||||
dup:to-stack this:=version this:=path this:=method
|
||||
} pop
|
||||
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: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 anew =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 anew this:write-body;
|
||||
}
|
||||
this:stream:close;
|
||||
}
|
||||
}
|
|
@ -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,17 @@ func argv {
|
|||
args nargs 2 0 nargs:len acopy
|
||||
nargs
|
||||
}
|
||||
func eputs {
|
||||
print
|
||||
}
|
||||
func isbplmod'import-transform { with s ;
|
||||
"spl: [compat] transforming import " s concat println
|
||||
s:_char "#" :_char eq if {
|
||||
"#nop.spl" =s
|
||||
}
|
||||
"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 +35,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 { }
|
|
@ -11,10 +11,11 @@ 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 call
|
||||
}
|
||||
pop
|
||||
}
|
||||
collect { array | with this ;
|
||||
[ { any | } this:foreach ]
|
|
@ -1,4 +1,4 @@
|
|||
"messaging bus, aka event bus"
|
||||
"messaging bus, aka event bus";
|
||||
|
||||
construct messaging namespace {
|
||||
Message
|
|
@ -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
49
spl/pure.spl
Normal 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
|
||||
}
|
63
spl/server.spl
Normal file
63
spl/server.spl
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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,48 @@ 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
|
||||
replacee:to-bytes replacement:to-bytes this:to-bytes:replace:to-str
|
||||
}
|
||||
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
|
||||
find { idx | with search this ;
|
||||
search _array this _array :find
|
||||
}
|
||||
starts-with { bool | with beginning this ;
|
||||
beginning:to-bytes this:to-bytes:starts-with
|
||||
}
|
||||
_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
|
||||
}
|
||||
} include _str_ext in str
|
||||
|
||||
construct _mega-ext {
|
||||
|
@ -93,7 +83,10 @@ 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 call 1 + }
|
||||
}
|
||||
} include _mega-ext in mega
|
||||
|
||||
|
@ -123,10 +116,81 @@ construct _array-ext {
|
|||
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
|
||||
}
|
||||
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 {
|
||||
0
|
||||
|
@ -138,7 +202,7 @@ construct _array-ext {
|
|||
this:len -- this:get
|
||||
}
|
||||
=last { | with this ;
|
||||
this:len -- this:set
|
||||
this:len -- this:set;
|
||||
}
|
||||
0 { any | with this ;
|
||||
0 this:get
|
||||
|
@ -365,16 +429,21 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -398,9 +467,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 +544,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
|
||||
}
|
||||
|
@ -513,7 +586,8 @@ func _ { |
|
|||
|
||||
func call-main-on-file { | with file ;
|
||||
catch {
|
||||
"@" file concat import
|
||||
"@" (0 (file _array):get 0 ("#" _array):get eq) if { pop "" } file concat
|
||||
import
|
||||
update-types
|
||||
argv main exit
|
||||
} { with err ;
|
||||
|
@ -539,8 +613,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>
|
||||
}
|
|
@ -16,13 +16,13 @@ construct Stream {
|
|||
}
|
||||
read-one { mega | with this ;
|
||||
def buf 1 anew =buf
|
||||
while { buf this:id read-stream not } { }
|
||||
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 this:id read-stream
|
||||
}
|
||||
"the buffer is written to in-place.";
|
||||
read-exact { [int] | with buf this ;
|
||||
|
@ -35,7 +35,7 @@ construct Stream {
|
|||
def read
|
||||
while { buf this:id read-stream pop _mega dup =read } {
|
||||
full (0 read buf:sub) aadd =full
|
||||
}
|
||||
} pop
|
||||
full
|
||||
}
|
||||
write { mega | with buf this ;
|
||||
|
@ -50,6 +50,23 @@ construct 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 +83,7 @@ construct StreamType {
|
|||
|
||||
def stream-types 0 anew =stream-types
|
||||
|
||||
construct _StreamType {
|
||||
construct _StreamTypes {
|
||||
;
|
||||
construct { this | with this ;
|
||||
{ | with type ;
|
||||
|
@ -79,7 +96,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 +104,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
9
spl/time.spl
Normal file
|
@ -0,0 +1,9 @@
|
|||
|
||||
construct time namespace {
|
||||
;
|
||||
unixms { mega | with this ;
|
||||
0 sleeptime
|
||||
}
|
||||
sleep { mega | with this ; _mega sleeptime }
|
||||
}
|
||||
|
49
src/lexer.rs
49
src/lexer.rs
|
@ -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;
|
||||
}
|
||||
|
@ -111,8 +124,8 @@ fn read_block(
|
|||
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,7 +240,16 @@ 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('\"') => {
|
||||
|
@ -286,7 +308,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 +372,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;
|
||||
|
|
|
@ -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,
|
||||
|
|
11
src/lib.rs
11
src/lib.rs
|
@ -76,12 +76,11 @@ pub fn start_file_in_runtime(path: &str) -> Result<Stack, Error> {
|
|||
/// Include the standard library in a runtime-stack-pair, where the runtime has been .set().
|
||||
pub fn add_std(stack: &mut Stack) -> OError {
|
||||
let f = find_in_splpath("std.spl");
|
||||
let words = f
|
||||
.map_err(|x| stack.error(ErrorKind::LexError(format!("{x}"))))
|
||||
.and_then(|f| {
|
||||
lex(f.ends_with(".isbpl"), fs::read_to_string(f).unwrap())
|
||||
.map_err(|x| stack.error(ErrorKind::LexError(format!("{x:?}"))))
|
||||
})?;
|
||||
let words = match f {
|
||||
Ok(f) => lex(f.ends_with(".isbpl"), fs::read_to_string(f).unwrap()),
|
||||
Err(content) => lex(false, content),
|
||||
}
|
||||
.map_err(|x| stack.error(ErrorKind::LexError(format!("{x:?}"))))?;
|
||||
words.exec(stack)
|
||||
}
|
||||
|
||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -1,5 +1,5 @@
|
|||
use spl::{
|
||||
find_in_splpath, lex, oxidizer::RustAppBuilder, start_file_in_runtime, Runtime, SetRuntime,
|
||||
lex, oxidizer::RustAppBuilder, sasm::sasm_write, start_file_in_runtime, Runtime, SetRuntime,
|
||||
};
|
||||
|
||||
use std::{env::args, fs};
|
||||
|
@ -7,12 +7,15 @@ use std::{env::args, fs};
|
|||
fn main() {
|
||||
Runtime::new().set();
|
||||
let mut args = args().skip(1);
|
||||
let arg = &args
|
||||
.next()
|
||||
.unwrap_or_else(|| find_in_splpath("repl.spl").expect("no file to be run"));
|
||||
if arg == "--build" || arg == "--run" || arg == "--buildrun" {
|
||||
let arg = &args.next().unwrap_or("#repl.spl".to_owned());
|
||||
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...");
|
||||
|
|
|
@ -171,13 +171,17 @@ impl RustAppBuilder {
|
|||
if let Err(x) = start_file_in_runtime(
|
||||
&args()
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| find_in_splpath("default_file").expect("no file to be run")),
|
||||
.unwrap_or("default_file".to_owned()),
|
||||
) {
|
||||
println!("{x:?}");
|
||||
}
|
||||
Runtime::reset();
|
||||
}
|
||||
}.to_owned().replace("default_file", &self.default_file).replace("runtime_init", &runtime_init) + &code,
|
||||
}
|
||||
.to_owned()
|
||||
.replace("default_file", &self.default_file)
|
||||
.replace("runtime_init", &runtime_init)
|
||||
+ &code,
|
||||
)?;
|
||||
Command::new("cargo")
|
||||
.arg("build")
|
||||
|
|
114
src/runtime.rs
114
src/runtime.rs
|
@ -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(),
|
||||
};
|
||||
|
@ -151,6 +162,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!(
|
||||
|
@ -461,6 +489,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 {
|
||||
|
@ -640,6 +673,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.
|
||||
|
@ -750,6 +798,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 +836,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),
|
||||
}
|
||||
|
||||
|
@ -1135,20 +1187,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 +1226,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 +1245,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 +1274,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 +1330,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 +1354,7 @@ impl Words {
|
|||
origin: stack.get_frame(),
|
||||
run_as_base: false,
|
||||
fname: None,
|
||||
name,
|
||||
name: name.to_owned(),
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
@ -1298,6 +1366,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());
|
||||
}
|
||||
|
@ -1323,9 +1392,13 @@ impl Words {
|
|||
}));
|
||||
}
|
||||
stack.push(f.spl());
|
||||
} else {
|
||||
if inline_calls {
|
||||
stack.fast_call(&f)?;
|
||||
} else {
|
||||
stack.call(&f)?;
|
||||
if rem {
|
||||
}
|
||||
if *rem {
|
||||
for _ in 0..f.ret_count {
|
||||
stack.pop();
|
||||
}
|
||||
|
@ -1333,6 +1406,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();
|
||||
|
@ -1369,9 +1443,13 @@ impl Words {
|
|||
}
|
||||
stack.pop();
|
||||
stack.push(f.spl())
|
||||
} else {
|
||||
if inline_calls {
|
||||
stack.fast_call(&f)?;
|
||||
} else {
|
||||
stack.call(&f)?;
|
||||
if rem {
|
||||
}
|
||||
if *rem {
|
||||
for _ in 0..f.ret_count {
|
||||
stack.pop();
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
@ -223,6 +226,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;
|
||||
|
|
|
@ -7,8 +7,12 @@ use std::{
|
|||
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 +54,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(())
|
||||
|
@ -605,19 +616,6 @@ 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 +631,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())
|
||||
|
@ -840,11 +860,51 @@ 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 register(r: &mut Stack, o: Arc<Frame>) {
|
||||
type Fn = fn(&mut Stack) -> OError;
|
||||
let fns: [(&str, Fn, u32); 53] = [
|
||||
let fns: [(&str, Fn, u32); 57] = [
|
||||
("pop", pop, 0),
|
||||
("dup", dup, 2),
|
||||
("dup2", dup2, 3),
|
||||
("clone", clone, 1),
|
||||
("swap", swap, 2),
|
||||
("mswap", mswap, 2),
|
||||
|
@ -896,6 +956,9 @@ 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),
|
||||
];
|
||||
for f in fns {
|
||||
r.define_func(
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
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 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/http/server.spl");
|
||||
pub const NOP: &str = "";
|
||||
|
||||
pub fn register(runtime: &mut Runtime) {
|
||||
multicall! {
|
||||
|
@ -23,5 +28,10 @@ pub fn register(runtime: &mut Runtime) {
|
|||
insert("assemble.spl", ASSEMBLE);
|
||||
insert("isbpl.spl", ISBPL);
|
||||
insert("repl.spl", REPL);
|
||||
insert("pure.spl", PURE);
|
||||
insert("time.spl", TIME);
|
||||
insert("server.spl", SERVER);
|
||||
insert("http/server.spl", HTTP_SERVER);
|
||||
insert("nop.spl", NOP);
|
||||
}
|
||||
}
|
||||
|
|
54
src/stream/mod.rs
Normal file
54
src/stream/mod.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
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); 11] = [
|
||||
("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),
|
||||
];
|
||||
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(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,47 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Stream {
|
||||
|
@ -124,14 +137,14 @@ 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)?;
|
||||
|
@ -163,6 +176,7 @@ pub fn write_stream(stack: &mut Stack) -> OError {
|
|||
)
|
||||
.spl(),
|
||||
);
|
||||
black_box(&stream.lock_ro()._writer_storage);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -184,6 +198,7 @@ pub fn write_all_stream(stack: &mut Stack) -> OError {
|
|||
.lock()
|
||||
.write_all(&fixed[..])
|
||||
.map_err(|x| stack.error(ErrorKind::IO(format!("{x:?}"))))?;
|
||||
black_box(&stream.lock_ro()._writer_storage);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -197,6 +212,7 @@ 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(())
|
||||
}
|
||||
|
||||
|
@ -256,16 +272,11 @@ pub fn read_all_stream(stack: &mut Stack) -> OError {
|
|||
|
||||
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) {}
|
||||
|
||||
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 +287,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 +323,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 {
|
||||
|
@ -348,40 +347,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
106
src/stream/server.rs
Normal 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)
|
||||
}
|
111
test.spl
111
test.spl
|
@ -1,11 +1,15 @@
|
|||
|
||||
[
|
||||
"#stream.spl" import
|
||||
"#http.spl" import
|
||||
"#messaging.spl" import
|
||||
"#server.spl" import
|
||||
"#time.spl" import
|
||||
"#http/server.spl" import
|
||||
|
||||
"SPL tester" =program-name
|
||||
|
||||
func main { int | with args ;
|
||||
|
||||
def thing
|
||||
|
||||
1 anew =thing
|
||||
|
@ -108,18 +112,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
|
||||
|
||||
catch {
|
||||
use net:http:Request
|
||||
"testing http" println
|
||||
catch {
|
||||
"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 +146,104 @@ 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;
|
||||
|
||||
5 :foreach<{ | "" println }>
|
||||
|
||||
"you now have a chance to connect too: localhost :4075 :4076 - stopping in 5 seconds..." println;
|
||||
5000 time:sleep;
|
||||
|
||||
] dup :len 0 eq not if {
|
||||
"" println
|
||||
"!! something went wrong somewhere. the stack is not empty." println
|
||||
dyn-__dump
|
||||
}
|
||||
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
|
||||
} } }
|
||||
}>}
|
||||
|
|
Loading…
Reference in a new issue