From ac50bee364a3d06e9ab17bd40cb25e6c398c84de Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 2 Nov 2023 18:26:10 +0100 Subject: [PATCH 01/11] =?UTF-8?q?Split=20DefaultEnvironment::handle(?= =?UTF-8?q?=E2=80=A6)=20into=20multiple=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compiler/vm/src/environment.rs | 121 ++++++++++++++++----------------- 1 file changed, 60 insertions(+), 61 deletions(-) diff --git a/compiler/vm/src/environment.rs b/compiler/vm/src/environment.rs index ed79d3a33..0b2b89df8 100644 --- a/compiler/vm/src/environment.rs +++ b/compiler/vm/src/environment.rs @@ -1,6 +1,6 @@ use crate::{ byte_code::ByteCode, - heap::{Data, Handle, Heap, Int, List, Struct, Tag, Text}, + heap::{Data, Handle, Heap, InlineObject, Int, List, Struct, Tag, Text}, tracer::Tracer, vm::VmHandleCall, StateAfterRun, StateAfterRunForever, Vm, VmFinished, @@ -89,72 +89,71 @@ impl Environment for DefaultEnvironment { heap: &mut Heap, call: VmHandleCall, ) -> Vm { - if call.handle == self.get_random_bytes_handle { - let [length] = call.arguments.as_slice() else { - unreachable!() - }; - - let Data::Int(length) = (*length).into() else { - // TODO: Panic - let message = Text::create( - heap, - true, - "Handle `getRandomBytes` was called with a non-integer.", - ); - let result = Tag::create_result(heap, true, Err(message.into())); - return call.complete(heap, result); - }; - let Some(length) = length.try_get::() else { - // TODO: Panic - let message = Text::create( - heap, - true, - "Handle `getRandomBytes` was called with a length that doesn't fit in usize.", - ); - let result = Tag::create_result(heap, true, Err(message.into())); - return call.complete(heap, result); - }; - - let mut bytes = vec![0u8; length]; - if let Err(error) = getrandom::getrandom(&mut bytes) { - let message = Text::create(heap, true, &error.to_string()); - let result = Tag::create_result(heap, true, Err(message.into())); - return call.complete(heap, result); - } - - let bytes = bytes - .into_iter() - .map(|it| Int::create(heap, true, it).into()) - .collect_vec(); - let bytes = List::create(heap, true, bytes.as_slice()); - let result = Tag::create_result(heap, true, Ok(bytes.into())); - call.complete(heap, result) + let result = if call.handle == self.get_random_bytes_handle { + Self::get_random_bytes(heap, &call.arguments) } else if call.handle == self.stdin_handle { - let [] = call.arguments.as_slice() else { - unreachable!() - }; - let input = { - let stdin = io::stdin(); - stdin.lock().lines().next().unwrap().unwrap() - }; - let text = Text::create(heap, true, &input); - call.complete(heap, text) + Self::stdin(heap, &call.arguments) } else if call.handle == self.stdout_handle { - let [message] = call.arguments.as_slice() else { - unreachable!() - }; + Self::stdout(heap, &call.arguments) + } else { + unreachable!() + }; + call.complete(heap, result) + } +} +impl DefaultEnvironment { + fn get_random_bytes(heap: &mut Heap, arguments: &[InlineObject]) -> InlineObject { + let [length] = arguments else { unreachable!() }; + let Data::Int(length) = (*length).into() else { + // TODO: Panic + let message = Text::create( + heap, + true, + "Handle `getRandomBytes` was called with a non-integer.", + ); + return Tag::create_result(heap, true, Err(message.into())).into(); + }; + let Some(length) = length.try_get::() else { + // TODO: Panic + let message = Text::create( + heap, + true, + "Handle `getRandomBytes` was called with a length that doesn't fit in usize.", + ); + return Tag::create_result(heap, true, Err(message.into())).into(); + }; - if let Data::Text(text) = (*message).into() { - println!("{}", text.get()); - } else { - info!("Non-text value sent to stdout: {message:?}"); - } + let mut bytes = vec![0u8; length]; + if let Err(error) = getrandom::getrandom(&mut bytes) { + let message = Text::create(heap, true, &error.to_string()); + return Tag::create_result(heap, true, Err(message.into())).into(); + } + + let bytes = bytes + .into_iter() + .map(|it| Int::create(heap, true, it).into()) + .collect_vec(); + let bytes = List::create(heap, true, bytes.as_slice()); + Tag::create_result(heap, true, Ok(bytes.into())).into() + } - let nothing = Tag::create_nothing(heap); - call.complete(heap, nothing) + fn stdin(heap: &mut Heap, arguments: &[InlineObject]) -> InlineObject { + assert!(arguments.is_empty()); + let input = { + let stdin = io::stdin(); + stdin.lock().lines().next().unwrap().unwrap() + }; + Text::create(heap, true, &input).into() + } + fn stdout(heap: &Heap, arguments: &[InlineObject]) -> InlineObject { + let [message] = arguments else { unreachable!() }; + if let Data::Text(text) = (*message).into() { + println!("{}", text.get()); } else { - unreachable!() + info!("Non-text value sent to stdout: {message:?}"); } + + Tag::create_nothing(heap).into() } } From bade569b89731d452d82b33e0da69e436d225b43 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 9 Nov 2023 18:01:55 +0100 Subject: [PATCH 02/11] Add callgrind files to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 4a15daa11..c9dffa03e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ local/ *.candy.* .goldens/ +# Valgrind +callgrind.out.* + # ANTLR .antlr/ From f620b8ebf6a24eae65937db8da58e9c14dff4cdf Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 9 Nov 2023 18:02:20 +0100 Subject: [PATCH 03/11] Add HTTP server --- compiler/vm/Cargo.toml | 1 + compiler/vm/src/environment.rs | 273 ++++++++++++++++++++++++++++- compiler/vm/src/heap/mod.rs | 22 ++- compiler/vm/src/lib.rs | 1 + packages/Http/_.candy | 1 + packages/Http/_package.candy | 0 packages/Http/server/_.candy | 41 +++++ packages/Http/server/request.candy | 10 ++ packages/Http/serverExample.candy | 18 ++ 9 files changed, 363 insertions(+), 4 deletions(-) create mode 100644 packages/Http/_.candy create mode 100644 packages/Http/_package.candy create mode 100644 packages/Http/server/_.candy create mode 100644 packages/Http/server/request.candy create mode 100644 packages/Http/serverExample.candy diff --git a/compiler/vm/Cargo.toml b/compiler/vm/Cargo.toml index 7aac1742b..26b47b417 100644 --- a/compiler/vm/Cargo.toml +++ b/compiler/vm/Cargo.toml @@ -25,6 +25,7 @@ rand = "0.8.5" rustc-hash = "1.1.0" salsa = "0.16.1" strum = { version = "0.25.0", features = ["derive"] } +tiny_http = "0.12.0" tracing = { version = "0.1", features = ["release_max_level_debug"] } unicode-segmentation = "1.9.0" walkdir = "2.3.3" diff --git a/compiler/vm/src/environment.rs b/compiler/vm/src/environment.rs index 0b2b89df8..a96961802 100644 --- a/compiler/vm/src/environment.rs +++ b/compiler/vm/src/environment.rs @@ -5,11 +5,16 @@ use crate::{ vm::VmHandleCall, StateAfterRun, StateAfterRunForever, Vm, VmFinished, }; +use candy_frontend::utils::HashMapExtension; use itertools::Itertools; +use rustc_hash::FxHashMap; use std::{ - borrow::Borrow, + borrow::{Borrow, Cow}, io::{self, BufRead}, + net::SocketAddr, + str::FromStr, }; +use tiny_http::{Request, Response, Server}; use tracing::info; pub trait Environment { @@ -47,11 +52,32 @@ impl, T: Tracer> Vm { } pub struct DefaultEnvironment { - // Sorted alphabetically get_random_bytes_handle: Handle, + + http_server_handle: Handle, + /// `None` means the server got closed. + http_server_states: Vec>, + stdin_handle: Handle, stdout_handle: Handle, + + dynamic_handles: FxHashMap, +} +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[allow(clippy::enum_variant_names)] +enum DynamicHandle { + HttpServerGetNextRequest(HttpServerIndex), + HttpServerSendResponse(HttpServerIndex, HttpRequestId), + HttpServerClose(HttpServerIndex), } +struct HttpServerState { + server: Server, + next_request_id: HttpRequestId, + open_requests: FxHashMap, +} +type HttpServerIndex = usize; +type HttpRequestId = usize; + impl DefaultEnvironment { pub fn new(heap: &mut Heap, args: &[String]) -> (Struct, Self) { let arguments = args @@ -60,6 +86,7 @@ impl DefaultEnvironment { .collect_vec(); let arguments = List::create(heap, true, arguments.as_slice()); let get_random_bytes_handle = Handle::new(heap, 1); + let http_server_handle = Handle::new(heap, 0); let stdin_handle = Handle::new(heap, 0); let stdout_handle = Handle::new(heap, 1); let environment_object = Struct::create_with_symbol_keys( @@ -71,14 +98,18 @@ impl DefaultEnvironment { heap.default_symbols().get_random_bytes, **get_random_bytes_handle, ), + (heap.default_symbols().http_server, **http_server_handle), (heap.default_symbols().stdin, **stdin_handle), (heap.default_symbols().stdout, **stdout_handle), ], ); let environment = Self { get_random_bytes_handle, + http_server_handle, + http_server_states: vec![], stdin_handle, stdout_handle, + dynamic_handles: FxHashMap::default(), }; (environment_object, environment) } @@ -91,12 +122,34 @@ impl Environment for DefaultEnvironment { ) -> Vm { let result = if call.handle == self.get_random_bytes_handle { Self::get_random_bytes(heap, &call.arguments) + } else if call.handle == self.http_server_handle { + self.http_server(heap, &call.arguments) } else if call.handle == self.stdin_handle { Self::stdin(heap, &call.arguments) } else if call.handle == self.stdout_handle { Self::stdout(heap, &call.arguments) } else { - unreachable!() + let dynamic_handle = self.dynamic_handles.get(&call.handle).unwrap_or_else(|| { + panic!( + "A handle was called that doesn't exist: {handle:?}", + handle = call.handle + ) + }); + match dynamic_handle { + DynamicHandle::HttpServerGetNextRequest(server_index) => { + self.http_server_get_next_request(heap, *server_index, &call.arguments) + } + DynamicHandle::HttpServerSendResponse(server_index, request_index) => self + .http_server_send_response( + heap, + *server_index, + *request_index, + &call.arguments, + ), + DynamicHandle::HttpServerClose(server_index) => { + self.http_server_close(heap, *server_index, &call.arguments) + } + } }; call.complete(heap, result) } @@ -137,6 +190,199 @@ impl DefaultEnvironment { Tag::create_result(heap, true, Ok(bytes.into())).into() } + fn http_server(&mut self, heap: &mut Heap, arguments: &[InlineObject]) -> InlineObject { + let [list_of_socket_texts] = arguments else { + unreachable!() + }; + + let Data::List(list_of_socket_texts) = (*list_of_socket_texts).into() else { + // TODO: Panic + let message = Text::create( + heap, + true, + "Handle `httpServer` was called with a non-list.", + ); + return Tag::create_result(heap, true, Err(message.into())).into(); + }; + let list_of_socket_addresses: Vec<_> = match list_of_socket_texts + .items() + .iter() + .map(|it| { + let Data::Text(text) = (*it).into() else { + return Err(Cow::Borrowed( + "Handle `httpServer` was called with a list containing non-texts.", + )); + }; + match SocketAddr::from_str(text.get()) { + Ok(address) => Ok(address), + Err(error) => Err(Cow::Owned(format!( + "Handle `httpServer` was called with an invalid socket address: {error}" + ))), + } + }) + .collect() + { + Ok(list_of_socket_addresses) => list_of_socket_addresses, + Err(error_message) => { + // TODO: Panic + let message = Text::create(heap, true, error_message.borrow()); + return Tag::create_result(heap, true, Err(message.into())).into(); + } + }; + + let server = match Server::http(list_of_socket_addresses.as_slice()) { + Ok(server) => server, + Err(error) => { + let message = Text::create(heap, true, &error.to_string()); + return Tag::create_result(heap, true, Err(message.into())).into(); + } + }; + + let server_index = self.http_server_states.len(); + self.http_server_states + .push(Some(HttpServerState::new(server))); + + let get_next_request_handle = self.create_dynamic_handle( + heap, + DynamicHandle::HttpServerGetNextRequest(server_index), + 0, + ); + let close_handle = + self.create_dynamic_handle(heap, DynamicHandle::HttpServerClose(server_index), 0); + Struct::create_with_symbol_keys( + heap, + true, + [ + ( + heap.default_symbols().get_next_request, + **get_next_request_handle, + ), + (heap.default_symbols().close, **close_handle), + ], + ) + .into() + } + fn http_server_get_next_request( + &mut self, + heap: &mut Heap, + server_index: HttpServerIndex, + arguments: &[InlineObject], + ) -> InlineObject { + assert!(arguments.is_empty()); + + let server_state = &mut self.http_server_states[server_index]; + let Some(server_state) = server_state else { + // TODO: Panic + return Self::http_server_error_closed(heap); + }; + + let mut request = match server_state.server.recv() { + Ok(request) => request, + Err(error) => { + let message = Text::create(heap, true, &error.to_string()); + return Tag::create_result(heap, true, Err(message.into())).into(); + } + }; + + // TODO: Support binary request bodies and other encodings + let mut body = String::new(); + if let Err(error) = request.as_reader().read_to_string(&mut body) { + let message = Text::create(heap, true, &error.to_string()); + return Tag::create_result(heap, true, Err(message.into())).into(); + } + // TODO: Expose all request properties, not just the body + let request_text = Text::create(heap, true, &body); + + let request_id = server_state.next_request_id; + server_state.next_request_id += 1; + server_state.open_requests.force_insert(request_id, request); + + let send_response_handle = self.create_dynamic_handle( + heap, + DynamicHandle::HttpServerSendResponse(server_index, request_id), + 1, + ); + + let result = Struct::create_with_symbol_keys( + heap, + true, + [ + (heap.default_symbols().request, request_text.into()), + (heap.default_symbols().send_response, **send_response_handle), + ], + ); + Tag::create_result(heap, true, Ok(result.into())).into() + } + fn http_server_send_response( + &mut self, + heap: &mut Heap, + server_index: HttpServerIndex, + request_id: HttpRequestId, + arguments: &[InlineObject], + ) -> InlineObject { + let [body] = arguments else { + unreachable!(); + }; + + let Data::Text(body) = (*body).into() else { + // TODO: Panic + let message = Text::create( + heap, + true, + "Handle `httpRequest.sendResponse` was called with a non-text.", + ); + return Tag::create_result(heap, true, Err(message.into())).into(); + }; + + let server_state = &mut self.http_server_states[server_index]; + let Some(server_state) = server_state else { + // TODO: Panic + return Self::http_server_error_closed(heap); + }; + + let request = server_state.open_requests.remove(&request_id); + let Some(request) = request else { + // TODO: Panic + let message = Text::create( + heap, + true, + "Handle `httpRequest.sendResponse` was called for a request that was already responded to.", + ); + return Tag::create_result(heap, true, Err(message.into())).into(); + }; + + // TODO: Support all response properties, not just the body. + let response = Response::from_string(body.get()); + let result = match request.respond(response) { + Ok(()) => Ok(Tag::create_nothing(heap).into()), + Err(error) => Err(Text::create(heap, true, &error.to_string()).into()), + }; + Tag::create_result(heap, true, result).into() + } + fn http_server_close( + &mut self, + heap: &mut Heap, + server_index: HttpServerIndex, + arguments: &[InlineObject], + ) -> InlineObject { + assert!(arguments.is_empty()); + + let server_state = &mut self.http_server_states[server_index]; + if server_state.is_none() { + // TODO: Panic + return Self::http_server_error_closed(heap); + } + + // The server is closed when dropped. + *server_state = None; + + Tag::create_nothing(heap).into() + } + fn http_server_error_closed(heap: &mut Heap) -> InlineObject { + let message = Text::create(heap, true, "The HTTP server was closed already."); + Tag::create_result(heap, true, Err(message.into())).into() + } + fn stdin(heap: &mut Heap, arguments: &[InlineObject]) -> InlineObject { assert!(arguments.is_empty()); let input = { @@ -155,6 +401,27 @@ impl DefaultEnvironment { Tag::create_nothing(heap).into() } + + fn create_dynamic_handle( + &mut self, + heap: &mut Heap, + dynamic_handle: DynamicHandle, + argument_count: usize, + ) -> Handle { + let handle = Handle::new(heap, argument_count); + self.dynamic_handles.force_insert(handle, dynamic_handle); + handle + } +} + +impl HttpServerState { + fn new(server: Server) -> Self { + Self { + server, + next_request_id: 0, + open_requests: FxHashMap::default(), + } + } } #[must_use] diff --git a/compiler/vm/src/heap/mod.rs b/compiler/vm/src/heap/mod.rs index c2f413898..bfa8217b8 100644 --- a/compiler/vm/src/heap/mod.rs +++ b/compiler/vm/src/heap/mod.rs @@ -229,12 +229,15 @@ pub struct DefaultSymbols { // Sorted alphabetically pub arguments: Text, pub builtin: Text, + pub close: Text, pub equal: Text, pub error: Text, pub false_: Text, pub function: Text, pub get_random_bytes: Text, + pub get_next_request: Text, pub greater: Text, + pub http_server: Text, pub int: Text, pub less: Text, pub list: Text, @@ -242,6 +245,8 @@ pub struct DefaultSymbols { pub not_utf8: Text, pub nothing: Text, pub ok: Text, + pub request: Text, + pub send_response: Text, pub stdin: Text, pub stdout: Text, pub struct_: Text, @@ -254,12 +259,15 @@ impl DefaultSymbols { Self { arguments: Text::create(heap, false, "Arguments"), builtin: Text::create(heap, false, "Builtin"), + close: Text::create(heap, false, "Close"), equal: Text::create(heap, false, "Equal"), error: Text::create(heap, false, "Error"), false_: Text::create(heap, false, "False"), function: Text::create(heap, false, "Function"), + get_next_request: Text::create(heap, false, "GetNextRequest"), get_random_bytes: Text::create(heap, false, "GetRandomBytes"), greater: Text::create(heap, false, "Greater"), + http_server: Text::create(heap, false, "HttpServer"), int: Text::create(heap, false, "Int"), less: Text::create(heap, false, "Less"), list: Text::create(heap, false, "List"), @@ -267,6 +275,8 @@ impl DefaultSymbols { not_utf8: Text::create(heap, false, "NotUtf8"), nothing: Text::create(heap, false, "Nothing"), ok: Text::create(heap, false, "Ok"), + request: Text::create(heap, false, "Request"), + send_response: Text::create(heap, false, "SendResponse"), stdin: Text::create(heap, false, "Stdin"), stdout: Text::create(heap, false, "Stdout"), struct_: Text::create(heap, false, "Struct"), @@ -292,12 +302,15 @@ impl DefaultSymbols { Self { arguments: clone_to_heap(heap, address_map, self.arguments), builtin: clone_to_heap(heap, address_map, self.builtin), + close: clone_to_heap(heap, address_map, self.close), equal: clone_to_heap(heap, address_map, self.equal), error: clone_to_heap(heap, address_map, self.error), false_: clone_to_heap(heap, address_map, self.false_), function: clone_to_heap(heap, address_map, self.function), + get_next_request: clone_to_heap(heap, address_map, self.get_next_request), get_random_bytes: clone_to_heap(heap, address_map, self.get_random_bytes), greater: clone_to_heap(heap, address_map, self.greater), + http_server: clone_to_heap(heap, address_map, self.http_server), int: clone_to_heap(heap, address_map, self.int), less: clone_to_heap(heap, address_map, self.less), list: clone_to_heap(heap, address_map, self.list), @@ -305,6 +318,8 @@ impl DefaultSymbols { not_utf8: clone_to_heap(heap, address_map, self.not_utf8), nothing: clone_to_heap(heap, address_map, self.nothing), ok: clone_to_heap(heap, address_map, self.ok), + request: clone_to_heap(heap, address_map, self.request), + send_response: clone_to_heap(heap, address_map, self.send_response), stdin: clone_to_heap(heap, address_map, self.stdin), stdout: clone_to_heap(heap, address_map, self.stdout), struct_: clone_to_heap(heap, address_map, self.struct_), @@ -323,16 +338,19 @@ impl DefaultSymbols { .map(|it| symbols[it]) } #[must_use] - pub const fn all_symbols(&self) -> [Text; 21] { + pub const fn all_symbols(&self) -> [Text; 26] { [ self.arguments, self.builtin, + self.close, self.equal, self.error, self.false_, self.function, + self.get_next_request, self.get_random_bytes, self.greater, + self.http_server, self.int, self.less, self.list, @@ -340,6 +358,8 @@ impl DefaultSymbols { self.not_utf8, self.nothing, self.ok, + self.request, + self.send_response, self.stdin, self.stdout, self.struct_, diff --git a/compiler/vm/src/lib.rs b/compiler/vm/src/lib.rs index e97eceb59..4fb5f520a 100644 --- a/compiler/vm/src/lib.rs +++ b/compiler/vm/src/lib.rs @@ -1,4 +1,5 @@ #![feature( + addr_parse_ascii, allocator_api, anonymous_lifetime_in_impl_trait, core_intrinsics, diff --git a/packages/Http/_.candy b/packages/Http/_.candy new file mode 100644 index 000000000..d021cfcfe --- /dev/null +++ b/packages/Http/_.candy @@ -0,0 +1 @@ +server := use ".server" diff --git a/packages/Http/_package.candy b/packages/Http/_package.candy new file mode 100644 index 000000000..e69de29bb diff --git a/packages/Http/server/_.candy b/packages/Http/server/_.candy new file mode 100644 index 000000000..2fc126291 --- /dev/null +++ b/packages/Http/server/_.candy @@ -0,0 +1,41 @@ +[bool, function, list, iterable] = use "Core" +request := use ".request" + +is server := server % + Server [getNextRequest, close] -> + function.is0 getNextRequest | bool.lazyAnd { function.is0 close } + _ -> False + +start environmentHttpServer listOfSockets := + # Start an HTTP server that's listening on all of the given sockets. + # + # `environmentHttpServer` should be `environment.httpServer`. + needs (function.is1 environmentHttpServer) + needs (list.is listOfSockets) + needs (listOfSockets | iterable.fromList | iterable.all text.is) + ## TODO: properly validate sockets + server = Server (environmentHttpServer listOfSockets) + check (is server) + server + +close server := + needs (is server) + Server [close] = server + close | function.run + +nextRequest server := + # Waits for the next request and returns `[request, sendResponse]`. + needs (is server) + Server [getNextRequest] = server + [Request: req, sendResponse] (getNextRequest | function.run) + check (text.is req) + check (function.is1 sendResponse) + + req = Request req + check (request.is req) + + sendResponse body = + needs (text.is body) + sendResponse body + + [Request: req, sendResponse] diff --git a/packages/Http/server/request.candy b/packages/Http/server/request.candy new file mode 100644 index 000000000..e83d2e686 --- /dev/null +++ b/packages/Http/server/request.candy @@ -0,0 +1,10 @@ +[text] = use "Core" + +is request = request + Request body -> text.is body + _ -> False + +body request := + needs (is request) + Request [body] = request + body diff --git a/packages/Http/serverExample.candy b/packages/Http/serverExample.candy new file mode 100644 index 000000000..729ffc005 --- /dev/null +++ b/packages/Http/serverExample.candy @@ -0,0 +1,18 @@ +[print] = use "Builtins" +[server] = use ".." + +main := { environment -> + sockets = ["127.0.0.1:8080"] + s = server.listen environment.httpServer sockets + print "Started an HTTP server at {sockets}" + + [Request: req, sendResponse] = s | server.nextRequest + print "Received a request: {req}" + + responseBody "Hello from Candy!" + sendResponse responseBody + print '"Sent a response: "{responseBody}""' + + s | server.close + print "Closed the server" +} From 79ea2bfbd18e015e2e9fff4ab38773774be95142 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 9 Nov 2023 18:05:18 +0100 Subject: [PATCH 04/11] Fix environment.httpServer parameter count --- compiler/vm/src/environment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/vm/src/environment.rs b/compiler/vm/src/environment.rs index a96961802..80ba7bf11 100644 --- a/compiler/vm/src/environment.rs +++ b/compiler/vm/src/environment.rs @@ -86,7 +86,7 @@ impl DefaultEnvironment { .collect_vec(); let arguments = List::create(heap, true, arguments.as_slice()); let get_random_bytes_handle = Handle::new(heap, 1); - let http_server_handle = Handle::new(heap, 0); + let http_server_handle = Handle::new(heap, 1); let stdin_handle = Handle::new(heap, 0); let stdout_handle = Handle::new(heap, 1); let environment_object = Struct::create_with_symbol_keys( From 2e916ffb8fd4e9180b35fc98b2f86679f7799b5b Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Fri, 10 Nov 2023 00:54:36 +0100 Subject: [PATCH 05/11] Fix constant text being ref-counted --- compiler/vm/src/lir_to_byte_code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/vm/src/lir_to_byte_code.rs b/compiler/vm/src/lir_to_byte_code.rs index d5c9cc9a4..2592b9fb4 100644 --- a/compiler/vm/src/lir_to_byte_code.rs +++ b/compiler/vm/src/lir_to_byte_code.rs @@ -170,7 +170,7 @@ impl<'c> LoweringContext<'c> { .default_symbols() .get(symbol) .unwrap_or_else(|| { - Text::create(&mut self.byte_code.constant_heap, true, symbol) + Text::create(&mut self.byte_code.constant_heap, false, symbol) }); self.emit_reference_to(*value); From 4d08a8a957e9d98c5062dd3c4642056695ff62b4 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Fri, 10 Nov 2023 01:02:14 +0100 Subject: [PATCH 06/11] Fix HTTP server and example --- packages/Http/server/_.candy | 5 +++-- packages/Http/server/request.candy | 2 +- packages/Http/serverExample.candy | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/Http/server/_.candy b/packages/Http/server/_.candy index 2fc126291..68d98bbe7 100644 --- a/packages/Http/server/_.candy +++ b/packages/Http/server/_.candy @@ -1,4 +1,4 @@ -[bool, function, list, iterable] = use "Core" +[bool, check, function, list, iterable, result, text] = use "Core" request := use ".request" is server := server % @@ -27,7 +27,8 @@ nextRequest server := # Waits for the next request and returns `[request, sendResponse]`. needs (is server) Server [getNextRequest] = server - [Request: req, sendResponse] (getNextRequest | function.run) + ## TODO: Handle errors + [Request: req, sendResponse] = getNextRequest | function.run | result.unwrap check (text.is req) check (function.is1 sendResponse) diff --git a/packages/Http/server/request.candy b/packages/Http/server/request.candy index e83d2e686..a28f7d65f 100644 --- a/packages/Http/server/request.candy +++ b/packages/Http/server/request.candy @@ -1,6 +1,6 @@ [text] = use "Core" -is request = request +is request := request % Request body -> text.is body _ -> False diff --git a/packages/Http/serverExample.candy b/packages/Http/serverExample.candy index 729ffc005..65cc002f1 100644 --- a/packages/Http/serverExample.candy +++ b/packages/Http/serverExample.candy @@ -2,14 +2,14 @@ [server] = use ".." main := { environment -> - sockets = ["127.0.0.1:8080"] - s = server.listen environment.httpServer sockets + sockets = ("127.0.0.1:8080",) + s = server.start environment.httpServer sockets print "Started an HTTP server at {sockets}" [Request: req, sendResponse] = s | server.nextRequest print "Received a request: {req}" - responseBody "Hello from Candy!" + responseBody = "Hello from Candy!" sendResponse responseBody print '"Sent a response: "{responseBody}""' From 2fe0a1c270a665073eb0345d08fd8d154142a138 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Fri, 10 Nov 2023 01:02:23 +0100 Subject: [PATCH 07/11] Update Cargo.lock --- Cargo.lock | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 1ef1cd554..7f4cfbdb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,12 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0224938f92e7aef515fac2ff2d18bd1115c1394ddf4a092e0c87e8be9499ee5" +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "async-trait" version = "0.1.64" @@ -256,6 +262,7 @@ dependencies = [ "rustc-hash", "salsa", "strum", + "tiny_http", "tracing", "tracing-subscriber", "unicode-segmentation", @@ -294,6 +301,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chunked_transfer" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" + [[package]] name = "ciborium" version = "0.2.1" @@ -790,6 +803,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "ident_case" version = "1.0.1" @@ -1701,6 +1720,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiny_http" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +dependencies = [ + "ascii", + "chunked_transfer", + "httpdate", + "log", +] + [[package]] name = "tinytemplate" version = "1.2.1" From f360f4393e9670b2f4d197449679ac6f0a72a61a Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Fri, 10 Nov 2023 01:03:02 +0100 Subject: [PATCH 08/11] Remove panic when module can't be resolved --- compiler/frontend/src/module/module.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/compiler/frontend/src/module/module.rs b/compiler/frontend/src/module/module.rs index e3564ae1b..13565fad0 100644 --- a/compiler/frontend/src/module/module.rs +++ b/compiler/frontend/src/module/module.rs @@ -114,12 +114,7 @@ impl Module { } #[must_use] pub fn try_to_path(&self, packages_path: &PackagesPath) -> Option { - let paths = self.to_possible_paths(packages_path).unwrap_or_else(|| { - panic!( - "Tried to get content of anonymous module {self} that is not cached by the language server.", - ) - }); - for path in paths { + for path in self.to_possible_paths(packages_path)? { match path.try_exists() { Ok(true) => return Some(path), Ok(false) => {} From 51e79c67f26a594d0ccf94e640d282039ae7e75c Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Fri, 10 Nov 2023 01:04:40 +0100 Subject: [PATCH 09/11] Add HTTP package label config --- .github/labels.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/labels.yaml b/.github/labels.yaml index 394b38fa9..deb49ebc1 100644 --- a/.github/labels.yaml +++ b/.github/labels.yaml @@ -22,6 +22,8 @@ - packages/Core/**/* 'P: Examples': - packages/Examples/**/* +'P: Http': + - packages/Http/**/* 'P: ProgrammingLanguageBenchmarks': - packages/ProgrammingLanguageBenchmarks/**/* 'P: Random': From 5b11a0e3af0f2ee0700588b8c25248adc4eb053e Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Fri, 10 Nov 2023 11:48:19 +0100 Subject: [PATCH 10/11] Fix text interpolation --- packages/Http/serverExample.candy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Http/serverExample.candy b/packages/Http/serverExample.candy index 65cc002f1..982dd0988 100644 --- a/packages/Http/serverExample.candy +++ b/packages/Http/serverExample.candy @@ -11,7 +11,7 @@ main := { environment -> responseBody = "Hello from Candy!" sendResponse responseBody - print '"Sent a response: "{responseBody}""' + print '"Sent a response: "{{responseBody}}""' s | server.close print "Closed the server" From d2e3ba6877584c98514f51f94433c08017d518b1 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Sun, 12 Nov 2023 15:53:12 +0100 Subject: [PATCH 11/11] Use pipe --- packages/Http/serverExample.candy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Http/serverExample.candy b/packages/Http/serverExample.candy index 982dd0988..a91b92116 100644 --- a/packages/Http/serverExample.candy +++ b/packages/Http/serverExample.candy @@ -3,7 +3,7 @@ main := { environment -> sockets = ("127.0.0.1:8080",) - s = server.start environment.httpServer sockets + s = environment.httpServer | server.start sockets print "Started an HTTP server at {sockets}" [Request: req, sendResponse] = s | server.nextRequest