From 53ba6e445f5d849298ff9e47751b4718e544107d Mon Sep 17 00:00:00 2001 From: Matthew Paras <34500476+mattwparas@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:54:44 -0700 Subject: [PATCH] Fix tcp functions add http parsing (#267) * checkpoint * polling * remove prints * check point * remove call/cc timing * clean up * fix wasm build * use shared type alias * line up imports * fix build * fix build * fix docs * add some eval tests * attempt to fix sandboxed build * fix build * fix sandbox import * disallow dylib loading for sandboxed engines --- Cargo.lock | 186 ++++++- Cargo.toml | 6 +- cogs/r5rs.scm | 142 +++-- crates/steel-core/Cargo.toml | 4 + crates/steel-core/src/compiler/code_gen.rs | 2 +- crates/steel-core/src/compiler/compiler.rs | 4 +- crates/steel-core/src/parser/kernel.rs | 37 +- crates/steel-core/src/primitives.rs | 7 +- .../steel-core/src/primitives/bytevectors.rs | 18 +- crates/steel-core/src/primitives/fs.rs | 5 + crates/steel-core/src/primitives/http.rs | 185 +++++++ crates/steel-core/src/primitives/polling.rs | 150 +++++ crates/steel-core/src/primitives/ports.rs | 143 ++++- crates/steel-core/src/primitives/tcp.rs | 34 +- crates/steel-core/src/rvals/cycles.rs | 32 +- .../steel-core/src/scheme/modules/reader.scm | 28 +- crates/steel-core/src/steel_vm/builtin.rs | 8 + crates/steel-core/src/steel_vm/engine.rs | 272 +++++---- crates/steel-core/src/steel_vm/primitives.rs | 219 +++++--- crates/steel-core/src/steel_vm/vm.rs | 523 +++++++++++++++--- crates/steel-core/src/steel_vm/vm/threads.rs | 5 + crates/steel-core/src/values/port.rs | 102 +++- .../tests/test_files/input_tests.rkt | 2 + .../tests/test_files/output_tests.rkt | 4 +- src/lib.rs | 14 + steel-examples/coop-threads.scm | 56 +- 26 files changed, 1729 insertions(+), 459 deletions(-) create mode 100644 crates/steel-core/src/primitives/http.rs create mode 100644 crates/steel-core/src/primitives/polling.rs diff --git a/Cargo.lock b/Cargo.lock index 118710e87..e828b23ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -208,7 +208,7 @@ dependencies = [ "bitflags 1.3.2", "bytes", "futures-util", - "http", + "http 0.2.12", "http-body", "hyper", "itoa", @@ -238,7 +238,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", + "http 0.2.12", "http-body", "mime", "rustversion", @@ -387,7 +387,7 @@ name = "cargo-steel-lib" version = "0.1.0" dependencies = [ "cargo-platform", - "cargo_metadata", + "cargo_metadata 0.15.4", "home", ] @@ -405,6 +405,20 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cast" version = "0.3.0" @@ -908,6 +922,41 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.74", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.74", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -944,6 +993,37 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.74", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" +dependencies = [ + "derive_builder_core", + "syn 2.0.74", +] + [[package]] name = "diff" version = "0.1.13" @@ -1305,6 +1385,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getset" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "gimli" version = "0.29.0" @@ -1385,6 +1477,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -1392,7 +1495,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", "pin-project-lite", ] @@ -1424,7 +1527,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", + "http 0.2.12", "http-body", "httparse", "httpdate", @@ -1460,6 +1563,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -1561,11 +1670,11 @@ dependencies = [ "encoding_rs", "event-listener 2.5.3", "futures-lite", - "http", + "http 0.2.12", "log", "mime", "once_cell", - "polling", + "polling 2.8.0", "serde", "serde_json", "slab", @@ -1909,6 +2018,15 @@ dependencies = [ "libm", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.36.3" @@ -2118,6 +2236,21 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "polling" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2910,7 +3043,7 @@ version = "0.6.0" dependencies = [ "abi_stable", "base64 0.22.1", - "http", + "http 0.2.12", "isahc", "steel-core", "urlencoding", @@ -2941,6 +3074,8 @@ dependencies = [ "fxhash", "getrandom", "home", + "http 1.1.0", + "httparse", "im", "im-lists", "im-rc", @@ -2949,6 +3084,7 @@ dependencies = [ "num", "once_cell", "parking_lot", + "polling 3.7.3", "pretty", "proptest", "quickscope", @@ -3007,6 +3143,7 @@ dependencies = [ "steel-derive", "steel-doc", "steel-repl", + "vergen", ] [[package]] @@ -3294,7 +3431,9 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", @@ -3552,7 +3691,7 @@ dependencies = [ "base64 0.13.1", "byteorder", "bytes", - "http", + "http 0.2.12", "httparse", "log", "rand", @@ -3691,6 +3830,35 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vergen" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c32e7318e93a9ac53693b6caccfb05ff22e04a44c7cf8a279051f24c09da286f" +dependencies = [ + "anyhow", + "cargo_metadata 0.18.1", + "derive_builder", + "getset", + "regex", + "rustc_version", + "rustversion", + "time", + "vergen-lib", +] + +[[package]] +name = "vergen-lib" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e06bee42361e43b60f363bad49d63798d0f42fb1768091812270eca00c784720" +dependencies = [ + "anyhow", + "derive_builder", + "getset", + "rustversion", +] + [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index 83d998111..efe4e1632 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,8 @@ path = "src/main.rs" # This has to line up with the workspace version above steel-core = { path = "./crates/steel-core", version = "0.6.0", features = ["dylibs", "markdown", "stacker", "dylib-build", "sync"] } -# [features] +[features] +build-info = ["vergen"] # Note: It does not appear that this will get propagated to any crate that depends on # the workspace feature. This is unfortunate, since we'd like everything to actually # use the workspace dependency. For now, if you want to test with sync, you should @@ -56,3 +57,6 @@ lto = true [profile.test] opt-level = 2 + +[build-dependencies] +vergen = { version = "9.0.0", features = [ "build", "cargo", "rustc" ], optional = true} diff --git a/cogs/r5rs.scm b/cogs/r5rs.scm index 367d5149c..bde6cb93f 100644 --- a/cogs/r5rs.scm +++ b/cogs/r5rs.scm @@ -43,6 +43,9 @@ (define __module__ 'r5rs-test-suite) +(check-equal? "eval catch exception" 100 (with-handler (lambda (err) 100) (eval `(+ 100 "foo")))) +(check-equal? "basic eval" 100 (eval '(* 10 10))) + (check-equal? "<= with rational numbers" (let* ([z (/ 3 2)]) (if (<= z 0) z (+ z 1))) (/ 5 2)) (check-equal? "Parsing hex" #x0f 15) @@ -112,13 +115,27 @@ ;; TODO (skip-compile (check-equal? '(b c) (or (memq 'b '(a b c)) (/ 3 0)))) -(check-equal? "basic let" 6 (let ([x 2] [y 3]) (* x y))) +(check-equal? "basic let" + 6 + (let ([x 2] + [y 3]) + (* x y))) (check-equal? "basic let with multiple levels" 35 - (let ([x 2] [y 3]) (let ([x 7] [z (+ x y)]) (* z x)))) - -(check-equal? "basic let*" 70 (let ([x 2] [y 3]) (let* ([x 7] [z (+ x y)]) (* z x)))) + (let ([x 2] + [y 3]) + (let ([x 7] + [z (+ x y)]) + (* z x)))) + +(check-equal? "basic let*" + 70 + (let ([x 2] + [y 3]) + (let* ([x 7] + [z (+ x y)]) + (* z x)))) (check-equal? "interior define" -2 @@ -144,7 +161,9 @@ (check-equal? "named let" '((6 1 3) (-5 -2)) - (let loop ([numbers '(3 -2 1 6 -5)] [nonneg '()] [neg '()]) + (let loop ([numbers '(3 -2 1 6 -5)] + [nonneg '()] + [neg '()]) (cond [(null? numbers) (list nonneg neg)] [(>= (car numbers) 0) (loop (cdr numbers) (cons (car numbers) nonneg) neg)] @@ -170,7 +189,9 @@ (check-equal? "double unquote and quote" '(a `(b ,x ,'y d) e) - (let ([name1 'x] [name2 'y]) `(a `(b ,,name1 ,',name2 d) e))) + (let ([name1 'x] + [name2 'y]) + `(a `(b ,,name1 ,',name2 d) e))) (check-equal? "named quasiquote" '(list 3 4) (quasiquote (list (unquote (+ 1 2)) 4))) @@ -588,13 +609,15 @@ ; (skip-compile (check-equal? "Dynamic wind" '(a b c) - (let* ([path '()] [add (lambda (s) (set! path (cons s path)))]) + (let* ([path '()] + [add (lambda (s) (set! path (cons s path)))]) (dynamic-wind (lambda () (add 'a)) (lambda () (add 'b)) (lambda () (add 'c))) (reverse path))) (check-equal? "Dynamic wind more complex" '(connect talk1 disconnect connect talk2 disconnect) - (let ([path '()] [c #f]) + (let ([path '()] + [c #f]) (let ([add (lambda (s) (set! path (cons s path)))]) (dynamic-wind (lambda () (add 'connect)) (lambda () @@ -617,10 +640,8 @@ ; ) - ;; vectors - (define vector->list immutable-vector->list) (define vector->string immutable-vector->string) (define vector-copy immutable-vector-copy) @@ -630,7 +651,7 @@ (check-equal? "vector predicate" #t (vector? #(1 2 3))) (check-equal? "vector predicated, quoted" #t (vector? '#(1 2 3))) -(let [(make-vector make-immutable-vector)] +(let ([make-vector make-immutable-vector]) (check-equal? "vector length, empty" 0 (vector-length (make-vector 0))) (check-equal? "vector length" 1000 (vector-length (make-vector 1000)))) @@ -639,15 +660,17 @@ (check-equal? "vector constructor" #(a b c) (vector 'a 'b 'c)) (check-equal? "vector ref" 8 (vector-ref '#(1 1 2 3 5 8 13 21) 5)) -(skip-compile (check-equal? "TODO" 13 (vector-ref '#(1 1 2 3 5 8 13 21) - (let ((i (round (* 2 (acos -1))))) - (if (inexact? i) - (exact i) - i))))) - -(skip-compile (check-equal? "TODO" #(0 ("Sue" "Sue") "Anna") (let ((vec (vector 0 '(2 2 2 2) "Anna"))) - (vector-set! vec 1 '("Sue" "Sue")) - vec))) +(skip-compile (check-equal? "TODO" + 13 + (vector-ref '#(1 1 2 3 5 8 13 21) + (let ([i (round (* 2 (acos -1)))]) + (if (inexact? i) (exact i) i))))) + +(skip-compile (check-equal? "TODO" + #(0 ("Sue" "Sue") "Anna") + (let ([vec (vector 0 '(2 2 2 2) "Anna")]) + (vector-set! vec 1 '("Sue" "Sue")) + vec))) (check-equal? "vector->list" '(dah dah didah) (vector->list '#(dah dah didah))) (check-equal? "vector->list with start" '(dah didah) (vector->list '#(dah dah didah) 1)) @@ -677,31 +700,64 @@ (check-equal? "vector-append" #(a b c d e) (vector-append #(a b c) #(d e))) (check-equal? "vector-append, multiple args" #(a b c d e f) (vector-append #(a b c) #(d e) #(f))) -(skip-compile (check-equal? "TODO" #(1 2 smash smash 5) - (let ((vec (vector 1 2 3 4 5))) (vector-fill! vec 'smash 2 4) vec))) -(skip-compile (check-equal? "TODO" #(x x x x x) - (let ((vec (vector 1 2 3 4 5))) (vector-fill! vec 'x) vec))) -(skip-compile (check-equal? "TODO" #(1 2 x x x) - (let ((vec (vector 1 2 3 4 5))) (vector-fill! vec 'x 2) vec))) -(skip-compile (check-equal? "TODO" #(1 2 x 4 5) - (let ((vec (vector 1 2 3 4 5))) (vector-fill! vec 'x 2 3) vec))) - -(skip-compile (check-equal? "TODO" #(1 a b 4 5) - (let ((vec (vector 1 2 3 4 5))) (vector-copy! vec 1 #(a b c d e) 0 2) vec))) -(skip-compile (check-equal? "TODO" #(a b c d e) - (let ((vec (vector 1 2 3 4 5))) (vector-copy! vec 0 #(a b c d e)) vec))) -(skip-compile (check-equal? "TODO" #(c d e 4 5) - (let ((vec (vector 1 2 3 4 5))) (vector-copy! vec 0 #(a b c d e) 2) vec))) -(skip-compile (check-equal? "TODO" #(1 2 a b c) - (let ((vec (vector 1 2 3 4 5))) (vector-copy! vec 2 #(a b c d e) 0 3) vec))) -(skip-compile (check-equal? "TODO" #(1 2 c 4 5) - (let ((vec (vector 1 2 3 4 5))) (vector-copy! vec 2 #(a b c d e) 2 3) vec))) +(skip-compile (check-equal? "TODO" + #(1 2 smash smash 5) + (let ([vec (vector 1 2 3 4 5)]) + (vector-fill! vec 'smash 2 4) + vec))) +(skip-compile (check-equal? "TODO" + #(x x x x x) + (let ([vec (vector 1 2 3 4 5)]) + (vector-fill! vec 'x) + vec))) +(skip-compile (check-equal? "TODO" + #(1 2 x x x) + (let ([vec (vector 1 2 3 4 5)]) + (vector-fill! vec 'x 2) + vec))) +(skip-compile (check-equal? "TODO" + #(1 2 x 4 5) + (let ([vec (vector 1 2 3 4 5)]) + (vector-fill! vec 'x 2 3) + vec))) + +(skip-compile (check-equal? "TODO" + #(1 a b 4 5) + (let ([vec (vector 1 2 3 4 5)]) + (vector-copy! vec 1 #(a b c d e) 0 2) + vec))) +(skip-compile (check-equal? "TODO" + #(a b c d e) + (let ([vec (vector 1 2 3 4 5)]) + (vector-copy! vec 0 #(a b c d e)) + vec))) +(skip-compile (check-equal? "TODO" + #(c d e 4 5) + (let ([vec (vector 1 2 3 4 5)]) + (vector-copy! vec 0 #(a b c d e) 2) + vec))) +(skip-compile (check-equal? "TODO" + #(1 2 a b c) + (let ([vec (vector 1 2 3 4 5)]) + (vector-copy! vec 2 #(a b c d e) 0 3) + vec))) +(skip-compile (check-equal? "TODO" + #(1 2 c 4 5) + (let ([vec (vector 1 2 3 4 5)]) + (vector-copy! vec 2 #(a b c d e) 2 3) + vec))) ; ;; same source and dest -(skip-compile (check-equal? "TODO" #(1 1 2 4 5) - (let ((vec (vector 1 2 3 4 5))) (vector-copy! vec 1 vec 0 2) vec))) -(skip-compile (check-equal? "TODO" #(1 2 3 1 2) - (let ((vec (vector 1 2 3 4 5))) (vector-copy! vec 3 vec 0 2) vec))) +(skip-compile (check-equal? "TODO" + #(1 1 2 4 5) + (let ([vec (vector 1 2 3 4 5)]) + (vector-copy! vec 1 vec 0 2) + vec))) +(skip-compile (check-equal? "TODO" + #(1 2 3 1 2) + (let ([vec (vector 1 2 3 4 5)]) + (vector-copy! vec 3 vec 0 2) + vec))) ;; -------------- Report ------------------ diff --git a/crates/steel-core/Cargo.toml b/crates/steel-core/Cargo.toml index ea7ac1f30..fdd766cef 100644 --- a/crates/steel-core/Cargo.toml +++ b/crates/steel-core/Cargo.toml @@ -76,12 +76,16 @@ bigdecimal = "0.4.5" # Also for sync stuff crossbeam = "0.8.4" +httparse = "1.9.4" +http = "1.1.0" + [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "*", features = ["js"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] which = { version = "4.4.0" } home = "0.5.9" +polling = "3.7.3" [dev-dependencies] proptest = "1.1.0" diff --git a/crates/steel-core/src/compiler/code_gen.rs b/crates/steel-core/src/compiler/code_gen.rs index 8618bd3ca..9d05b8092 100644 --- a/crates/steel-core/src/compiler/code_gen.rs +++ b/crates/steel-core/src/compiler/code_gen.rs @@ -34,7 +34,7 @@ use crate::rvals::Result; // TODO: Have this interner also be a part of what gets saved... pub(crate) static FUNCTION_ID: AtomicUsize = AtomicUsize::new(0); -fn fresh_function_id() -> usize { +pub fn fresh_function_id() -> usize { FUNCTION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed) } diff --git a/crates/steel-core/src/compiler/compiler.rs b/crates/steel-core/src/compiler/compiler.rs index b96a8decc..4263ebf3a 100644 --- a/crates/steel-core/src/compiler/compiler.rs +++ b/crates/steel-core/src/compiler/compiler.rs @@ -776,7 +776,7 @@ impl Compiler { Ok(expanded_statements) } - fn lower_expressions_impl( + pub(crate) fn lower_expressions_impl( &mut self, exprs: Vec, constants: ImmutableHashMap, @@ -1005,7 +1005,7 @@ impl Compiler { // println!("--- Final AST ---"); // println!(""); - // expanded_statements.pretty_print(); + // steel_parser::ast::AstTools::pretty_print(&expanded_statements); log::debug!(target: "expansion-phase", "Generating instructions"); diff --git a/crates/steel-core/src/parser/kernel.rs b/crates/steel-core/src/parser/kernel.rs index c83594c12..85b0604f3 100644 --- a/crates/steel-core/src/parser/kernel.rs +++ b/crates/steel-core/src/parser/kernel.rs @@ -33,23 +33,38 @@ use super::{ }; thread_local! { - pub(crate) static KERNEL_IMAGE: Engine = Engine::new_bootstrap_kernel(); + pub(crate) static KERNEL_IMAGE: Engine = Engine::new_bootstrap_kernel(false); + pub(crate) static KERNEL_IMAGE_SB: Engine = Engine::new_bootstrap_kernel(true); } #[cfg(feature = "sync")] -pub static STATIC_KERNEL_IMAGE: Lazy = Lazy::new(Engine::new_bootstrap_kernel); +pub static STATIC_KERNEL_IMAGE: Lazy = Lazy::new(|| Engine::new_bootstrap_kernel(false)); +#[cfg(feature = "sync")] +pub static STATIC_KERNEL_IMAGE_SB: Lazy = Lazy::new(|| Engine::new_bootstrap_kernel(true)); -pub(crate) fn fresh_kernel_image() -> Engine { +pub(crate) fn fresh_kernel_image(sandbox: bool) -> Engine { // Just deep clone the env coming out - #[cfg(feature = "sync")] - { - STATIC_KERNEL_IMAGE.clone().deep_clone() - } + if sandbox { + #[cfg(feature = "sync")] + { + STATIC_KERNEL_IMAGE_SB.clone().deep_clone() + } - #[cfg(not(feature = "sync"))] - { - KERNEL_IMAGE.with(|x| x.clone()) + #[cfg(not(feature = "sync"))] + { + KERNEL_IMAGE_SB.with(|x| x.clone()) + } + } else { + #[cfg(feature = "sync")] + { + STATIC_KERNEL_IMAGE.clone().deep_clone() + } + + #[cfg(not(feature = "sync"))] + { + KERNEL_IMAGE.with(|x| x.clone()) + } } } @@ -82,7 +97,7 @@ impl Default for Kernel { impl Kernel { pub fn new() -> Self { - let mut engine = fresh_kernel_image(); + let mut engine = fresh_kernel_image(false); let transformers = Transformers { set: Arc::new(RwLock::new(HashMap::default())), diff --git a/crates/steel-core/src/primitives.rs b/crates/steel-core/src/primitives.rs index e29cc520e..a62ad03d1 100644 --- a/crates/steel-core/src/primitives.rs +++ b/crates/steel-core/src/primitives.rs @@ -4,11 +4,16 @@ mod control; mod fs; pub mod hashmaps; pub mod hashsets; +pub mod http; mod io; pub mod lists; pub mod meta_ops; /// Implements numbers as defined in section 6.2 of the R7RS spec. pub mod numbers; + +#[cfg(not(target_arch = "wasm32"))] +pub mod polling; + pub mod ports; pub mod process; pub mod random; @@ -37,7 +42,7 @@ use crate::{ rvals::SteelString, }; pub use control::ControlOperations; -pub use fs::fs_module; +pub use fs::{fs_module, fs_module_sandbox}; pub use io::IoFunctions; pub use lists::UnRecoverableResult; pub use meta_ops::MetaOperations; diff --git a/crates/steel-core/src/primitives/bytevectors.rs b/crates/steel-core/src/primitives/bytevectors.rs index 13efdf1bb..38699cd62 100644 --- a/crates/steel-core/src/primitives/bytevectors.rs +++ b/crates/steel-core/src/primitives/bytevectors.rs @@ -25,7 +25,9 @@ pub fn bytevector_module() -> BuiltInModule { .register_native_fn_definition(BYTES_TO_LIST_DEFINITION) .register_native_fn_definition(LIST_TO_BYTES_DEFINITION) .register_native_fn_definition(BYTES_APPEND_DEFINITION) - .register_native_fn_definition(BYTES_TO_STRING_DEFINITION); + .register_native_fn_definition(BYTES_TO_STRING_DEFINITION) + .register_native_fn_definition(BYTES_PUSH_DEFINITION) + .register_native_fn_definition(BYTES_CLEAR_DEFINITION); module } @@ -258,6 +260,20 @@ pub fn bytes_set(value: &mut SteelByteVector, index: usize, byte: u8) -> Result< Ok(SteelVal::Void) } +#[function(name = "bytes-push!")] +pub fn bytes_push(value: &mut SteelByteVector, byte: u8) -> Result { + let mut guard = value.vec.write(); + guard.push(byte); + Ok(SteelVal::Void) +} + +#[function(name = "bytes-clear!")] +pub fn bytes_clear(value: &mut SteelByteVector) -> Result { + let mut guard = value.vec.write(); + guard.clear(); + Ok(SteelVal::Void) +} + /// Converts the bytevector to the equivalent list representation. /// /// # Examples diff --git a/crates/steel-core/src/primitives/fs.rs b/crates/steel-core/src/primitives/fs.rs index 3a5b9ad13..775179e04 100644 --- a/crates/steel-core/src/primitives/fs.rs +++ b/crates/steel-core/src/primitives/fs.rs @@ -57,6 +57,11 @@ pub fn fs_module() -> BuiltInModule { module } +#[steel_derive::define_module(name = "steel/filesystem")] +pub fn fs_module_sandbox() -> BuiltInModule { + BuiltInModule::new("steel/filesystem") +} + /// Deletes the directory #[steel_derive::function(name = "delete-directory!")] pub fn delete_directory(directory: &SteelString) -> Result { diff --git a/crates/steel-core/src/primitives/http.rs b/crates/steel-core/src/primitives/http.rs new file mode 100644 index 000000000..8985173c7 --- /dev/null +++ b/crates/steel-core/src/primitives/http.rs @@ -0,0 +1,185 @@ +use crate::{ + gc::{Gc, ShareableMut}, + rvals::{AsRefSteelVal, Custom, IntoSteelVal, SteelByteVector, SteelHashMap, SteelString}, + steel_vm::builtin::BuiltInModule, + SteelVal, +}; + +use crate::rvals::Result; + +pub struct Header { + pub name: String, + pub value: Vec, +} + +pub struct SteelRequest { + method: SteelString, + path: SteelString, + version: SteelString, + // Offset into the buffer where the body starts + body_offset: usize, + // Probably just... push down directly into a hashmap? + // or keep it some kind of key value pair store? + headers: Vec
, +} + +impl Custom for SteelRequest {} + +pub struct SteelResponse { + pub version: u8, + /// The response code, such as `200`. + pub code: u16, + /// The response reason-phrase, such as `OK`. + /// + /// Contains an empty string if the reason-phrase was missing or contained invalid characters. + pub reason: String, + /// The response headers. + pub headers: Vec
, + + pub body_offset: usize, +} + +impl Custom for SteelResponse {} + +#[steel_derive::function(name = "http-request-method")] +pub fn method(value: &SteelVal) -> Result { + SteelRequest::as_ref(value) + .map(|x| x.method.clone()) + .map(SteelVal::StringV) +} + +#[steel_derive::function(name = "http-request-path")] +pub fn path(value: &SteelVal) -> Result { + SteelRequest::as_ref(value) + .map(|x| x.path.clone()) + .map(SteelVal::StringV) +} + +#[steel_derive::function(name = "http-request-version")] +pub fn version(value: &SteelVal) -> Result { + SteelRequest::as_ref(value) + .map(|x| x.version.clone()) + .map(SteelVal::StringV) +} + +#[steel_derive::function(name = "http-request-body-offset")] +pub fn body_offset(value: &SteelVal) -> Result { + SteelRequest::as_ref(value) + .map(|x| x.body_offset as isize) + .map(SteelVal::IntV) +} + +#[steel_derive::function(name = "http-request-headers")] +pub fn headers(value: &SteelVal) -> Result { + let req = SteelRequest::as_ref(value)?; + + Ok(SteelVal::HashMapV(SteelHashMap(Gc::new( + req.headers + .iter() + .map(|x| { + ( + SteelVal::StringV(x.name.clone().into()), + SteelVal::ByteVector(SteelByteVector::new(x.value.clone())), + ) + }) + .collect::>(), + )))) +} + +// If not complete, try again? +fn parse_request(buf: &[u8]) -> Result { + // Pull more bytes from the stream? + let mut headers = [httparse::EMPTY_HEADER; 16]; + let mut req = httparse::Request::new(&mut headers); + let res = req.parse(&buf).unwrap(); + if res.is_complete() { + let request = SteelRequest { + method: req.method.unwrap().to_string().into(), + path: req.path.unwrap().to_string().into(), + version: req.version.unwrap().to_string().into(), + body_offset: res.unwrap(), + headers: headers + .iter() + .filter_map(|x| { + if *x != httparse::EMPTY_HEADER { + Some(Header { + name: x.name.to_string(), + value: x.value.to_vec(), + }) + } else { + None + } + }) + .collect(), + }; + + request.into_steelval() + } else { + Ok(SteelVal::BoolV(false)) + } +} + +fn parse_response(buf: &[u8]) -> Result { + // Pull more bytes from the stream? + let mut headers = [httparse::EMPTY_HEADER; 16]; + let mut req = httparse::Response::new(&mut headers); + let res = req.parse(&buf).unwrap(); + if res.is_complete() { + let request = SteelResponse { + version: req.version.unwrap(), + code: req.code.unwrap(), + reason: req.reason.unwrap().to_string(), + body_offset: res.unwrap(), + headers: headers + .iter() + .filter_map(|x| { + if *x != httparse::EMPTY_HEADER { + Some(Header { + name: x.name.to_string(), + value: x.value.to_vec(), + }) + } else { + None + } + }) + .collect(), + }; + + request.into_steelval() + } else { + Ok(SteelVal::BoolV(false)) + } +} + +#[steel_derive::function(name = "http-parse-request")] +pub fn parse_http_request(vector: &SteelByteVector) -> Result { + parse_request(&vector.vec.read()) +} + +#[steel_derive::function(name = "http-parse-response")] +pub fn parse_http_response(vector: &SteelByteVector) -> Result { + parse_response(&vector.vec.read()) +} + +pub fn http_module() -> BuiltInModule { + let mut module = BuiltInModule::new("steel/http".to_string()); + + module + .register_native_fn_definition(PARSE_HTTP_REQUEST_DEFINITION) + .register_native_fn_definition(METHOD_DEFINITION) + .register_native_fn_definition(VERSION_DEFINITION) + .register_native_fn_definition(PATH_DEFINITION) + .register_native_fn_definition(BODY_OFFSET_DEFINITION) + .register_native_fn_definition(HEADERS_DEFINITION) + .register_native_fn_definition(PARSE_HTTP_RESPONSE_DEFINITION); + + // module + // .register_native_fn_definition(TCP_CONNECT_DEFINITION) + // .register_native_fn_definition(TCP_INPUT_PORT_DEFINITION) + // .register_native_fn_definition(TCP_OUTPUT_PORT_DEFINITION) + // .register_native_fn_definition(TCP_BUFFERED_OUTPUT_PORT_DEFINITION) + // .register_native_fn_definition(TCP_LISTEN_DEFINITION) + // .register_native_fn_definition(TCP_ACCEPT_DEFINITION); + + module +} diff --git a/crates/steel-core/src/primitives/polling.rs b/crates/steel-core/src/primitives/polling.rs new file mode 100644 index 000000000..731d95917 --- /dev/null +++ b/crates/steel-core/src/primitives/polling.rs @@ -0,0 +1,150 @@ +use polling::{Event, Events, Poller}; +use std::{cell::RefCell, net::TcpListener, sync::atomic::AtomicUsize}; + +use crate::{ + rvals::{AsRefSteelVal, Custom, IntoSteelVal, Result}, + steel_vm::builtin::BuiltInModule, + SteelVal, +}; + +impl Custom for Poller {} +impl Custom for Events {} + +static KEY_ID: AtomicUsize = AtomicUsize::new(0); + +#[steel_derive::function(name = "fresh-event-id")] +fn fresh_key() -> Result { + KEY_ID + .fetch_add(1, std::sync::atomic::Ordering::Relaxed) + .into_steelval() +} + +#[steel_derive::function(name = "make-poller")] +pub fn new_poller() -> Result { + Poller::new()?.into_steelval() +} + +#[steel_derive::function(name = "add-event-interest-read")] +pub fn add_read(poller: &SteelVal, socket: &SteelVal, key: usize) -> Result { + unsafe { + Poller::as_ref(poller)?.add(&*(TcpListener::as_ref(socket)?), Event::readable(key))?; + Ok(SteelVal::Void) + } +} + +#[steel_derive::function(name = "add-event-interest-write")] +pub fn add_write(poller: &SteelVal, socket: &SteelVal, key: usize) -> Result { + unsafe { + Poller::as_ref(poller)?.add(&*(TcpListener::as_ref(socket)?), Event::writable(key))?; + Ok(SteelVal::Void) + } +} + +#[steel_derive::function(name = "add-event-interest-all")] +pub fn add_all(poller: &SteelVal, socket: &SteelVal, key: usize) -> Result { + unsafe { + Poller::as_ref(poller)?.add(&*(TcpListener::as_ref(socket)?), Event::all(key))?; + Ok(SteelVal::Void) + } +} + +// Probably, just need to have a thread local events? +thread_local! { + pub static EVENTS: RefCell = RefCell::new(Events::new()); +} + +#[steel_derive::function(name = "events-clear!")] +pub fn clear_events() { + EVENTS.with(|x| x.borrow_mut().clear()); +} + +#[steel_derive::function(name = "poller-wait")] +pub fn poller_wait(poller: &SteelVal) -> Result { + let poller = Poller::as_ref(poller)?; + EVENTS.with(|x| poller.wait(&mut x.borrow_mut(), None))?; + Ok(SteelVal::Void) +} + +#[steel_derive::function(name = "events->list")] +pub fn events() -> Result { + Ok(SteelVal::ListV(EVENTS.with(|x| { + x.borrow() + .iter() + .map(|x| SteelVal::IntV(x.key as _)) + .collect::>() + }))) +} + +#[steel_derive::function(name = "modify-event-interest-read!")] +pub fn modify_read(poller: &SteelVal, socket: &SteelVal, key: usize) -> Result { + Poller::as_ref(poller)?.modify(&*(TcpListener::as_ref(socket)?), Event::readable(key))?; + Ok(SteelVal::Void) +} + +#[steel_derive::function(name = "modify-event-interest-write!")] +pub fn modify_write(poller: &SteelVal, socket: &SteelVal, key: usize) -> Result { + Poller::as_ref(poller)?.modify(&*(TcpListener::as_ref(socket)?), Event::writable(key))?; + Ok(SteelVal::Void) +} + +#[steel_derive::function(name = "modify-event-interest-all!")] +pub fn modify_all(poller: &SteelVal, socket: &SteelVal, key: usize) -> Result { + Poller::as_ref(poller)?.modify(&*(TcpListener::as_ref(socket)?), Event::all(key))?; + Ok(SteelVal::Void) +} + +#[steel_derive::function(name = "poller-delete!")] +pub fn delete_interest(poller: &SteelVal, socket: &SteelVal) -> Result { + Poller::as_ref(poller)?.delete(&*(TcpListener::as_ref(socket)?))?; + Ok(SteelVal::Void) +} + +pub fn polling_module() -> BuiltInModule { + let mut module = BuiltInModule::new("steel/polling".to_string()); + + module + .register_native_fn_definition(FRESH_KEY_DEFINITION) + .register_native_fn_definition(NEW_POLLER_DEFINITION) + .register_native_fn_definition(ADD_READ_DEFINITION) + .register_native_fn_definition(ADD_WRITE_DEFINITION) + .register_native_fn_definition(ADD_ALL_DEFINITION) + .register_native_fn_definition(CLEAR_EVENTS_DEFINITION) + .register_native_fn_definition(POLLER_WAIT_DEFINITION) + .register_native_fn_definition(EVENTS_DEFINITION) + .register_native_fn_definition(CLEAR_EVENTS_DEFINITION) + .register_native_fn_definition(MODIFY_READ_DEFINITION) + .register_native_fn_definition(MODIFY_WRITE_DEFINITION) + .register_native_fn_definition(MODIFY_ALL_DEFINITION) + .register_native_fn_definition(DELETE_INTEREST_DEFINITION); + + module +} + +// unsafe fn test() -> std::result::Result<(), Box> { +// // Create a TCP listener. +// let socket = TcpListener::bind("127.0.0.1:8000")?; +// socket.set_nonblocking(true)?; +// let key = 7; // Arbitrary key identifying the socket. + +// // Create a poller and register interest in readability on the socket. +// let poller = Poller::new()?; +// poller.add(&socket, Event::readable(key))?; + +// // The event loop. +// let mut events = Events::new(); +// loop { +// // Wait for at least one I/O event. +// events.clear(); +// poller.wait(&mut events, None)?; + +// for ev in events.iter() { +// // Map the key to the event properly +// if ev.key == key { +// // Perform a non-blocking accept operation. +// socket.accept()?; +// // Set interest in the next readability event. +// poller.modify(&socket, Event::readable(key))?; +// } +// } +// } +// } diff --git a/crates/steel-core/src/primitives/ports.rs b/crates/steel-core/src/primitives/ports.rs index 710bc1e96..18a445d74 100644 --- a/crates/steel-core/src/primitives/ports.rs +++ b/crates/steel-core/src/primitives/ports.rs @@ -3,7 +3,7 @@ use crate::gc::Gc; use crate::rvals::{RestArgsIter, Result, SteelByteVector, SteelString, SteelVal}; use crate::steel_vm::builtin::BuiltInModule; use crate::stop; -use crate::values::port::{SteelPort, SteelPortRepr}; +use crate::values::port::{would_block, SteelPort, SteelPortRepr, WOULD_BLOCK_OBJECT}; use crate::values::structs::{make_struct_singleton, StructTypeDescriptor}; use steel_derive::function; @@ -50,7 +50,48 @@ pub fn port_module() -> BuiltInModule { .register_native_fn_definition(READ_CHAR_DEFINITION) .register_native_fn_definition(WRITE_BYTE_DEFINITION) .register_native_fn_definition(WRITE_BYTES_DEFINITION) - .register_native_fn_definition(PEEK_BYTE_DEFINITION); + .register_native_fn_definition(PEEK_BYTE_DEFINITION) + .register_native_fn_definition(READ_BYTES_DEFINITION) + .register_native_fn_definition(READ_BYTES_INTO_BUF_DEFINITION) + .register_native_fn_definition(WOULD_BLOCK_OBJECTP_DEFINITION) + .register_native_fn_definition(WOULD_BLOCK_OBJECT_DEFINITION); + module +} + +pub fn port_module_without_filesystem() -> BuiltInModule { + let mut module = BuiltInModule::new("steel/ports"); + module + .register_native_fn_definition(OPEN_STDIN_DEFINITION) + .register_native_fn_definition(OPEN_STDOUT_DEFINITION) + .register_native_fn_definition(OPEN_OUTPUT_STRING_DEFINITION) + .register_native_fn_definition(OPEN_OUTPUT_BYTEVECTOR_DEFINITION) + .register_native_fn_definition(WRITE_LINE_DEFINITION) + .register_native_fn_definition(WRITE_STRING_DEFINITION) + .register_native_fn_definition(WRITE_DEFINITION) + .register_native_fn_definition(WRITE_CHAR_DEFINITION) + .register_native_fn_definition(FLUSH_OUTPUT_PORT_DEFINITION) + .register_native_fn_definition(READ_PORT_TO_STRING_DEFINITION) + .register_native_fn_definition(READ_LINE_TO_STRING_DEFINITION) + .register_native_fn_definition(GET_OUTPUT_STRING_DEFINITION) + .register_native_fn_definition(GET_OUTPUT_BYTEVECTOR_DEFINITION) + .register_native_fn_definition(IS_INPUT_DEFINITION) + .register_native_fn_definition(IS_OUTPUT_DEFINITION) + .register_native_fn_definition(DEFAULT_INPUT_PORT_DEFINITION) + .register_native_fn_definition(DEFAULT_OUTPUT_PORT_DEFINITION) + .register_native_fn_definition(CLOSE_OUTPUT_PORT_DEFINITION) + .register_native_fn_definition(DEFAULT_ERROR_PORT_DEFINITION) + .register_native_fn_definition(EOF_OBJECT_DEFINITION) + .register_native_fn_definition(OPEN_INPUT_STRING_DEFINITION) + .register_native_fn_definition(OPEN_INPUT_BYTEVECTOR_DEFINITION) + .register_native_fn_definition(READ_BYTE_DEFINITION) + .register_native_fn_definition(READ_CHAR_DEFINITION) + .register_native_fn_definition(WRITE_BYTE_DEFINITION) + .register_native_fn_definition(WRITE_BYTES_DEFINITION) + .register_native_fn_definition(PEEK_BYTE_DEFINITION) + .register_native_fn_definition(READ_BYTES_DEFINITION) + .register_native_fn_definition(READ_BYTES_INTO_BUF_DEFINITION) + .register_native_fn_definition(WOULD_BLOCK_OBJECTP_DEFINITION) + .register_native_fn_definition(WOULD_BLOCK_OBJECT_DEFINITION); module } @@ -369,6 +410,31 @@ pub fn eof_object() -> SteelVal { eof() } +#[function(name = "would-block")] +pub fn would_block_object() -> SteelVal { + would_block() +} + +/// Returns `#t` if the value is an EOF object. +/// +/// (eof-object? any/c) -> bool? +#[function(name = "would-block-object?")] +pub fn would_block_objectp(value: &SteelVal) -> bool { + let SteelVal::CustomStruct(struct_) = value else { + return false; + }; + + #[cfg(feature = "sync")] + { + struct_.type_descriptor == WOULD_BLOCK_OBJECT.1 + } + + #[cfg(not(feature = "sync"))] + { + WOULD_BLOCK_OBJECT.with(|eof| struct_.type_descriptor == eof.1) + } +} + /// Reads a single byte from an input port. /// /// (read-byte [port]) -> byte? @@ -378,10 +444,65 @@ pub fn eof_object() -> SteelVal { pub fn read_byte(rest: RestArgsIter<&SteelPort>) -> Result { let port = input_args(rest)?; - Ok(port - .read_byte()? - .map(|b| SteelVal::IntV(b.into())) - .unwrap_or_else(eof)) + let maybe_byte = port.read_byte()?; + + match maybe_byte { + crate::values::port::MaybeBlocking::Nonblocking(b) => { + Ok(b.map(|b| SteelVal::IntV(b.into())).unwrap_or_else(eof)) + } + crate::values::port::MaybeBlocking::WouldBlock => Ok(would_block_object()), + } + + // Ok(port + // .read_byte()? + // .map(|b| SteelVal::IntV(b.into())) + // .unwrap_or_else(eof)) +} + +/// Reads bytes from an input port. +/// +/// (read-bytes amt [port]) -> bytes? +/// +/// * amt : (and positive? int?) +/// * port : input-port? = (current-input-port) +#[function(name = "read-bytes")] +pub fn read_bytes(amt: usize, rest: RestArgsIter<&SteelPort>) -> Result { + let port = input_args(rest)?; + + let bytes = port.read_bytes(amt)?; + + match bytes { + crate::values::port::MaybeBlocking::Nonblocking(b) => { + Ok(SteelVal::ByteVector(SteelByteVector::new(b).into())) + } + crate::values::port::MaybeBlocking::WouldBlock => Ok(would_block_object()), + } +} + +/// Reads bytes from an input port into a given buffer. +/// +/// (read-bytes-into-buf buf amt [port]) -> bytes? +/// +/// * buf : bytes? +/// * amt : (and positive? int?) +/// * port : input-port? = (current-input-port) +#[function(name = "read-bytes-into-buf")] +pub fn read_bytes_into_buf( + buf: &SteelByteVector, + amt: usize, + rest: RestArgsIter<&SteelPort>, +) -> Result { + let port = input_args(rest)?; + + let mut guard = buf.vec.write(); + + if guard.len() < amt { + stop!(ContractViolation => "read-bytes-into-buf expects a buffer with the capacity to fill the buffer with the specified amount"); + } + + port.read_bytes_into_buf(&mut guard)?; + + Ok(SteelVal::Void) } /// Writes a single byte to an output port. @@ -435,7 +556,15 @@ pub fn peek_byte(rest: RestArgsIter<&SteelPort>) -> Result { #[function(name = "read-char")] pub fn read_char(rest: RestArgsIter<&SteelPort>) -> Result { let port = input_args(rest)?; - Ok(port.read_char()?.map(SteelVal::CharV).unwrap_or_else(eof)) + + let char = port.read_char()?; + + match char { + crate::values::port::MaybeBlocking::Nonblocking(c) => { + Ok(c.map(SteelVal::CharV).unwrap_or_else(eof)) + } + crate::values::port::MaybeBlocking::WouldBlock => Ok(would_block_object()), + } } fn input_args(args: RestArgsIter<&SteelPort>) -> Result { diff --git a/crates/steel-core/src/primitives/tcp.rs b/crates/steel-core/src/primitives/tcp.rs index 871eb5564..538f58569 100644 --- a/crates/steel-core/src/primitives/tcp.rs +++ b/crates/steel-core/src/primitives/tcp.rs @@ -6,6 +6,7 @@ use steel_derive::function; use crate::gc::Gc; use crate::rvals::{AsRefSteelVal, Custom, IntoSteelVal, Result, SteelString, SteelVal}; use crate::steel_vm::builtin::BuiltInModule; +use crate::values::lists::Pair; use crate::values::port::SteelPort; use crate::values::SteelPortRepr; @@ -22,13 +23,13 @@ pub fn tcp_connect(addr: SteelString) -> Result { TcpStream::connect(addr.as_str())?.into_steelval() } -#[function(name = "tcp-stream-input-port")] +#[function(name = "tcp-stream-writer")] pub fn tcp_input_port(stream: &SteelVal) -> Result { let writer = TcpStream::as_ref(stream)?.try_clone().unwrap(); Ok(SteelVal::new_dyn_writer_port(writer)) } -#[function(name = "tcp-stream-output-port")] +#[function(name = "tcp-stream-reader")] pub fn tcp_output_port(stream: &SteelVal) -> Result { let reader = TcpStream::as_ref(stream)?.try_clone().unwrap(); Ok(SteelVal::PortV(SteelPort { @@ -36,7 +37,7 @@ pub fn tcp_output_port(stream: &SteelVal) -> Result { })) } -#[function(name = "tcp-stream-buffered-output-port")] +#[function(name = "tcp-stream-buffered-reader")] pub fn tcp_buffered_output_port(stream: &SteelVal) -> Result { let reader = TcpStream::as_ref(stream)?.try_clone().unwrap(); Ok(SteelVal::PortV(SteelPort { @@ -54,6 +55,28 @@ pub fn tcp_accept(value: &SteelVal) -> Result { TcpListener::as_ref(value)?.accept()?.0.into_steelval() } +#[function(name = "tcp-accept-with-addr")] +pub fn tcp_accept_addr(value: &SteelVal) -> Result { + let res = TcpListener::as_ref(value)?.accept()?; + + Ok(SteelVal::Pair(Gc::new(Pair::cons( + res.0.into_steelval()?, + res.1.to_string().into_steelval()?, + )))) +} + +#[function(name = "tcp-listener-set-non-blocking!")] +pub fn tcp_listener_set_non_blocking(value: &SteelVal) -> Result { + TcpListener::as_ref(value)?.set_nonblocking(true)?; + Ok(SteelVal::Void) +} + +#[function(name = "tcp-stream-set-non-blocking!")] +pub fn tcp_stream_set_non_blocking(value: &SteelVal) -> Result { + TcpStream::as_ref(value)?.set_nonblocking(true)?; + Ok(SteelVal::Void) +} + pub fn tcp_module() -> BuiltInModule { let mut module = BuiltInModule::new("steel/tcp".to_string()); @@ -63,7 +86,10 @@ pub fn tcp_module() -> BuiltInModule { .register_native_fn_definition(TCP_OUTPUT_PORT_DEFINITION) .register_native_fn_definition(TCP_BUFFERED_OUTPUT_PORT_DEFINITION) .register_native_fn_definition(TCP_LISTEN_DEFINITION) - .register_native_fn_definition(TCP_ACCEPT_DEFINITION); + .register_native_fn_definition(TCP_ACCEPT_DEFINITION) + .register_native_fn_definition(TCP_ACCEPT_ADDR_DEFINITION) + .register_native_fn_definition(TCP_LISTENER_SET_NON_BLOCKING_DEFINITION) + .register_native_fn_definition(TCP_STREAM_SET_NON_BLOCKING_DEFINITION); module } diff --git a/crates/steel-core/src/rvals/cycles.rs b/crates/steel-core/src/rvals/cycles.rs index c4aa4097b..25ef55a9f 100644 --- a/crates/steel-core/src/rvals/cycles.rs +++ b/crates/steel-core/src/rvals/cycles.rs @@ -1,41 +1,11 @@ use crate::gc::shared::{MutableContainer, ShareableMut}; -use crate::steel_vm::{ - builtin::get_function_name, engine::Engine, vm::Continuation, vm::ContinuationMark, -}; +use crate::steel_vm::{builtin::get_function_name, vm::Continuation, vm::ContinuationMark}; use crate::values::lists::Pair; use num::BigInt; use std::{cell::Cell, collections::VecDeque}; use super::*; -thread_local! { - // Use this to print values, in lieu of a bespoke printer - static PRINTING_KERNEL: RefCell = { - - let mut engine = Engine::new_printer(); - - engine.run(include_str!("../scheme/print.scm")).unwrap(); - - RefCell::new(engine) - }; -} - -pub fn install_printer() { - PRINTING_KERNEL.with(|x| { - x.borrow().globals(); - }); -} - -#[steel_derive::function(name = "print-in-engine")] -pub fn print_in_engine(value: SteelVal) { - PRINTING_KERNEL - .with(|x| { - x.borrow_mut() - .call_function_by_name_with_args("print", vec![value]) - }) - .unwrap(); -} - #[derive(Default)] // Keep track of any reference counted values that are visited, in a pointer pub(super) struct CycleDetector { diff --git a/crates/steel-core/src/scheme/modules/reader.scm b/crates/steel-core/src/scheme/modules/reader.scm index 346163523..07cd65c36 100644 --- a/crates/steel-core/src/scheme/modules/reader.scm +++ b/crates/steel-core/src/scheme/modules/reader.scm @@ -2,17 +2,24 @@ (require "steel/result") (require "#%private/steel/control") -(provide read) +(provide read + read-syntax-object) (define *reader* (reader.new-reader)) (define (read . port) (if (null? port) - (read-impl) + (read-impl reader.reader-read-one) (parameterize ([current-input-port (car port)]) - (read-impl)))) + (read-impl reader.reader-read-one)))) -(define (read-impl) +(define (read-syntax-object . port) + (if (null? port) + (read-impl reader.reader-read-one-syntax-object) + (parameterize ([current-input-port (car port)]) + (read-impl reader.reader-read-one-syntax-object)))) + +(define (read-impl finisher) (cond [(reader.reader-empty? *reader*) (define next-line (read-line-from-port (current-input-port))) @@ -20,18 +27,14 @@ [(string? next-line) (reader.reader-push-string *reader* next-line) - (let ([next (reader.reader-read-one *reader*)]) - + (let ([next (finisher *reader*)]) (if (void? next) (begin - ; (displayln "pushing another string") (reader.reader-push-string *reader* (read-line-from-port (current-input-port))) - (read-impl)) + (read-impl finisher)) next))] - [else - => - next-line])] + [else next-line])] ;; The reader is not empty! [else @@ -40,7 +43,6 @@ (if (void? next) (begin - ; (displayln "pushing another string") (reader.reader-push-string *reader* (read-line-from-port (current-input-port))) - (read-impl)) + (read-impl finisher)) next))])) diff --git a/crates/steel-core/src/steel_vm/builtin.rs b/crates/steel-core/src/steel_vm/builtin.rs index f86535650..07a430222 100644 --- a/crates/steel-core/src/steel_vm/builtin.rs +++ b/crates/steel-core/src/steel_vm/builtin.rs @@ -395,6 +395,10 @@ impl BuiltInModuleRepr { } } + pub fn try_get(&self, name: String) -> Option { + self.values.get(name.as_str()).cloned() + } + /// This does the boot strapping for bundling modules /// Rather than expose a native hash-get, the built in module above should expose a raw /// function to fetch a dependency. It will be a packaged # with only a function to @@ -647,6 +651,10 @@ impl BuiltInModule { self.module.read().get(name) } + pub fn try_get(&self, name: String) -> Option { + self.module.read().try_get(name) + } + /// This does the boot strapping for bundling modules /// Rather than expose a native hash-get, the built in module above should expose a raw /// function to fetch a dependency. It will be a packaged # with only a function to diff --git a/crates/steel-core/src/steel_vm/engine.rs b/crates/steel-core/src/steel_vm/engine.rs index f425ac9d9..d3a51be14 100644 --- a/crates/steel-core/src/steel_vm/engine.rs +++ b/crates/steel-core/src/steel_vm/engine.rs @@ -2,7 +2,7 @@ use super::{ builtin::{BuiltInModule, FunctionSignatureMetadata}, - primitives::{register_builtin_modules, register_builtin_modules_without_io, CONSTANTS}, + primitives::{register_builtin_modules, CONSTANTS}, vm::{SteelThread, ThreadStateController}, }; @@ -23,7 +23,10 @@ use crate::{ }, }, containers::RegisterValue, - core::{instructions::Instruction, labels::Expr}, + core::{ + instructions::{pretty_print_dense_instructions, DenseInstruction, Instruction}, + labels::Expr, + }, gc::{ unsafe_erased_pointers::{ BorrowedObject, CustomReference, OpaqueReferenceNursery, ReadOnlyBorrowedObject, @@ -40,12 +43,15 @@ use crate::{ }, rerrs::{back_trace, back_trace_to_string}, rvals::{ - cycles::{install_printer, print_in_engine, PRINT_IN_ENGINE_DEFINITION}, - FromSteelVal, IntoSteelVal, MaybeSendSyncStatic, Result, SteelVal, + AsRefMutSteelVal, AsRefSteelVal as _, FromSteelVal, IntoSteelVal, MaybeSendSyncStatic, + Result, SteelString, SteelVal, }, steel_vm::register_fn::RegisterFn, stop, throw, - values::{closed::GlobalSlotRecycler, functions::BoxedDynFunction}, + values::{ + closed::GlobalSlotRecycler, + functions::{BoxedDynFunction, ByteCodeLambda}, + }, SteelErr, }; use std::{ @@ -64,6 +70,7 @@ use crate::values::HashMap as ImmutableHashMap; use fxhash::{FxBuildHasher, FxHashMap}; use lasso::ThreadedRodeo; use once_cell::sync::OnceCell; +use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use steel_gen::OpCode; use steel_parser::{ @@ -175,6 +182,9 @@ impl EngineId { } } +/// Handle to a steel engine. This contains a main entrypoint thread, alongside +/// the compiler and all of the state necessary to keep a VM instance alive and +/// well. #[derive(Clone)] pub struct Engine { pub(crate) virtual_machine: SteelThread, @@ -367,6 +377,17 @@ thread_local! { pub(crate) static DEFAULT_PRELUDE_MACROS: RefCell> = RefCell::new(HashMap::default()); } +pub(crate) struct SelfCompiler { + pub(crate) compiler: Option<*mut Compiler>, + pub(crate) sources: Sources, + pub(crate) modules: ModuleContainer, + pub(crate) constants: ImmutableHashMap, +} + +unsafe impl Send for SelfCompiler {} +unsafe impl Sync for SelfCompiler {} +impl crate::rvals::Custom for SelfCompiler {} + impl Engine { #[cfg(feature = "sync")] pub(crate) fn deep_clone(&self) -> Self { @@ -385,19 +406,20 @@ impl Engine { /// Function to access a kernel level execution environment /// Has access to primitives and syntax rules, but will not defer to a child /// kernel in the compiler - pub(crate) fn new_kernel() -> Self { + pub(crate) fn new_kernel(sandbox: bool) -> Self { log::debug!(target:"kernel", "Instantiating a new kernel"); #[cfg(feature = "profiling")] let mut total_time = std::time::Instant::now(); #[cfg(feature = "profiling")] let mut now = std::time::Instant::now(); + let sources = Sources::new(); let mut vm = Engine { - virtual_machine: SteelThread::new(), + virtual_machine: SteelThread::new(sources.clone()), compiler: Compiler::default(), constants: None, modules: ModuleContainer::default(), - sources: Sources::new(), + sources, #[cfg(feature = "dylibs")] dylibs: DylibContainers::new(), id: EngineId::new(), @@ -406,7 +428,7 @@ impl Engine { time!( "engine-creation", "Registering builtin modules", - register_builtin_modules(&mut vm) + register_builtin_modules(&mut vm, sandbox) ); // These are used for creating the bootstrapped image @@ -516,11 +538,7 @@ impl Engine { /// Function to access a kernel level execution environment /// Has access to primitives and syntax rules, but will not defer to a child /// kernel in the compiler - pub(crate) fn new_bootstrap_kernel() -> Self { - // if !install_drop_handler() { - // panic!("Unable to install the drop handler!"); - // } - + pub(crate) fn new_bootstrap_kernel(sandbox: bool) -> Self { // If the interner has already been initialized, it most likely means that either: // 1) Tests are being run // 2) The parser was used in a standalone fashion, somewhere, which invalidates the bootstrap @@ -530,11 +548,11 @@ impl Engine { // however given that its a huge chore to pass around the interner everywhere there are strings, // its probably inevitable we have that. if get_interner().is_some() { - return Engine::new_kernel(); + return Engine::new_kernel(sandbox); } if matches!(option_env!("STEEL_BOOTSTRAP"), Some("false") | None) { - let mut vm = Engine::new_kernel(); + let mut vm = Engine::new_kernel(sandbox); let sources = vm.sources.clone(); @@ -547,19 +565,21 @@ impl Engine { log::debug!(target:"kernel", "Instantiating a new kernel"); + let sources = Sources::new(); + let mut vm = Engine { - virtual_machine: SteelThread::new(), + virtual_machine: SteelThread::new(sources.clone()), compiler: Compiler::default(), constants: None, modules: ModuleContainer::default(), - sources: Sources::new(), + sources, #[cfg(feature = "dylibs")] dylibs: DylibContainers::new(), id: EngineId::new(), }; if let Some(programs) = Engine::load_from_bootstrap(&mut vm) { - register_builtin_modules(&mut vm); + register_builtin_modules(&mut vm, sandbox); for program in programs { vm.compiler.constant_map = program.constant_map.clone(); @@ -578,7 +598,7 @@ impl Engine { vm } else { - let mut vm = Engine::new_kernel(); + let mut vm = Engine::new_kernel(sandbox); let sources = vm.sources.clone(); @@ -658,18 +678,20 @@ impl Engine { // Create kernel bootstrap pub fn create_kernel_bootstrap_from_programs(output_path: PathBuf) { + let sources = Sources::new(); + let mut vm = Engine { - virtual_machine: SteelThread::new(), + virtual_machine: SteelThread::new(sources.clone()), compiler: Compiler::default(), constants: None, modules: ModuleContainer::default(), - sources: Sources::new(), + sources, #[cfg(feature = "dylibs")] dylibs: DylibContainers::new(), id: EngineId::new(), }; - register_builtin_modules(&mut vm); + register_builtin_modules(&mut vm, false); let mut programs = Vec::new(); @@ -710,18 +732,19 @@ impl Engine { } pub fn create_new_engine_from_bootstrap(output_path: PathBuf) { + let sources = Sources::new(); let mut vm = Engine { - virtual_machine: SteelThread::new(), + virtual_machine: SteelThread::new(sources.clone()), compiler: Compiler::default(), constants: None, modules: ModuleContainer::default(), - sources: Sources::new(), + sources, #[cfg(feature = "dylibs")] dylibs: DylibContainers::new(), id: EngineId::new(), }; - register_builtin_modules(&mut vm); + register_builtin_modules(&mut vm, false); let mut pre_kernel_programs = Vec::new(); @@ -817,20 +840,21 @@ impl Engine { pub fn top_level_load_from_bootstrap(bin: &[u8]) -> Engine { let bootstrap: StartupBootstrapImage = bincode::deserialize(bin).unwrap(); + let sources = Sources::new(); // This is going to be the kernel let mut vm = Engine { - virtual_machine: SteelThread::new(), + virtual_machine: SteelThread::new(sources.clone()), compiler: Compiler::default(), constants: None, modules: ModuleContainer::default(), - sources: Sources::new(), + sources, #[cfg(feature = "dylibs")] dylibs: DylibContainers::new(), id: EngineId::new(), }; // Register the modules - register_builtin_modules(&mut vm); + register_builtin_modules(&mut vm, false); // Set the syntax object id to be AFTER the previous items have been parsed SYNTAX_OBJECT_ID.store( @@ -893,18 +917,20 @@ impl Engine { } fn create_bootstrap() { + let sources = Sources::new(); + let mut vm = Engine { - virtual_machine: SteelThread::new(), + virtual_machine: SteelThread::new(sources.clone()), compiler: Compiler::default(), constants: None, modules: ModuleContainer::default(), - sources: Sources::new(), + sources, #[cfg(feature = "dylibs")] dylibs: DylibContainers::new(), id: EngineId::new(), }; - register_builtin_modules(&mut vm); + register_builtin_modules(&mut vm, false); let mut asts = Vec::new(); @@ -952,12 +978,14 @@ impl Engine { /// assert!(vm.run("(+ 1 2 3").is_err()); // + is a free identifier /// ``` pub fn new_raw() -> Self { + let sources = Sources::new(); + Engine { - virtual_machine: SteelThread::new(), + virtual_machine: SteelThread::new(sources.clone()), compiler: Compiler::default_with_kernel(), constants: None, modules: ModuleContainer::default(), - sources: Sources::new(), + sources, #[cfg(feature = "dylibs")] dylibs: DylibContainers::new(), id: EngineId::new(), @@ -993,14 +1021,11 @@ impl Engine { pub fn new_base() -> Self { let mut vm = Engine::new_raw(); // Embed any primitives that we want to use - - register_builtin_modules(&mut vm); + register_builtin_modules(&mut vm, false); vm.compile_and_run_raw_program(crate::steel_vm::primitives::ALL_MODULES) .unwrap(); - // vm.dylibs.load_modules(&mut vm); - vm } @@ -1012,20 +1037,25 @@ impl Engine { #[inline] pub fn new_sandboxed() -> Self { - let mut vm = Engine::new_raw(); - - register_builtin_modules_without_io(&mut vm); + let mut engine = fresh_kernel_image(true); - vm.compile_and_run_raw_program(crate::steel_vm::primitives::SANDBOXED_MODULES) - .unwrap(); + engine.compiler.kernel = Some(Kernel::new()); - let core_libraries = [crate::stdlib::PRELUDE]; + #[cfg(feature = "profiling")] + let now = std::time::Instant::now(); - for core in core_libraries.into_iter() { - vm.compile_and_run_raw_program(core).unwrap(); + if let Err(e) = engine.run(PRELUDE_WITHOUT_BASE) { + raise_error(&engine.sources, e); + panic!("This shouldn't happen!"); } - vm + #[cfg(feature = "profiling")] + log::info!(target: "engine-creation", "Engine Creation: {:?}", now.elapsed()); + + // Block dylib loading for sandboxed instances + engine.disallow_dylib_loading(); + + engine } /// Call the print method within the VM @@ -1241,7 +1271,7 @@ impl Engine { /// vm.run(r#"(+ 1 2 3)"#).unwrap(); /// ``` pub fn new() -> Self { - let mut engine = fresh_kernel_image(); + let mut engine = fresh_kernel_image(false); engine.compiler.kernel = Some(Kernel::new()); @@ -1276,14 +1306,6 @@ impl Engine { self.virtual_machine.synchronizer.state.clone() } - pub(crate) fn new_printer() -> Self { - let mut engine = fresh_kernel_image(); - - engine.compiler.kernel = Some(Kernel::new()); - - engine - } - /// Consumes the current `Engine` and emits a new `Engine` with the prelude added /// to the environment. The prelude won't work unless the primitives are also enabled. /// @@ -1669,7 +1691,37 @@ impl Engine { self.compiler.symbol_map.free_list.increment_generation(); } - self.virtual_machine.run_executable(&executable) + // TODO: This isn't my favorite pattern. + // I'd prefer to just use a weak reference, but I don't think + // it is really worth it. + + { + let constants = self.constants(); + let mut guard = self.virtual_machine.compiler.lock().unwrap(); + let compiler = &mut self.compiler as *mut _; + + let compiler_value = SelfCompiler { + compiler: Some(compiler), + sources: self.sources.clone(), + modules: self.modules.clone(), + constants, + }; + *guard = Some(compiler_value); + } + + let res = self.virtual_machine.run_executable(&executable); + + // Overwrite the compiler reference + self.virtual_machine + .compiler + .lock() + .unwrap() + .as_mut() + .unwrap() + .compiler + .take(); + + res } pub fn run_executable(&mut self, executable: &Executable) -> Result> { @@ -1794,10 +1846,6 @@ impl Engine { /// ``` pub fn register_value(&mut self, name: &str, value: SteelVal) -> &mut Self { self.register_value_inner(name, value) - - // let idx = self.compiler.register(name); - // self.virtual_machine.insert_binding(idx, value); - // self } pub fn update_value(&mut self, name: &str, value: SteelVal) -> Option<&mut Self> { @@ -1946,82 +1994,6 @@ impl Engine { // .execute_program::(program) // } - // TODO: Come back to this - /* - // / Execute a program (as per [`run`](crate::steel_vm::engine::Engine::run)), however do not enforce any contracts. Any contracts that are added are not - // / enforced. - // / - // / # Examples - // / - // / ``` - // / # extern crate steel; - // / # use steel::steel_vm::engine::Engine; - // / use steel::rvals::SteelVal; - // / let mut vm = Engine::new(); - // / let output = vm.run_without_contracts(r#" - // / (define/contract (foo x) - // / (->/c integer? any/c) - // / "hello world") - // / - // / (foo "bad-input") - // / "#).unwrap(); - // / ``` - // pub fn run_without_contracts(&mut self, expr: &str) -> Result> { - // let constants = self.constants(); - // let program = self.compiler.compile_program(expr, None, constants)?; - // self.virtual_machine.execute_program::(program) - // } - */ - - /// Execute a program without invoking any callbacks, or enforcing any contract checking - // pub fn run_without_callbacks_or_contracts(&mut self, expr: &str) -> Result> { - // let constants = self.constants(); - // let program = self.compiler.compile_program(expr, None, constants)?; - // self.virtual_machine - // .execute_program::(program) - // } - - /// Similar to [`run`](crate::steel_vm::engine::Engine::run), however it includes path information - /// for error reporting purposes. - // pub fn run_with_path(&mut self, expr: &str, path: PathBuf) -> Result> { - // let constants = self.constants(); - // let program = self.compiler.compile_program(expr, Some(path), constants)?; - // self.virtual_machine.execute_program(program) - // } - - // pub fn compile_and_run_raw_program(&mut self, expr: &str) -> Result> { - // let constants = self.constants(); - // let program = self.compiler.compile_program(expr, None, constants)?; - // self.virtual_machine.execute_program(program) - // } - - // pub fn compile_and_run_raw_program(&mut self, expr: &str) -> Result> { - // self.compile_and_run_raw_program(expr) - // } - - // Read in the file from the given path and execute accordingly - // Loads all the functions in from the given env - // pub fn parse_and_execute_from_path>( - // &mut self, - // path: P, - // ) -> Result> { - // let mut file = std::fs::File::open(path)?; - // let mut exprs = String::new(); - // file.read_to_string(&mut exprs)?; - // self.compile_and_run_raw_program(exprs.as_str(), ) - // } - - // pub fn parse_and_execute_from_path>( - // &mut self, - // path: P, - // ) -> Result> { - // let path_buf = PathBuf::from(path.as_ref()); - // let mut file = std::fs::File::open(path)?; - // let mut exprs = String::new(); - // file.read_to_string(&mut exprs)?; - // self.run_with_path(exprs.as_str(), path_buf) - // } - // TODO this does not take into account the issues with // people registering new functions that shadow the original one fn constants(&mut self) -> ImmutableHashMap { @@ -2269,6 +2241,18 @@ pub(crate) fn raise_error_to_string(sources: &Sources, error: SteelErr) -> Optio None } +pub struct EngineBuilder { + engine: Engine, +} + +impl EngineBuilder { + pub fn raw() -> Self { + Self { + engine: Engine::new_raw(), + } + } +} + #[cfg(test)] mod engine_api_tests { use crate::custom_reference; @@ -2367,3 +2351,17 @@ mod engine_api_tests { .is_err()); } } + +#[cfg(test)] +mod engine_sandbox_tests { + use super::*; + + #[test] + fn sandbox() { + let mut engine = Engine::new_sandboxed(); + + assert!(engine + .compile_and_run_raw_program(r#"(create-directory! "foo-bar")"#) + .is_err()); + } +} diff --git a/crates/steel-core/src/steel_vm/primitives.rs b/crates/steel-core/src/steel_vm/primitives.rs index d09e0132a..68c190fd7 100644 --- a/crates/steel-core/src/steel_vm/primitives.rs +++ b/crates/steel-core/src/steel_vm/primitives.rs @@ -3,7 +3,11 @@ use super::{ cache::WeakMemoizationTable, engine::Engine, register_fn::RegisterFn, - vm::{get_test_mode, list_modules, set_test_mode, VmCore}, + vm::{ + get_test_mode, list_modules, set_test_mode, VmCore, CALL_CC_DEFINITION, + CALL_WITH_EXCEPTION_HANDLER_DEFINITION, EVAL_DEFINITION, EVAL_FILE_DEFINITION, + EXPAND_SYNTAX_OBJECTS_DEFINITION, INSPECT_DEFINITION, + }, }; use crate::{ compiler::modules::steel_home, @@ -14,13 +18,14 @@ use crate::{ }, primitives::{ bytevectors::bytevector_module, - fs_module, + fs_module, fs_module_sandbox, hashmaps::{hashmap_module, HM_CONSTRUCT, HM_GET, HM_INSERT}, hashsets::hashset_module, + http::http_module, lists::{list_module, UnRecoverableResult}, numbers::{self, realp}, port_module, - ports::EOF_OBJECTP_DEFINITION, + ports::{port_module_without_filesystem, EOF_OBJECTP_DEFINITION}, process::process_module, random::random_module, string_module, @@ -67,7 +72,17 @@ use crate::{ use fxhash::{FxHashMap, FxHashSet}; use once_cell::sync::Lazy; use std::cmp::Ordering; -use steel_parser::{interner::interned_current_memory_usage, parser::SourceId}; +use steel_parser::{ast::ExprKind, interner::interned_current_memory_usage, parser::SourceId}; + +#[cfg(not(target_arch = "wasm32"))] +use crate::primitives::polling::polling_module; + +#[cfg(target_arch = "wasm32")] +fn polling_module() -> BuiltInModule { + let mut module = BuiltInModule::new("steel/polling".to_string()); + + module +} #[cfg(feature = "dylibs")] use crate::steel_vm::ffi::ffi_module; @@ -317,13 +332,14 @@ define_modules! { STEEL_SYMBOL_MODULE => symbol_module, STEEL_IO_MODULE => io_module, STEEL_FS_MODULE => fs_module, + STEEL_FS_MODULE_SB => fs_module_sandbox, STEEL_PORT_MODULE => port_module, + STEEL_PORT_WITHOUT_FS_MODULE => port_module_without_filesystem, STEEL_META_MODULE => meta_module, STEEL_JSON_MODULE => json_module, STEEL_CONSTANTS_MODULE => constants_module, STEEL_SYNTAX_MODULE => syntax_module, STEEL_SANDBOXED_META_MODULE => sandboxed_meta_module, - STEEL_SANDBOXED_IO_MODULE => sandboxed_io_module, STEEL_PROCESS_MODULE => process_module, STEEL_RANDOM_MODULE => random_module, STEEL_RESULT_MODULE => build_result_structs, @@ -335,7 +351,10 @@ define_modules! { STEEL_MUTABLE_VECTOR_MODULE => mutable_vector_module, STEEL_PRIVATE_READER_MODULE => reader_module, STEEL_TCP_MODULE => tcp_module, + STEEL_POLLING_MODULE => polling_module, + STEEL_HTTP_MODULE => http_module, STEEL_PRELUDE_MODULE => prelude, + STEEL_SB_PRELUDE => sandboxed_prelude, } thread_local! { @@ -358,24 +377,28 @@ thread_local! { pub static SYMBOL_MODULE: BuiltInModule = symbol_module(); pub static IO_MODULE: BuiltInModule = io_module(); pub static FS_MODULE: BuiltInModule = fs_module(); + pub static FS_MODULE_SB: BuiltInModule = fs_module_sandbox(); pub static PORT_MODULE: BuiltInModule = port_module(); + pub static PORT_MODULE_WITHOUT_FILESYSTEM: BuiltInModule = port_module_without_filesystem(); pub static META_MODULE: BuiltInModule = meta_module(); pub static JSON_MODULE: BuiltInModule = json_module(); pub static CONSTANTS_MODULE: BuiltInModule = constants_module(); pub static SYNTAX_MODULE: BuiltInModule = syntax_module(); pub static SANDBOXED_META_MODULE: BuiltInModule = sandboxed_meta_module(); - pub static SANDBOXED_IO_MODULE: BuiltInModule = sandboxed_io_module(); pub static PROCESS_MODULE: BuiltInModule = process_module(); pub static RANDOM_MODULE: BuiltInModule = random_module(); pub static RESULT_MODULE: BuiltInModule = build_result_structs(); pub static TYPE_ID_MODULE: BuiltInModule = build_type_id_module(); pub static OPTION_MODULE: BuiltInModule = build_option_structs(); pub static TCP_MODULE: BuiltInModule = tcp_module(); + pub static HTTP_MODULE: BuiltInModule = http_module(); + pub static POLLING_MODULE: BuiltInModule = polling_module(); #[cfg(feature = "dylibs")] pub static FFI_MODULE: BuiltInModule = ffi_module(); pub static PRELUDE_MODULE: BuiltInModule = prelude(); + pub static SB_PRELUDE: BuiltInModule = sandboxed_prelude(); pub(crate) static PRELUDE_INTERNED_STRINGS: FxHashSet = PRELUDE_MODULE.with(|x| x.names().into_iter().map(|x| x.into()).collect()); @@ -451,38 +474,66 @@ pub fn prelude() -> BuiltInModule { } } -pub fn register_builtin_modules_without_io(engine: &mut Engine) { - engine.register_fn("##__module-get", BuiltInModule::get); - engine.register_fn("%module-get%", BuiltInModule::get); - - engine.register_fn("load-from-module!", BuiltInModule::get); - - engine.register_value("%proto-hash%", HM_CONSTRUCT); - engine.register_value("%proto-hash-insert%", HM_INSERT); - engine.register_value("%proto-hash-get%", HM_GET); - engine.register_value("error!", ControlOperations::error()); - - engine.register_value("error", ControlOperations::error()); +pub fn sandboxed_prelude() -> BuiltInModule { + #[cfg(feature = "sync")] + { + BuiltInModule::new("steel/base") + .with_module(STEEL_MAP_MODULE.clone()) + .with_module(STEEL_SET_MODULE.clone()) + .with_module(STEEL_LIST_MODULE.clone()) + .with_module(STEEL_STRING_MODULE.clone()) + .with_module(STEEL_VECTOR_MODULE.clone()) + .with_module(STEEL_STREAM_MODULE.clone()) + .with_module(STEEL_IDENTITY_MODULE.clone()) + .with_module(STEEL_NUMBER_MODULE.clone()) + .with_module(STEEL_EQUALITY_MODULE.clone()) + .with_module(STEEL_ORD_MODULE.clone()) + .with_module(STEEL_TRANSDUCER_MODULE.clone()) + .with_module(STEEL_SYMBOL_MODULE.clone()) + .with_module(STEEL_IO_MODULE.clone()) + .with_module(STEEL_FS_MODULE_SB.clone()) + .with_module(STEEL_PORT_WITHOUT_FS_MODULE.clone()) + .with_module(STEEL_META_MODULE.clone()) + .with_module(STEEL_JSON_MODULE.clone()) + .with_module(STEEL_CONSTANTS_MODULE.clone()) + .with_module(STEEL_SYNTAX_MODULE.clone()) + .with_module(STEEL_RESULT_MODULE.clone()) + .with_module(STEEL_OPTION_MODULE.clone()) + .with_module(STEEL_TYPE_ID_MODULE.clone()) + .with_module(STEEL_TIME_MODULE.clone()) + .with_module(STEEL_THREADING_MODULE.clone()) + .with_module(STEEL_BYTEVECTOR_MODULE.clone()) + } - engine - .register_module(MAP_MODULE.with(|x| x.clone())) - .register_module(SET_MODULE.with(|x| x.clone())) - .register_module(LIST_MODULE.with(|x| x.clone())) - .register_module(STRING_MODULE.with(|x| x.clone())) - .register_module(VECTOR_MODULE.with(|x| x.clone())) - .register_module(STREAM_MODULE.with(|x| x.clone())) - .register_module(IDENTITY_MODULE.with(|x| x.clone())) - .register_module(NUMBER_MODULE.with(|x| x.clone())) - .register_module(EQUALITY_MODULE.with(|x| x.clone())) - .register_module(ORD_MODULE.with(|x| x.clone())) - .register_module(TRANSDUCER_MODULE.with(|x| x.clone())) - .register_module(SYMBOL_MODULE.with(|x| x.clone())) - .register_module(SANDBOXED_IO_MODULE.with(|x| x.clone())) - .register_module(SANDBOXED_META_MODULE.with(|x| x.clone())) - .register_module(JSON_MODULE.with(|x| x.clone())) - .register_module(CONSTANTS_MODULE.with(|x| x.clone())) - .register_module(SYNTAX_MODULE.with(|x| x.clone())) - .register_module(PRELUDE_MODULE.with(|x| x.clone())); + #[cfg(not(feature = "sync"))] + { + BuiltInModule::new("steel/base") + .with_module(MAP_MODULE.with(|x| x.clone())) + .with_module(SET_MODULE.with(|x| x.clone())) + .with_module(LIST_MODULE.with(|x| x.clone())) + .with_module(STRING_MODULE.with(|x| x.clone())) + .with_module(VECTOR_MODULE.with(|x| x.clone())) + .with_module(STREAM_MODULE.with(|x| x.clone())) + .with_module(IDENTITY_MODULE.with(|x| x.clone())) + .with_module(NUMBER_MODULE.with(|x| x.clone())) + .with_module(EQUALITY_MODULE.with(|x| x.clone())) + .with_module(ORD_MODULE.with(|x| x.clone())) + .with_module(TRANSDUCER_MODULE.with(|x| x.clone())) + .with_module(SYMBOL_MODULE.with(|x| x.clone())) + .with_module(IO_MODULE.with(|x| x.clone())) + .with_module(FS_MODULE_SB.with(|x| x.clone())) + .with_module(PORT_MODULE_WITHOUT_FILESYSTEM.with(|x| x.clone())) + .with_module(META_MODULE.with(|x| x.clone())) + .with_module(JSON_MODULE.with(|x| x.clone())) + .with_module(CONSTANTS_MODULE.with(|x| x.clone())) + .with_module(SYNTAX_MODULE.with(|x| x.clone())) + .with_module(RESULT_MODULE.with(|x| x.clone())) + .with_module(OPTION_MODULE.with(|x| x.clone())) + .with_module(TYPE_ID_MODULE.with(|x| x.clone())) + .with_module(TIME_MODULE.with(|x| x.clone())) + .with_module(THREADING_MODULE.with(|x| x.clone())) + .with_module(BYTEVECTOR_MODULE.with(|x| x.clone())) + } } fn render_as_md(text: String) { @@ -493,11 +544,12 @@ fn render_as_md(text: String) { println!("{}", text); } -pub fn register_builtin_modules(engine: &mut Engine) { +pub fn register_builtin_modules(engine: &mut Engine, sandbox: bool) { engine.register_value("std::env::args", SteelVal::ListV(List::new())); engine.register_fn("##__module-get", BuiltInModule::get); engine.register_fn("%module-get%", BuiltInModule::get); + engine.register_fn("%#maybe-module-get", BuiltInModule::try_get); engine.register_fn("load-from-module!", BuiltInModule::get); @@ -548,8 +600,8 @@ pub fn register_builtin_modules(engine: &mut Engine) { .register_module(STEEL_TRANSDUCER_MODULE.clone()) .register_module(STEEL_SYMBOL_MODULE.clone()) .register_module(STEEL_IO_MODULE.clone()) - .register_module(STEEL_FS_MODULE.clone()) .register_module(STEEL_PORT_MODULE.clone()) + .register_module(STEEL_FS_MODULE.clone()) .register_module(STEEL_META_MODULE.clone()) .register_module(STEEL_JSON_MODULE.clone()) .register_module(STEEL_CONSTANTS_MODULE.clone()) @@ -562,8 +614,19 @@ pub fn register_builtin_modules(engine: &mut Engine) { .register_module(STEEL_TIME_MODULE.clone()) .register_module(STEEL_RANDOM_MODULE.clone()) .register_module(STEEL_THREADING_MODULE.clone()) - .register_module(STEEL_BYTEVECTOR_MODULE.clone()) - .register_module(STEEL_TCP_MODULE.clone()); + .register_module(STEEL_BYTEVECTOR_MODULE.clone()); + + if !sandbox { + engine + .register_module(STEEL_TCP_MODULE.clone()) + .register_module(STEEL_HTTP_MODULE.clone()) + .register_module(STEEL_POLLING_MODULE.clone()); + } else { + engine + .register_module(STEEL_FS_MODULE_SB.clone()) + .register_module(STEEL_PORT_WITHOUT_FS_MODULE.clone()) + .register_module(STEEL_SB_PRELUDE.clone()); + } #[cfg(feature = "dylibs")] engine.register_module(STEEL_FFI_MODULE.clone()); @@ -604,8 +667,19 @@ pub fn register_builtin_modules(engine: &mut Engine) { .register_module(TIME_MODULE.with(|x| x.clone())) .register_module(RANDOM_MODULE.with(|x| x.clone())) .register_module(THREADING_MODULE.with(|x| x.clone())) - .register_module(BYTEVECTOR_MODULE.with(|x| x.clone())) - .register_module(TCP_MODULE.with(|x| x.clone())); + .register_module(BYTEVECTOR_MODULE.with(|x| x.clone())); + + if !sandbox { + engine + .register_module(TCP_MODULE.with(|x| x.clone())) + .register_module(HTTP_MODULE.with(|x| x.clone())) + .register_module(POLLING_MODULE.with(|x| x.clone())); + } else { + engine + .register_module(FS_MODULE_SB.with(|x| x.clone())) + .register_module(PORT_MODULE_WITHOUT_FILESYSTEM.with(|x| x.clone())) + .register_module(SB_PRELUDE.with(|x| x.clone())); + } #[cfg(feature = "dylibs")] engine.register_module(FFI_MODULE.with(|x| x.clone())); @@ -1252,31 +1326,12 @@ fn symbol_module() -> BuiltInModule { fn io_module() -> BuiltInModule { let mut module = BuiltInModule::new("steel/io"); module - // .register_value("display", IoFunctions::display()) - // .register_value("displayln", IoFunctions::displayln()) - // .register_value("simple-display", IoFunctions::display()) .register_value("stdout-simple-displayln", IoFunctions::displayln()) - // .register_value("newline", IoFunctions::newline()) .register_value("read-to-string", IoFunctions::read_to_string()); - // #[cfg(feature = "colors")] - // module.register_value("display-color", IoFunctions::display_color()); - module } -fn sandboxed_io_module() -> BuiltInModule { - let mut module = BuiltInModule::new("steel/io"); - module - .register_value("stdout-simple-displayln", IoFunctions::displayln()) - // .register_value("newline", IoFunctions::newline()) - .register_value("read-to-string", IoFunctions::read_to_string()); - // .register_value("display", IoFunctions::sandboxed_display()) - // .register_value("display-color", IoFunctions::display_color()) - // .register_value("newline", IoFunctions::sandboxed_newline()); - // .register_value("read-to-string", IoFunctions::read_to_string()); - module -} pub const VOID_DOC: MarkdownDoc = MarkdownDoc::from_str( "The void value, returned by many forms with side effects, such as `define`.", ); @@ -1305,7 +1360,6 @@ fn sandboxed_meta_module() -> BuiltInModule { module // .register_value("assert!", MetaOperations::assert_truthy()) .register_value("active-object-count", MetaOperations::active_objects()) - .register_value("inspect-bytecode", MetaOperations::inspect_bytecode()) // .register_value("memory-address", MetaOperations::memory_address()) // .register_value("async-exec", MetaOperations::exec_async()) // .register_value("poll!", MetaOperations::poll_value()) @@ -1553,15 +1607,11 @@ impl Reader { self.buffer.is_empty() } - fn read_one(&mut self) -> Result { + fn read_one_impl(&mut self, finisher: fn(ExprKind) -> Result) -> Result { if let Some(buffer) = self.buffer.get(self.offset..) { - // println!("Reading range: {}", buffer); - let mut parser = crate::parser::parser::Parser::new_flat(buffer, SourceId::none()); if let Some(raw) = parser.next() { - // self.offset += parser.offset(); - let next = if let Ok(next) = raw { next } else { @@ -1570,7 +1620,7 @@ impl Reader { self.offset += parser.offset(); - let result = TryFromExprKindForSteelVal::try_from_expr_kind_quoted(next); + let result = finisher(next); if let Some(remaining) = self.buffer.get(self.offset..) { for _ in remaining.chars().take_while(|x| x.is_whitespace()) { @@ -1592,6 +1642,16 @@ impl Reader { Ok(crate::primitives::ports::eof()) } } + + fn read_one(&mut self) -> Result { + self.read_one_impl(TryFromExprKindForSteelVal::try_from_expr_kind_quoted) + } + + fn read_one_syntax_object(&mut self) -> Result { + self.read_one_impl( + crate::parser::tryfrom_visitor::SyntaxObjectFromExprKind::try_from_expr_kind, + ) + } } fn reader_module() -> BuiltInModule { @@ -1601,7 +1661,11 @@ fn reader_module() -> BuiltInModule { .register_fn("new-reader", Reader::create_reader) .register_fn("reader-push-string", Reader::push_string) .register_fn("reader-read-one", Reader::read_one) - .register_fn("reader-empty?", Reader::is_empty); + .register_fn("reader-empty?", Reader::is_empty) + .register_fn( + "reader-read-one-syntax-object", + Reader::read_one_syntax_object, + ); module } @@ -1715,7 +1779,6 @@ fn meta_module() -> BuiltInModule { ) .register_value("assert!", MetaOperations::assert_truthy()) .register_value("active-object-count", MetaOperations::active_objects()) - .register_value("inspect-bytecode", MetaOperations::inspect_bytecode()) .register_value("memory-address", MetaOperations::memory_address()) // .register_value("async-exec", MetaOperations::exec_async()) .register_value("poll!", MetaOperations::poll_value()) @@ -1739,12 +1802,13 @@ fn meta_module() -> BuiltInModule { .register_value("error-with-span", error_with_src_loc()) .register_value("raise-error-with-span", error_from_error_with_span()) .register_value("raise-error", raise_error_from_error()) - .register_value("call/cc", SteelVal::BuiltIn(super::vm::call_cc)) - .register_value( - "call-with-exception-handler", - SteelVal::BuiltIn(super::vm::call_with_exception_handler), - ) + .register_native_fn_definition(CALL_CC_DEFINITION) + .register_native_fn_definition(EVAL_DEFINITION) + .register_native_fn_definition(EVAL_FILE_DEFINITION) + .register_native_fn_definition(EXPAND_SYNTAX_OBJECTS_DEFINITION) + .register_native_fn_definition(CALL_WITH_EXCEPTION_HANDLER_DEFINITION) .register_value("breakpoint!", SteelVal::BuiltIn(super::vm::breakpoint)) + .register_native_fn_definition(INSPECT_DEFINITION) .register_value( "#%environment-length", SteelVal::BuiltIn(super::vm::environment_offset), @@ -1753,9 +1817,6 @@ fn meta_module() -> BuiltInModule { "call-with-current-continuation", SteelVal::BuiltIn(super::vm::call_cc), ) - // .register_value("make-tls", SteelVal::BuiltIn(super::vm::make_tls)) - // .register_value("get-tls", SteelVal::BuiltIn(super::vm::get_tls)) - // .register_value("set-tls!", SteelVal::BuiltIn(super::vm::set_tls)) .register_fn("eval!", super::meta::eval) .register_fn("value->string", super::meta::value_to_string) // TODO: @Matt -> implement the traits for modules as well diff --git a/crates/steel-core/src/steel_vm/vm.rs b/crates/steel-core/src/steel_vm/vm.rs index 09ab37b54..54bbd7f4a 100644 --- a/crates/steel-core/src/steel_vm/vm.rs +++ b/crates/steel-core/src/steel_vm/vm.rs @@ -1,11 +1,14 @@ #![allow(unused)] +use crate::compiler::code_gen::fresh_function_id; +use crate::core::instructions::pretty_print_dense_instructions; use crate::gc::shared::MutContainer; use crate::gc::shared::ShareableMut; use crate::gc::shared::Shared; use crate::gc::shared::WeakShared; use crate::gc::shared::WeakSharedMut; use crate::gc::SharedMut; +use crate::parser::parser::Sources; use crate::primitives::lists::car; use crate::primitives::lists::cdr; use crate::primitives::lists::cons; @@ -16,7 +19,10 @@ use crate::primitives::numbers::add_two; use crate::rvals::as_underlying_type; use crate::rvals::cycles::BreadthFirstSearchSteelValVisitor; use crate::rvals::number_equality; +use crate::rvals::AsRefMutSteelVal as _; use crate::rvals::BoxedAsyncFunctionSignature; +use crate::rvals::FromSteelVal as _; +use crate::rvals::SteelString; use crate::steel_vm::primitives::steel_not; use crate::steel_vm::primitives::steel_set_box_mutable; use crate::steel_vm::primitives::steel_unbox_mutable; @@ -48,6 +54,7 @@ use crate::{ values::functions::ByteCodeLambda, }; use std::cell::UnsafeCell; +use std::io::Read as _; use std::rc::Weak; use std::sync::atomic::AtomicBool; use std::sync::Arc; @@ -303,6 +310,7 @@ pub enum ThreadState { PausedAtSafepoint, } +/// The thread execution context #[derive(Clone)] pub struct SteelThread { pub(crate) global_env: Env, @@ -318,6 +326,9 @@ pub struct SteelThread { pub(crate) synchronizer: Synchronizer, // This will be static, for the thread. pub(crate) thread_local_storage: Vec, + pub(crate) sources: Sources, + + pub(crate) compiler: Arc>>, } #[derive(Clone)] @@ -444,7 +455,7 @@ impl Synchronizer { // TODO: Have to use a condvar loop { if let Some(ctx) = ctx.load() { - println!("Sweeping other threads"); + log::debug!("Sweeping other threads"); unsafe { let live_ctx = &(*ctx); @@ -467,7 +478,7 @@ impl Synchronizer { break; } else { - println!("Waiting for thread...") + log::debug!("Waiting for thread...") // TODO: Some kind of condvar or message passing // is probably a better scheme here, but the idea is to just @@ -513,7 +524,7 @@ impl Synchronizer { } impl SteelThread { - pub fn new() -> SteelThread { + pub fn new(sources: Sources) -> SteelThread { SteelThread { global_env: Env::root(), stack: Vec::with_capacity(128), @@ -533,6 +544,8 @@ impl SteelThread { interrupted: Default::default(), synchronizer: Synchronizer::new(), thread_local_storage: Vec::new(), + sources, + compiler: Arc::new(Mutex::new(None)), } } @@ -596,6 +609,10 @@ impl SteelThread { self.constant_map = constant_map.clone(); + // dbg!(&self.stack); + // dbg!(&self.stack_frames); + // dbg!(&self.current_frame); + let result = instructions .iter() .zip(spans.iter()) @@ -723,6 +740,18 @@ impl SteelThread { } } + pub fn execute_eval( + &mut self, + instructions: Shared<[DenseInstruction]>, + constant_map: ConstantMap, + spans: Shared<[Span]>, + ) -> Result { + todo!() + + // let stack = std::mem::take(&mut self.stack); + // let frames = std::mem::take(&mut self.stack_frames); + } + pub fn execute( &mut self, instructions: Shared<[DenseInstruction]>, @@ -818,6 +847,7 @@ impl SteelThread { } self.stack.clear(); + // self.current_frame = StackFrame::main(); return Err(e); } else { @@ -828,6 +858,10 @@ impl SteelThread { // Clean up self.stack.clear(); + // self.current_frame = StackFrame::main(); + + // dbg!(&self.stack_frames); + return result; } } @@ -1005,8 +1039,6 @@ impl Continuation { panic!("Failed to find an open continuation on the stack"); } else { - // println!("Setting state from closed continuation"); - match Shared::try_unwrap(this.inner).map(|x| x.into_inner()) { Ok(cont) => { ctx.set_state_from_continuation(cont.into_closed().unwrap()); @@ -1323,13 +1355,11 @@ impl<'a> VmCore<'a> { stop!(Generic => format!("Thread: {:?} - Interrupted by user", std::thread::current().id()); self.current_span()); } ThreadState::Suspended => { - println!("Suspending thread"); self.park_thread_while_paused(); } ThreadState::PausedAtSafepoint => { // TODO: // Insert the code to do the stack things here - println!("Suspending thread at safepoint"); self.thread.synchronizer.ctx.store(Some(self.thread as _)); self.park_thread_while_paused(); self.thread.synchronizer.ctx.store(None); @@ -1518,7 +1548,7 @@ impl<'a> VmCore<'a> { } // Reset state FULLY - fn call_with_instructions_and_reset_state( + pub(crate) fn call_with_instructions_and_reset_state( &mut self, closure: Shared<[DenseInstruction]>, ) -> Result { @@ -1532,6 +1562,8 @@ impl<'a> VmCore<'a> { self.depth += 1; + // println!("Before: {:?}", self.thread.stack_frames.len()); + let mut res = Ok(SteelVal::Void); 'outer: loop { @@ -1618,7 +1650,7 @@ impl<'a> VmCore<'a> { // Continuation::close_marks(&self, &frame); // } - // self.thread.stack.clear(); + // self.thread.stack.truncate(self.sp); res = result; break; @@ -1633,6 +1665,9 @@ impl<'a> VmCore<'a> { // self.spans = old_spans; self.sp = self.thread.stack_frames.last().map(|x| x.sp).unwrap_or(0); + // println!("After: {:?}", self.thread.stack_frames.len()); + // self.thread.stack.truncate(self.sp); + res } @@ -1747,6 +1782,45 @@ impl<'a> VmCore<'a> { .ok_or_else(throw!(Generic => "stack empty at pop!")) } + // pub(crate) fn eval_executable(&mut self, executable: &Executable) -> Result> { + // // let prev_length = self.thread.stack.len(); + + // // let prev_stack_frames = std::mem::take(&mut self.thread.stack_frames); + + // // let mut results = Vec::new(); + + // // for instr in executable.instructions { + // // self.call_with_instructions_and_reset_state(Arc::clone(instr)) + // // } + + // let Executable { + // instructions, + // constant_map, + // spans, + // .. + // } = executable; + + // // let res = instructions + // // .iter() + // // .zip(spans.iter()) + // // .map(|x| { + // // pretty_print_dense_instructions(&x.0); + + // // self.call_with_instructions_and_reset_state( + // // Shared::clone(x.0), + // // // constant_map.clone(), + // // // Shared::clone(x.1), + // // ) + // // }) + // // .collect::>>(); + + // // self.thread.stack.truncate(prev_length); + + // // self.thread.stack_frames = prev_stack_frames; + + // res + // } + // Call with an arbitrary number of arguments pub(crate) fn call_with_args( &mut self, @@ -1754,6 +1828,8 @@ impl<'a> VmCore<'a> { args: impl IntoIterator, ) -> Result { let prev_length = self.thread.stack.len(); + // let prev_stack_frame = self.thread.current_frame.clone(); + // let stack_frame_len = self.thread.stack_frames.len(); let instructions = closure.body_exp(); @@ -1779,6 +1855,8 @@ impl<'a> VmCore<'a> { // Clean up the stack now self.thread.stack.truncate(prev_length); + // self.thread.current_frame = prev_stack_frame; + // self.thread.stack_frames.truncate(stack_frame_len); res } @@ -1977,6 +2055,10 @@ impl<'a> VmCore<'a> { // Otherwise, we're going to be copying the instruction _every_ time we iterate which is going to slow down the loop // We'd rather just reference the instruction and call it a day + if self.ip >= self.instructions.len() { + pretty_print_dense_instructions(&self.instructions); + } + let instr = self.instructions[self.ip]; match instr { @@ -2739,23 +2821,7 @@ impl<'a> VmCore<'a> { std::mem::replace(&mut self.thread.stack[offset], SteelVal::Void) } - // #[inline(always)] - // TODO: This is definitely an issue - if the instruction stack is empty, - // We will probably end up grabbing a garbage span - fn current_span(&self) -> Span { - //// New way - // self.thread - // .stack_frames - // .last() - // .and_then(|x| x.function.spans.get(self.ip)) - // .copied() - // // .unwrap() - // .unwrap_or_default() - - // println!("Span vec: {:#?}", self.spans); - - // self.spans.get(self.ip).copied().unwrap_or_default() - + fn current_span_for_index(&self, ip: usize) -> Span { self.thread .stack_frames .last() @@ -2765,26 +2831,18 @@ impl<'a> VmCore<'a> { .function_interner .spans .get(&x) - .and_then(|x| x.get(self.ip)) + .and_then(|x| x.get(ip)) }) - .or_else(|| self.root_spans.get(self.ip)) + .or_else(|| self.root_spans.get(ip)) .copied() .unwrap_or_default() + } - // todo!() - - // self.spans - // .get( - // self.instructions - // .get(self.ip) - // .map(|x| x.span_index) - // .unwrap_or_default(), - // ) - // // .flatten() - // .copied() - // .unwrap_or_default() - - // Span::default() + // #[inline(always)] + // TODO: This is definitely an issue - if the instruction stack is empty, + // We will probably end up grabbing a garbage span + fn current_span(&self) -> Span { + self.current_span_for_index(self.ip) } fn enclosing_span(&self) -> Option { @@ -3347,7 +3405,15 @@ impl<'a> VmCore<'a> { spans[self.ip..forward_jump_index].into() } } else { - self.root_spans[self.ip..forward_jump_index].into() + // TODO: This seems to be causing an error + // self.root_spans[self.ip..forward_jump_index].into() + + // For now, lets go ahead and use this... hack to get us going + if let Some(span_range) = self.root_spans.get(self.ip..forward_jump_index) { + span_range.into_iter().cloned().collect::>().into() + } else { + Shared::from(Vec::new()) + } }; // snag the arity from the eclosure instruction @@ -3374,7 +3440,6 @@ impl<'a> VmCore<'a> { .insert(closure_id, spans); constructed_lambda.set_captures(captures); - // constructed_lambda.set_heap_allocated(heap_vars); constructed_lambda }; @@ -3549,6 +3614,7 @@ impl<'a> VmCore<'a> { #[inline(always)] fn handle_tail_call(&mut self, stack_func: SteelVal, payload_size: usize) -> Result<()> { use SteelVal::*; + match stack_func { FuncV(f) => self.call_primitive_func(f, payload_size), MutFunc(f) => self.call_primitive_mut_func(f, payload_size), @@ -3560,9 +3626,10 @@ impl<'a> VmCore<'a> { _ => { // println!("{:?}", self.stack); // println!("{:?}", self.stack_index); - println!("Bad tail call"); - crate::core::instructions::pretty_print_dense_instructions(&self.instructions); - stop!(BadSyntax => format!("TailCall - Application not a procedure or function type not supported: {stack_func}"); self.current_span()); + // println!("Bad tail call"); + // crate::core::instructions::pretty_print_dense_instructions(&self.instructions); + stop!(BadSyntax => format!("TailCall - Application not a procedure or function type + not supported: {stack_func}"); self.current_span()); } } } @@ -3934,7 +4001,7 @@ impl<'a> VmCore<'a> { } // // #[inline(always)] - fn handle_function_call_closure( + pub(crate) fn handle_function_call_closure( &mut self, closure: Gc, payload_size: usize, @@ -4180,27 +4247,164 @@ pub fn current_function_span(ctx: &mut VmCore, args: &[SteelVal]) -> Option format!("current-function-span requires no arguments, found {}", args.len())) } - // println!("Enclosing span: {:?}", ctx.enclosing_span()); - match ctx.enclosing_span() { Some(s) => Some(Span::into_steelval(s)), None => Some(Ok(SteelVal::Void)), } } +#[steel_derive::context(name = "inspect", arity = "Exact(1)")] +pub fn inspect(ctx: &mut VmCore, args: &[SteelVal]) -> Option> { + let guard = ctx.thread.sources.sources.lock().unwrap(); + + if let Some(SteelVal::Closure(c)) = args.get(0) { + let spans = ctx.thread.function_interner.spans.get(&c.id); + + let instructions = c.body_exp(); + + let first_column_width = instructions.len().to_string().len(); + let second_column_width = instructions + .iter() + .map(|x| format!("{:?}", x.op_code).len()) + .max() + .unwrap(); + let third_column_width = instructions + .iter() + .map(|x| x.payload_size.to_string().len()) + .max() + .unwrap(); + + let mut buffer = String::new(); + + for (i, instruction) in c.body_exp().iter().enumerate() { + let span = spans.and_then(|x| x.get(i)); + + let index = i.to_string(); + + buffer.push_str(index.as_str()); + for _ in 0..(first_column_width - index.len()) { + buffer.push(' '); + } + + buffer.push_str(" "); + + let op_code = format!("{:?}", instruction.op_code); + buffer.push_str(op_code.as_str()); + for _ in 0..(second_column_width - op_code.len()) { + buffer.push(' '); + } + + buffer.push_str(" : "); + + let payload_size = instruction.payload_size.to_string(); + buffer.push_str(payload_size.as_str()); + for _ in 0..(third_column_width - payload_size.len()) { + buffer.push(' '); + } + + buffer.push_str(" "); + + if let Some(source_id) = span.and_then(|x| x.source_id()) { + let source = guard.get(source_id); + if let Some(span) = span { + if let Some(source) = source { + let range = source.get(span.start..span.end); + + if let Some(range) = + range.and_then(|x| if x.len() > 30 { x.get(0..30) } else { Some(x) }) + { + buffer.push_str(";; "); + buffer.push_str(range); + } + } + } + } + + println!("{}", buffer); + buffer.clear(); + } + } + + Some(Ok(SteelVal::Void)) +} + /// Inspect the locals at the given function. Probably need to provide a way to /// loop this back into the sources, in order to resolve any span information. pub fn breakpoint(ctx: &mut VmCore, args: &[SteelVal]) -> Option> { let offset = ctx.get_offset(); - println!("----- Locals -----"); - for (slot, i) in (offset..ctx.thread.stack.len()).enumerate() { - println!("x{} = {:?}", slot, &ctx.thread.stack[i]); + // Wait for user input to continue... + // -> Evaluation context, but lets first see if we can resolve what everything is on the stack + + let guard = ctx.thread.sources.sources.lock().unwrap(); + + // Determine the globals at the current spot, use the span in order to resolve + // what they're looking for, into something more interesting. + // - Note: This can probably be done externally, as a library, in order to avoid + // having to bundle read line into steel-core + for (index, instr) in ctx.instructions.iter().enumerate() { + match instr { + DenseInstruction { + op_code: + OpCode::READLOCAL + | OpCode::MOVEREADLOCAL + | OpCode::MOVEREADLOCAL0 + | OpCode::MOVEREADLOCAL1 + | OpCode::MOVEREADLOCAL2 + | OpCode::MOVEREADLOCAL3 + | OpCode::READLOCAL0 + | OpCode::READLOCAL1 + | OpCode::READLOCAL2 + | OpCode::READLOCAL3, + payload_size, + } => { + let span = ctx.current_span_for_index(index); + + if let Some(source_id) = span.source_id() { + let source = guard.get(source_id); + + if let Some(source) = source { + let range = source.get(span.start..span.end); + + println!( + "x{} = {:?} = {}", + payload_size, + range, + ctx.thread.stack[payload_size.to_usize() + offset] + ); + } + } + } + + DenseInstruction { + op_code: OpCode::READCAPTURED, + payload_size, + } => { + let span = ctx.current_span_for_index(index); + + if let Some(source_id) = span.source_id() { + let source = guard.get(source_id); + + if let Some(source) = source { + let range = source.get(span.start..span.end); + + let value = ctx.thread.stack_frames.last().unwrap().function.captures() + [payload_size.to_usize()] + .clone(); + + println!("x(captured){} = {:?} = {}", payload_size, range, value); + } + } + } + + _ => {} + } } Some(Ok(SteelVal::Void)) } +#[steel_derive::context(name = "call-with-exception-handler", arity = "Exact(2)")] pub fn call_with_exception_handler( ctx: &mut VmCore, args: &[SteelVal], @@ -4212,9 +4416,6 @@ pub fn call_with_exception_handler( let handler = args[0].clone(); let thunk = args[1].clone(); - // let guard = ctx.stack_frames.last_mut().unwrap(); - // guard.attach_handler(handler); - match thunk { SteelVal::Closure(closure) => { if ctx.thread.stack_frames.len() == STACK_LIMIT { @@ -4254,7 +4455,6 @@ pub fn call_with_exception_handler( } } - // Some(Ok(SteelVal::Void)) None } @@ -4262,6 +4462,7 @@ pub fn oneshot_call_cc(ctx: &mut VmCore, args: &[SteelVal]) -> Option Option> { /* - Construct the continuation @@ -4338,6 +4539,193 @@ pub fn call_cc(ctx: &mut VmCore, args: &[SteelVal]) -> Option> Some(Ok(SteelVal::ContinuationFunction(continuation))) } +fn eval_impl(ctx: &mut crate::steel_vm::vm::VmCore, args: &[SteelVal]) -> Result { + let mut compiler_guard = ctx.thread.compiler.lock().unwrap(); + let mut compiler = compiler_guard.as_mut().unwrap(); + let expr = crate::parser::ast::TryFromSteelValVisitorForExprKind::root(&args[0])?; + let comp = &mut compiler.compiler; + + if let Some(mut comp) = comp { + // SAFETY: + // This is guarded by the RwLock surround `compiler`, which locks this for writing since + // we're taking an &mut reference to it using the `as_mut_ref` function. + let comp = unsafe { &mut *comp }; + + let res = comp.compile_executable_from_expressions( + vec![expr], + compiler.modules.clone(), + compiler.constants.clone(), + &mut compiler.sources, + ); + + drop(compiler_guard); + + match res { + Ok(program) => { + let symbol_map_offset = comp.symbol_map.len(); + let result = program.build("eval-context".to_string(), &mut comp.symbol_map)?; + + eval_program(result, ctx)?; + + return Ok(SteelVal::Void); + } + Err(e) => { + return Err(e); + } + } + } else { + stop!(Generic => "compiler missing!"); + } +} + +fn eval_program(program: crate::compiler::program::Executable, ctx: &mut VmCore) -> Result<()> { + let current_instruction = ctx.instructions[ctx.ip - 1]; + + let tail_call = matches!( + current_instruction.op_code, + OpCode::TAILCALL | OpCode::CALLGLOBALTAIL + ); + + let Executable { + name, + version, + time_stamp, + instructions, + constant_map, + spans, + } = program; + let mut bytecode = Vec::new(); + let mut new_spans = Vec::new(); + for (instr, span) in instructions.into_iter().zip(spans) { + new_spans.extend_from_slice(&span); + bytecode.extend_from_slice(&instr); + bytecode.last_mut().unwrap().op_code = OpCode::POPSINGLE; + } + bytecode.last_mut().unwrap().op_code = OpCode::POPPURE; + let function_id = crate::compiler::code_gen::fresh_function_id(); + let function = Gc::new(ByteCodeLambda::new( + function_id as _, + Shared::from(bytecode), + 0, + false, + Vec::new(), + )); + ctx.thread + .function_interner + .spans + .insert(function_id as _, Shared::from(new_spans)); + + if tail_call { + ctx.new_handle_tail_call_closure(function, 0).unwrap(); + } else { + ctx.ip -= 1; + ctx.handle_function_call_closure(function, 0).unwrap(); + } + Ok(()) +} + +#[steel_derive::context(name = "eval", arity = "Exact(1)")] +fn eval(ctx: &mut crate::steel_vm::vm::VmCore, args: &[SteelVal]) -> Option> { + match eval_impl(ctx, args) { + Ok(_) => None, + Err(e) => Some(Err(e)), + } +} + +#[steel_derive::context(name = "load", arity = "Exact(1)")] +fn eval_file(ctx: &mut crate::steel_vm::vm::VmCore, args: &[SteelVal]) -> Option> { + match eval_file_impl(ctx, args) { + Ok(_) => None, + Err(e) => Some(Err(e)), + } +} + +#[steel_derive::context(name = "#%expand", arity = "Exact(1)")] +fn expand_syntax_objects( + ctx: &mut crate::steel_vm::vm::VmCore, + args: &[SteelVal], +) -> Option> { + Some(expand_impl(ctx, args)) +} + +// Expand syntax objects? +fn expand_impl(ctx: &mut VmCore, args: &[SteelVal]) -> Result { + let mut compiler_guard = ctx.thread.compiler.lock().unwrap(); + let mut compiler = compiler_guard.as_mut().unwrap(); + + // Syntax Objects -> Expr, expand, put back to syntax objects. + let expr = crate::parser::ast::TryFromSteelValVisitorForExprKind::root(&args[0])?; + let comp = &mut compiler.compiler; + + if let Some(mut comp) = comp { + // SAFETY: + // This is guarded by the RwLock surround `compiler`, which locks this for writing since + // we're taking an &mut reference to it using the `as_mut_ref` function. + let comp = unsafe { &mut *comp }; + + let res = comp.lower_expressions_impl( + vec![expr], + compiler.constants.clone(), + compiler.modules.clone(), + None, + &mut compiler.sources, + )?; + + crate::parser::tryfrom_visitor::SyntaxObjectFromExprKind::try_from_expr_kind( + res.into_iter().next().unwrap(), + ) + } else { + stop!(Generic => "compiler missing!"); + } +} + +fn eval_file_impl(ctx: &mut crate::steel_vm::vm::VmCore, args: &[SteelVal]) -> Result { + let mut compiler_guard = ctx.thread.compiler.lock().unwrap(); + + let mut compiler = compiler_guard.as_mut().unwrap(); + + let path = SteelString::from_steelval(&args[0])?; + let comp = &mut compiler.compiler; + + if let Some(mut comp) = comp { + // SAFETY: + // This is guarded by the RwLock surround `compiler`, which locks this for writing since + // we're taking an &mut reference to it using the `as_mut_ref` function. + let comp = unsafe { &mut *comp }; + + let mut file = std::fs::File::open(path.as_str())?; + + let mut exprs = String::new(); + file.read_to_string(&mut exprs)?; + + let res = comp.compile_executable( + exprs, + Some(std::path::PathBuf::from(path.as_str())), + compiler.constants.clone(), + compiler.modules.clone(), + &mut compiler.sources, + ); + + match res { + Ok(program) => { + let symbol_map_offset = comp.symbol_map.len(); + let result = program.build("eval-context".to_string(), &mut comp.symbol_map)?; + + drop(compiler_guard); + + eval_program(result, ctx)?; + + return Ok(SteelVal::Void); + } + Err(e) => { + return Err(e); + } + } + } else { + stop!(Generic => "compiler missing!"); + } +} + pub(crate) fn get_test_mode(ctx: &mut VmCore, _args: &[SteelVal]) -> Option> { Some(Ok(ctx.thread.runtime_options.test.into())) } @@ -4369,12 +4757,6 @@ pub(crate) fn environment_offset(ctx: &mut VmCore, args: &[SteelVal]) -> Option< Some(Ok(ctx.thread.global_env.len().into_steelval().unwrap())) } -// TODO: This apply does not respect tail position -// Something like this: (define (loop) (apply loop '())) -// _should_ result in an infinite loop. In the current form, this is a Rust stack overflow. -// Similarly, care should be taken to check out transduce, because nested calls to that will -// result in a stack overflow with sufficient depth on the recursive calls - /// Applies the given `function` with arguments as the contents of the `list`. /// /// (apply function lst) -> any? @@ -4389,13 +4771,6 @@ pub(crate) fn environment_offset(ctx: &mut VmCore, args: &[SteelVal]) -> Option< ///``` #[steel_derive::context(name = "apply", arity = "Exact(2)")] pub(crate) fn apply(ctx: &mut VmCore, args: &[SteelVal]) -> Option> { - // arity_check!(apply, args, 2); - - // println!("Calling apply!"); - // println!("Current instruction: {:?}", ctx.instructions[ctx.ip]); - - // ctx.ip -= 1; - let current_instruction = ctx.instructions[ctx.ip - 1]; let tail_call = matches!( @@ -4403,10 +4778,6 @@ pub(crate) fn apply(ctx: &mut VmCore, args: &[SteelVal]) -> Option "apply expected 2 arguments"); } @@ -4417,9 +4788,6 @@ pub(crate) fn apply(ctx: &mut VmCore, args: &[SteelVal]) -> Option { for arg in l { @@ -4430,7 +4798,6 @@ pub(crate) fn apply(ctx: &mut VmCore, args: &[SteelVal]) -> Option Result constants }); + let sources = ctx.thread.sources.clone(); + let thread = MovableThread { constants, @@ -505,6 +507,7 @@ fn spawn_thread_result(ctx: &mut VmCore, args: &[SteelVal]) -> Result // as much as possible between threads. let mut thread = SteelThread { global_env, + sources, stack: Vec::with_capacity(64), profiler: OpCodeOccurenceProfiler::new(), function_interner, @@ -516,6 +519,8 @@ fn spawn_thread_result(ctx: &mut VmCore, args: &[SteelVal]) -> Result interrupted: Default::default(), synchronizer: Synchronizer::new(), thread_local_storage: Vec::new(), + // TODO: Fix this + compiler: Arc::new(Mutex::new(None)), }; #[cfg(feature = "profiling")] diff --git a/crates/steel-core/src/values/port.rs b/crates/steel-core/src/values/port.rs index 03d8a623c..b672f18e4 100644 --- a/crates/steel-core/src/values/port.rs +++ b/crates/steel-core/src/values/port.rs @@ -16,6 +16,7 @@ use crate::gc::shared::ShareableMut; use crate::gc::Gc; use crate::gc::GcMut; use crate::rvals::Result; +use crate::SteelVal; // use crate::rvals::{new_rc_ref_cell, RcRefSteelVal}; @@ -194,27 +195,28 @@ impl SteelPortRepr { } } - pub fn read_char(&mut self) -> Result> { + pub fn read_char(&mut self) -> Result>> { let mut buf = [0; 4]; for i in 0..4 { let result = self.read_byte()?; let b = match result { - Some(b) => b, - None => { + MaybeBlocking::Nonblocking(Some(b)) => b, + MaybeBlocking::Nonblocking(None) => { if i == 0 { - return Ok(None); + return Ok(MaybeBlocking::Nonblocking(None)); } else { stop!(ConversionError => "unable to decode character, found {:?}", &buf[0..=i]); } } + MaybeBlocking::WouldBlock => return Ok(MaybeBlocking::WouldBlock), }; buf[i] = b; match std::str::from_utf8(&buf[0..=i]) { - Ok(s) => return Ok(s.chars().next()), + Ok(s) => return Ok(MaybeBlocking::Nonblocking(s.chars().next())), Err(err) if err.error_len().is_some() => { stop!(ConversionError => "unable to decode character, found {:?}", &buf[0..=i]); } @@ -225,7 +227,41 @@ impl SteelPortRepr { stop!(ConversionError => "unable to decode character, found {:?}", buf); } - pub fn read_byte(&mut self) -> Result> { + pub fn read_bytes(&mut self, buf: &mut [u8]) -> Result> { + let result = match self { + SteelPortRepr::FileInput(_, reader) => reader.read_exact(buf), + SteelPortRepr::StdInput(stdin) => stdin.read_exact(buf), + SteelPortRepr::ChildStdOutput(output) => output.read_exact(buf), + SteelPortRepr::ChildStdError(output) => output.read_exact(buf), + SteelPortRepr::StringInput(reader) => reader.read_exact(buf), + SteelPortRepr::DynReader(reader) => reader.read_exact(buf), + SteelPortRepr::TcpStream(t) => t.read_exact(buf), + SteelPortRepr::FileOutput(_, _) + | SteelPortRepr::StdOutput(_) + | SteelPortRepr::StdError(_) + | SteelPortRepr::ChildStdInput(_) + | SteelPortRepr::StringOutput(_) + | SteelPortRepr::DynWriter(_) => stop!(ContractViolation => "expected input-port?"), + SteelPortRepr::Closed => return Ok(MaybeBlocking::Nonblocking(true)), + }; + + if let Err(err) = result { + if err.kind() == io::ErrorKind::UnexpectedEof { + return Ok(MaybeBlocking::Nonblocking(false)); + } + + if err.kind() == io::ErrorKind::WouldBlock { + // TODO: If this would block, do something + return Ok(MaybeBlocking::WouldBlock); + } + + return Err(err.into()); + } + + Ok(MaybeBlocking::Nonblocking(true)) + } + + pub fn read_byte(&mut self) -> Result>> { let mut byte = [0]; let result = match self { @@ -242,18 +278,18 @@ impl SteelPortRepr { | SteelPortRepr::ChildStdInput(_) | SteelPortRepr::StringOutput(_) | SteelPortRepr::DynWriter(_) => stop!(ContractViolation => "expected input-port?"), - SteelPortRepr::Closed => return Ok(None), + SteelPortRepr::Closed => return Ok(MaybeBlocking::Nonblocking(None)), }; if let Err(err) = result { if err.kind() == io::ErrorKind::UnexpectedEof { - return Ok(None); + return Ok(MaybeBlocking::Nonblocking(None)); } return Err(err.into()); } - Ok(Some(byte[0])) + Ok(MaybeBlocking::Nonblocking(Some(byte[0]))) } pub fn peek_byte(&mut self) -> Result> { @@ -393,6 +429,11 @@ impl SteelPortRepr { } } +pub enum MaybeBlocking { + Nonblocking(T), + WouldBlock, +} + impl SteelPort { pub fn new_textual_file_input(path: &str) -> Result { let file = OpenOptions::new().read(true).open(path)?; @@ -454,14 +495,27 @@ impl SteelPort { self.port.write().read_all_str() } - pub fn read_char(&self) -> Result> { + pub fn read_char(&self) -> Result>> { self.port.write().read_char() } - pub fn read_byte(&self) -> Result> { + pub fn read_byte(&self) -> Result>> { self.port.write().read_byte() } + pub fn read_bytes(&self, amount: usize) -> Result>> { + // TODO: This is going to allocate unnecessarily + let mut buf = vec![0; amount]; + match self.port.write().read_bytes(&mut buf)? { + MaybeBlocking::Nonblocking(_) => Ok(MaybeBlocking::Nonblocking(buf)), + MaybeBlocking::WouldBlock => Ok(MaybeBlocking::WouldBlock), + } + } + + pub fn read_bytes_into_buf(&self, buf: &mut [u8]) -> Result> { + self.port.write().read_bytes(buf) + } + pub fn peek_byte(&self) -> Result> { self.port.write().peek_byte() } @@ -538,3 +592,29 @@ impl SteelPort { self.port.write().close_output_port() } } + +#[cfg(not(feature = "sync"))] +thread_local! { + pub static WOULD_BLOCK_OBJECT: once_cell::unsync::Lazy<(crate::SteelVal, + super::structs::StructTypeDescriptor)>= once_cell::unsync::Lazy::new(|| { + super::structs::make_struct_singleton("would-block".into()) + }); +} + +#[cfg(feature = "sync")] +pub static WOULD_BLOCK_OBJECT: once_cell::sync::Lazy<( + crate::SteelVal, + super::structs::StructTypeDescriptor, +)> = once_cell::sync::Lazy::new(|| super::structs::make_struct_singleton("eof".into())); + +pub fn would_block() -> SteelVal { + #[cfg(feature = "sync")] + { + WOULD_BLOCK_OBJECT.0.clone() + } + + #[cfg(not(feature = "sync"))] + { + WOULD_BLOCK_OBJECT.with(|eof| eof.0.clone()) + } +} diff --git a/crates/steel-core/tests/test_files/input_tests.rkt b/crates/steel-core/tests/test_files/input_tests.rkt index 2073a65bd..9729388c3 100644 --- a/crates/steel-core/tests/test_files/input_tests.rkt +++ b/crates/steel-core/tests/test_files/input_tests.rkt @@ -22,3 +22,5 @@ (vector? '(1 2 3)) (list? 'a) list +(eval '(define eval-expression 100)) +(eval 'eval-expression) diff --git a/crates/steel-core/tests/test_files/output_tests.rkt b/crates/steel-core/tests/test_files/output_tests.rkt index 19ac77b22..0d30c8d8c 100644 --- a/crates/steel-core/tests/test_files/output_tests.rkt +++ b/crates/steel-core/tests/test_files/output_tests.rkt @@ -21,4 +21,6 @@ Error: Parse: Parse: Unexpected token: CloseParen(Round) #false #false #false -# \ No newline at end of file +# +# +100 diff --git a/src/lib.rs b/src/lib.rs index 0fb067643..8388db237 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,6 +56,16 @@ enum EmitAction { Dylib, } +#[cfg(feature = "build-info")] +const VERSION_MESSAGE: &str = concat!( + env!("CARGO_PKG_VERSION"), + "-", + env!("VERGEN_RUSTC_SEMVER"), + " (", + env!("VERGEN_BUILD_DATE"), + ")" +); + pub fn run(clap_args: Args) -> Result<(), Box> { let mut vm = Engine::new(); vm.register_value("std::env::args", steel::SteelVal::ListV(vec![].into())); @@ -66,6 +76,10 @@ pub fn run(clap_args: Args) -> Result<(), Box> { action: None, .. } => { + #[cfg(feature = "build-info")] + { + println!("{}", VERSION_MESSAGE); + } run_repl(vm)?; Ok(()) } diff --git a/steel-examples/coop-threads.scm b/steel-examples/coop-threads.scm index 97e7aef7c..bd5e3e5cc 100644 --- a/steel-examples/coop-threads.scm +++ b/steel-examples/coop-threads.scm @@ -6,24 +6,22 @@ ; current-continuation : -> continuation (define (current-continuation) - (call/cc - (lambda (cc) - (cc cc)))) + (call/cc (lambda (cc) (cc cc)))) ; spawn : (-> anything) -> void (define (spawn thunk) - (let ((cc (current-continuation))) + (let ([cc (current-continuation)]) (if (continuation? cc) (set! *thread-queue* (append *thread-queue* (list cc))) - (begin - (thunk) - (quit))))) + (begin + (thunk) + (quit))))) ; yield : value -> void (define (yield) - (let ((cc (current-continuation))) + (let ([cc (current-continuation)]) (if (and (continuation? cc) (pair? *thread-queue*)) - (let ((next-thread (car *thread-queue*))) + (let ([next-thread (car *thread-queue*)]) (set! *thread-queue* (append (cdr *thread-queue*) (list cc))) (next-thread 'resume)) void))) @@ -31,47 +29,47 @@ ; quit : -> ... (define (quit) (if (pair? *thread-queue*) - (let ((next-thread (car *thread-queue*))) + (let ([next-thread (car *thread-queue*)]) (set! *thread-queue* (cdr *thread-queue*)) (next-thread 'resume)) (halt))) - + ; start-threads : -> ... (define (start-threads) - (let ((cc (current-continuation))) - (displayln cc) + (let ([cc (current-continuation)]) + ; (displayln cc) (if cc (begin (displayln cc) - (set! halt (lambda () - ; (inspect-bytecode cc) - (displayln cc) - (cc #f))) + (set! halt + (lambda () + ; (inspect-bytecode cc) + (displayln cc) + (cc #f))) (displayln cc) (if (null? *thread-queue*) void (begin - (let ((next-thread (car *thread-queue*))) + (let ([next-thread (car *thread-queue*)]) (set! *thread-queue* (cdr *thread-queue*)) (next-thread 'resume))))) void))) - ;; Example cooperatively threaded program (define counter 10) (define (make-thread-thunk name) (define (loop) - (when (< counter 0) - (quit)) - (display "in thread ") - (display name) - (display "; counter = ") - (display counter) - (newline) - (set! counter (- counter 1)) - (yield) - (loop)) + (when (< counter 0) + (quit)) + (display "in thread ") + (display name) + (display "; counter = ") + (display counter) + (newline) + (set! counter (- counter 1)) + (yield) + (loop)) loop) (spawn (make-thread-thunk 'a))