Compare commits

...

17 commits
v0.3.1 ... main

30 changed files with 1123 additions and 235 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.3.2"
dependencies = [
"multicall",
"once_cell",
"readformat",
]

View file

@ -1,6 +1,6 @@
[package]
name = "spl"
version = "0.3.1"
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
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,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
View 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;
}
}

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,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 { }

View file

@ -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 ]

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
}

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,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
}
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 _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
@ -119,14 +112,85 @@ 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
}
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
}
@ -540,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>
}

View file

@ -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
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;
}
@ -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;

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

@ -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(),
};
@ -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());
}
@ -1324,8 +1393,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 +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();
@ -1370,8 +1444,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(),
@ -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;

View file

@ -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(

View file

@ -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
View 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(),
}),
);
}
}

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,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
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)
}

111
test.spl
View file

@ -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
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 +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
} } }
}>}