diff --git a/Cargo.lock b/Cargo.lock index e3476b71..c729ee66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ahash" @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "bitflags" @@ -172,7 +172,7 @@ dependencies = [ [[package]] name = "gc-arena" version = "0.5.3" -source = "git+https://github.com/kyren/gc-arena?rev=5a7534b883b703f23cfb8c3cfdf033460aa77ea9#5a7534b883b703f23cfb8c3cfdf033460aa77ea9" +source = "git+https://github.com/kyren/gc-arena?rev=4d526347a9b625c29e16f7165fd998615e51c52a#4d526347a9b625c29e16f7165fd998615e51c52a" dependencies = [ "allocator-api2", "gc-arena-derive", @@ -183,7 +183,7 @@ dependencies = [ [[package]] name = "gc-arena-derive" version = "0.5.3" -source = "git+https://github.com/kyren/gc-arena?rev=5a7534b883b703f23cfb8c3cfdf033460aa77ea9#5a7534b883b703f23cfb8c3cfdf033460aa77ea9" +source = "git+https://github.com/kyren/gc-arena?rev=4d526347a9b625c29e16f7165fd998615e51c52a#4d526347a9b625c29e16f7165fd998615e51c52a" dependencies = [ "proc-macro2", "quote", @@ -311,9 +311,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -442,9 +442,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.68" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -464,18 +464,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 06712a6e..3c222833 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,16 +16,16 @@ license = "MIT" repository = "https://github.com/kyren/piccolo" [workspace.dependencies] -ahash = "0.8" -allocator-api2 = "0.2" -anyhow = "1.0" -gc-arena = { git = "https://github.com/kyren/gc-arena", rev = "5a7534b883b703f23cfb8c3cfdf033460aa77ea9", features = ["allocator-api2", "hashbrown"] } +ahash = { version = "0.8", default-features = false } +allocator-api2 = { version = "0.2", default-features = false, features = ["alloc"] } +anyhow = { version = "1.0.87", default-features = false } +gc-arena = { git = "https://github.com/kyren/gc-arena", rev = "4d526347a9b625c29e16f7165fd998615e51c52a", default-features = false, features = ["allocator-api2", "hashbrown"] } hashbrown = { version = "0.14", features = ["raw"] } -rand = { version = "0.8", features = ["small_rng"] } -serde = "1.0" -thiserror = "1.0" +rand = { version = "0.8", default-features = false, features = ["small_rng"] } +serde = { version = "1.0", default-features = false, features = ["alloc"] } +thiserror = { version = "2.0", default-features = false } -piccolo = { path = "./", version = "0.3.3" } +piccolo = { path = "./", version = "0.3.3", default-features = false } [package] name = "piccolo" @@ -43,9 +43,15 @@ allocator-api2.workspace = true anyhow.workspace = true gc-arena.workspace = true hashbrown.workspace = true -rand.workspace = true thiserror.workspace = true +rand = { workspace = true, optional = true } + [dev-dependencies] clap = { version = "4.5", features = ["cargo"] } rustyline = "14.0" + +[features] +default = ["std", "rng"] +rng = ["dep:rand", "ahash/runtime-rng"] +std = ["rand?/std"] diff --git a/examples/compiler.rs b/examples/compiler.rs index 213092e4..e4ef2755 100644 --- a/examples/compiler.rs +++ b/examples/compiler.rs @@ -1,4 +1,4 @@ -use std::{error::Error as StdError, fs::File}; +use std::{error::Error as StdError, fs::File, io::Read}; use clap::{crate_description, crate_name, crate_version, Arg, Command}; @@ -75,15 +75,17 @@ fn main() -> Result<(), Box> { ) .get_matches(); - let file = io::buffered_read(File::open(matches.get_one::("file").unwrap())?)?; + let mut file = io::buffered_read(File::open(matches.get_one::("file").unwrap())?)?; + let mut source = Vec::new(); + file.read_to_end(&mut source)?; let mut interner = BasicInterner::default(); if matches.contains_id("parse") { - let chunk = compiler::parse_chunk(file, &mut interner)?; + let chunk = compiler::parse_chunk(&source, &mut interner)?; println!("{:#?}", chunk); } else { - let chunk = compiler::parse_chunk(file, &mut interner)?; + let chunk = compiler::parse_chunk(&source, &mut interner)?; let prototype = compiler::compile_chunk(&chunk, &mut interner)?; print_function(&prototype, 0); } diff --git a/examples/execute.rs b/examples/execute.rs index 71d85181..0b6a1d9f 100644 --- a/examples/execute.rs +++ b/examples/execute.rs @@ -1,10 +1,13 @@ use std::fs::File; +use std::io::Read; use piccolo::{io::buffered_read, Closure, Executor, Lua}; fn main() -> Result<(), Box> { // Load the Lua file - let file = buffered_read(File::open("./examples/execute.lua")?)?; + let mut file = buffered_read(File::open("./examples/execute.lua")?)?; + let mut source = Vec::new(); + file.read_to_end(&mut source)?; // Instantiate the Lua instance let mut lua = Lua::full(); @@ -14,7 +17,7 @@ fn main() -> Result<(), Box> { // Get the global env let env = ctx.globals(); // Run the lua script in the global context - let closure = Closure::load_with_env(ctx, None, file, env)?; + let closure = Closure::load_with_env(ctx, None, &*source, env)?; // Create an executor that will run the lua script let ex = Executor::start(ctx, closure.into(), ()); diff --git a/examples/interpreter.rs b/examples/interpreter.rs index 4fadedfc..725fa46f 100644 --- a/examples/interpreter.rs +++ b/examples/interpreter.rs @@ -1,5 +1,5 @@ -use std::error::Error as StdError; use std::fs::File; +use std::{error::Error as StdError, io::Read}; use clap::{crate_description, crate_name, crate_version, Arg, Command}; use rustyline::DefaultEditor; @@ -107,10 +107,12 @@ fn main() -> Result<(), Box> { } let file_name = matches.get_one::("file").unwrap(); - let file = io::buffered_read(File::open(file_name)?)?; + let mut file = io::buffered_read(File::open(file_name)?)?; + let mut source = Vec::new(); + file.read_to_end(&mut source)?; let executor = lua.try_enter(|ctx| { - let closure = Closure::load(ctx, Some(file_name.as_str()), file)?; + let closure = Closure::load(ctx, Some(file_name.as_str()), &source)?; Ok(ctx.stash(Executor::start(ctx, closure.into(), ()))) })?; diff --git a/src/any.rs b/src/any.rs index 2c505217..aa2bfbcb 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,4 +1,4 @@ -use std::{ +use core::{ any::TypeId, fmt, hash::{Hash, Hasher}, diff --git a/src/async_callback.rs b/src/async_callback.rs index 675f8873..fc53a991 100644 --- a/src/async_callback.rs +++ b/src/async_callback.rs @@ -1,11 +1,11 @@ -use std::{ +use alloc::rc::Rc; +use core::{ cell::Cell, future::{poll_fn, Future}, marker::PhantomData, mem, pin::Pin, ptr, - rc::Rc, task::{self, Poll, RawWaker, RawWakerVTable, Waker}, }; diff --git a/src/callback.rs b/src/callback.rs index 28898ba9..26a51217 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -1,4 +1,4 @@ -use std::{ +use core::{ fmt, hash::{Hash, Hasher}, pin::Pin, diff --git a/src/closure.rs b/src/closure.rs index 619877d5..410912d9 100644 --- a/src/closure.rs +++ b/src/closure.rs @@ -1,7 +1,4 @@ -use std::{ - hash::{Hash, Hasher}, - io::Read, -}; +use core::hash::{Hash, Hasher}; use allocator_api2::{boxed, vec, SliceExt}; use gc_arena::{allocator_api::MetricsAlloc, lock::Lock, Collect, Gc, Mutation}; @@ -115,7 +112,7 @@ impl<'gc> FunctionPrototype<'gc> { pub fn compile( ctx: Context<'gc>, source_name: &str, - source: impl Read, + source: &[u8], ) -> Result, CompilerError> { #[derive(Copy, Clone)] struct Interner<'gc>(Context<'gc>); @@ -261,7 +258,7 @@ impl<'gc> Closure<'gc> { pub fn load( ctx: Context<'gc>, name: Option<&str>, - source: impl Read, + source: &[u8], ) -> Result, CompilerError> { Self::load_with_env(ctx, name, source, ctx.globals()) } @@ -270,7 +267,7 @@ impl<'gc> Closure<'gc> { pub fn load_with_env( ctx: Context<'gc>, name: Option<&str>, - source: impl Read, + source: &[u8], env: Table<'gc>, ) -> Result, CompilerError> { let proto = FunctionPrototype::compile(ctx, name.unwrap_or(""), source)?; diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index a7fa5feb..d661bda6 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -1,10 +1,9 @@ -use std::{ - collections::{hash_map, VecDeque}, - fmt, iter, mem, -}; +use alloc::{boxed::Box, collections::VecDeque, vec, vec::Vec}; +use core::{fmt, iter, mem}; +use hashbrown::hash_map; -use ahash::HashMap; use gc_arena::Collect; +use hashbrown::HashMap; use thiserror::Error; use crate::{ diff --git a/src/compiler/interning.rs b/src/compiler/interning.rs index 2579545a..6277541b 100644 --- a/src/compiler/interning.rs +++ b/src/compiler/interning.rs @@ -1,6 +1,6 @@ -use std::rc::Rc; +use alloc::{boxed::Box, rc::Rc}; -use ahash::HashSet; +use hashbrown::HashSet; pub trait StringInterner { type String: AsRef<[u8]> + Clone; diff --git a/src/compiler/lexer.rs b/src/compiler/lexer.rs index b01112b6..f868fba5 100644 --- a/src/compiler/lexer.rs +++ b/src/compiler/lexer.rs @@ -1,7 +1,5 @@ -use std::{ - char, fmt, - io::{self, Read}, -}; +use alloc::vec::Vec; +use core::{char, fmt}; use gc_arena::Collect; use thiserror::Error; @@ -242,8 +240,6 @@ pub enum LexError { UnfinishedLongString, #[error("malformed number")] BadNumber, - #[error("IO Error: {0}")] - IOError(#[from] io::Error), } /// A 0-indexed line number of the current source input. @@ -257,24 +253,23 @@ impl fmt::Display for LineNumber { } } -pub struct Lexer { - source: Option, +pub struct Lexer<'a, S> { + source: &'a [u8], interner: S, - peek_buffer: Vec, + peek_count: usize, string_buffer: Vec, line_number: u64, } -impl Lexer +impl<'a, S> Lexer<'a, S> where - R: Read, S: StringInterner, { - pub fn new(source: R, interner: S) -> Lexer { + pub fn new(source: &'a [u8], interner: S) -> Lexer<'a, S> { Lexer { - source: Some(source), + source, interner, - peek_buffer: Vec::new(), + peek_count: 0, string_buffer: Vec::new(), line_number: 0, } @@ -506,8 +501,8 @@ where // End of stream encountered, clear any input handles and temp buffers fn reset(&mut self) { - self.source = None; - self.peek_buffer.clear(); + self.source = &[]; + self.peek_count = 0; self.string_buffer.clear(); } @@ -855,36 +850,16 @@ where } fn peek(&mut self, n: usize) -> Result, LexError> { - if let Some(source) = self.source.as_mut() { - while self.peek_buffer.len() <= n { - let mut c = [0]; - match source.read(&mut c) { - Ok(0) => { - self.source = None; - break; - } - Ok(_) => { - self.peek_buffer.push(c[0]); - } - Err(e) => { - if e.kind() != io::ErrorKind::Interrupted { - self.source = None; - return Err(LexError::IOError(e)); - } - } - } - } - } - - Ok(self.peek_buffer.get(n).copied()) + self.peek_count = (n + 1).min(self.source.len()); + Ok(self.source.get(n).copied()) } fn advance(&mut self, n: usize) { assert!( - n <= self.peek_buffer.len(), + n <= self.peek_count, "cannot advance over un-peeked characters" ); - self.peek_buffer.drain(0..n); + self.source = &self.source[n..]; } fn take_string(&mut self) -> S::String { @@ -945,7 +920,7 @@ fn get_reserved_word_token(word: &[u8]) -> Option> { #[cfg(test)] mod tests { - use std::rc::Rc; + use alloc::rc::Rc; use crate::compiler::interning::BasicInterner; diff --git a/src/compiler/parser.rs b/src/compiler/parser.rs index b931a586..1aa27978 100644 --- a/src/compiler/parser.rs +++ b/src/compiler/parser.rs @@ -1,4 +1,5 @@ -use std::{io::Read, ops, rc::Rc}; +use alloc::{borrow::ToOwned, boxed::Box, format, rc::Rc, string::String, vec, vec::Vec}; +use core::ops; use thiserror::Error; @@ -306,9 +307,8 @@ pub struct ParseError { pub line_number: LineNumber, } -pub fn parse_chunk(source: R, interner: S) -> Result, ParseError> +pub fn parse_chunk(source: &[u8], interner: S) -> Result, ParseError> where - R: Read, S: StringInterner, { Parser { @@ -319,16 +319,13 @@ where .parse_chunk() } -struct Parser { - lexer: Lexer, +struct Parser<'a, S: StringInterner> { + lexer: Lexer<'a, S>, read_buffer: Vec>>, recursion_guard: Rc<()>, } -impl Parser -where - R: Read, -{ +impl Parser<'_, S> { fn parse_chunk(&mut self) -> Result, ParseError> { let block = self.parse_block()?; if !self.look_ahead(0)?.is_none() { diff --git a/src/compiler/string_utils.rs b/src/compiler/string_utils.rs index 1abe1f96..a862857c 100644 --- a/src/compiler/string_utils.rs +++ b/src/compiler/string_utils.rs @@ -1,4 +1,4 @@ -use std::{ +use core::{ fmt::{self, Write as _}, str, }; diff --git a/src/constant.rs b/src/constant.rs index fb0bb259..6b388d23 100644 --- a/src/constant.rs +++ b/src/constant.rs @@ -1,4 +1,4 @@ -use std::hash::{Hash, Hasher}; +use core::hash::{Hash, Hasher}; use gc_arena::Collect; @@ -139,7 +139,11 @@ impl> Constant { Some(Self::Integer(d)) } } - (a, b) => Some(Self::Number((a.to_number()? / b.to_number()?).floor())), + (a, b) => { + let a = a.to_number()?; + let b = b.to_number()?; + Some(Self::Number(crate::math::floor(a / b))) + } } } @@ -163,7 +167,12 @@ impl> Constant { /// This operation always returns a Number, even when called with Integer arguments. pub fn exponentiate(&self, rhs: &Self) -> Option { - Some(Self::Number(self.to_number()?.powf(rhs.to_number()?))) + let lhs = self.to_number()?; + let rhs = rhs.to_number()?; + // This may fail if the environment doesn't support powf, as is + // currently the case for #![no_std] builds. This will cause an + // operator error when evaluating. + Some(Self::Number(crate::math::try_powf(lhs, rhs)?)) } pub fn negate(&self) -> Option { diff --git a/src/conversion.rs b/src/conversion.rs index 6a1616d0..09a6b747 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -1,4 +1,5 @@ -use std::{array, iter, ops, string::String as StdString}; +use alloc::{borrow::ToOwned, string::String as StdString, vec::Vec}; +use core::{array, iter, ops}; use crate::{ Callback, Closure, Context, Function, String, Table, Thread, TypeError, UserData, Value, diff --git a/src/error.rs b/src/error.rs index 41a61567..a113b32e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,8 @@ -use std::{error::Error as StdError, fmt, string::String as StdString, sync::Arc}; +use alloc::{ + string::{String as StdString, ToString}, + sync::Arc, +}; +use core::{error::Error as StdError, fmt}; use gc_arena::{Collect, Gc, Rootable}; use thiserror::Error; diff --git a/src/finalizers.rs b/src/finalizers.rs index 9a94d0b8..70f2b9ad 100644 --- a/src/finalizers.rs +++ b/src/finalizers.rs @@ -1,3 +1,5 @@ +use alloc::vec::Vec; + use gc_arena::{lock::RefLock, Collect, Finalization, Gc, GcWeak, Mutation}; use crate::{thread::ThreadInner, Thread}; diff --git a/src/function.rs b/src/function.rs index 10558768..05662a8f 100644 --- a/src/function.rs +++ b/src/function.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use gc_arena::{Collect, Gc, Mutation}; diff --git a/src/io.rs b/src/io.rs index 681d481b..01baf93d 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,4 +1,4 @@ -use std::io::{self, BufRead, BufReader, Read}; +use std::io::{BufRead, BufReader, Error, Read}; /// Takes an `R: BufRead` and: /// @@ -8,7 +8,7 @@ use std::io::{self, BufRead, BufReader, Read}; /// /// This mimics the initial behavior of luaL_loadfile(x). In order to correctly detect and skip the /// BOM and unix shebang, the internal buffer of the BufRead must be >= 3 bytes. -pub fn skip_prefix(r: &mut R) -> Result<(), io::Error> { +pub fn skip_prefix(r: &mut R) -> Result<(), Error> { if { let buf = r.fill_buf()?; buf.len() >= 3 && buf[0] == 0xef && buf[1] == 0xbb && buf[2] == 0xbf @@ -46,7 +46,7 @@ pub fn skip_prefix(r: &mut R) -> Result<(), io::Error> { /// Reads a Lua script from a `R: Read` and wraps it in a BufReader /// /// Also calls `skip_prefix` to skip any leading UTF-8 BOM or unix shebang. -pub fn buffered_read(r: R) -> Result, io::Error> { +pub fn buffered_read(r: R) -> Result, Error> { let mut r = BufReader::new(r); skip_prefix(&mut r)?; Ok(r) @@ -54,6 +54,8 @@ pub fn buffered_read(r: R) -> Result, io::Error> { #[cfg(test)] mod tests { + use alloc::{vec, vec::Vec}; + use super::*; #[test] diff --git a/src/lib.rs b/src/lib.rs index 0df7831e..08c2af9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,10 @@ +#![no_std] + +extern crate alloc; + +#[cfg(feature = "std")] +extern crate std; + pub mod any; pub mod async_callback; pub mod callback; @@ -9,7 +16,6 @@ pub mod error; pub mod finalizers; pub mod fuel; pub mod function; -pub mod io; pub mod lua; pub mod meta_ops; pub mod opcode; @@ -24,6 +30,11 @@ pub mod types; pub mod userdata; pub mod value; +#[cfg(feature = "std")] +pub mod io; + +mod math; + pub use self::{ async_callback::{async_sequence, SequenceReturn}, callback::{BoxSequence, Callback, CallbackFn, CallbackReturn, Sequence, SequencePoll}, diff --git a/src/lua.rs b/src/lua.rs index 67f03007..808fa2fc 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1,4 +1,4 @@ -use std::ops; +use core::ops; use gc_arena::{ arena::{CollectionPhase, Root}, @@ -9,7 +9,7 @@ use gc_arena::{ use crate::{ finalizers::Finalizers, stash::{Fetchable, Stashable}, - stdlib::{load_base, load_coroutine, load_io, load_math, load_string, load_table}, + stdlib::{load_base, load_coroutine, load_math, load_string, load_table}, string::InternedStringSet, thread::BadThreadMode, Error, ExternError, FromMultiValue, FromValue, Fuel, IntoValue, Registry, RuntimeError, @@ -155,6 +155,7 @@ impl Lua { } /// Create a new `Lua` instance with all of the stdlib loaded. + #[cfg(feature = "std")] pub fn full() -> Self { let mut lua = Lua::core(); lua.load_io(); @@ -180,9 +181,10 @@ impl Lua { } /// Load the parts of the stdlib that allow I/O. + #[cfg(feature = "std")] pub fn load_io(&mut self) { self.enter(|ctx| { - load_io(ctx); + crate::stdlib::load_io(ctx); }) } diff --git a/src/math.rs b/src/math.rs new file mode 100644 index 00000000..a9ed9755 --- /dev/null +++ b/src/math.rs @@ -0,0 +1,76 @@ +#[cfg(feature = "std")] +pub fn try_powf(a: f64, b: f64) -> Option { + Some(f64::powf(a, b)) +} + +#[cfg(not(feature = "std"))] +pub fn try_powf(a: f64, b: f64) -> Option { + // For now, disable powf on #![no_std] platforms. + // We could use a software implementation like the libm crate, but + // that will be inefficient; the ideal solution would be to use a + // proper libm for the platform, but that has portability issues. + None +} + +#[cfg(feature = "std")] +pub fn floor(val: f64) -> f64 { + val.floor() +} + +#[cfg(not(feature = "std"))] +pub fn floor(val: f64) -> f64 { + floor_manual(val) +} + +#[allow(dead_code)] +fn floor_manual(val: f64) -> f64 { + if !val.is_finite() { + return val; + } + let mut bits = val.to_bits(); + let exp = ((bits >> 52) & 0x7ff) as i32 - 1023; + if exp >= 0 { + // abs(val) >= 1 + let mask = (1 << u32::saturating_sub(52, exp.unsigned_abs())) - 1; + if val.is_sign_negative() { + bits += mask; // Note: this may overflow into the exponent + } + f64::from_bits(bits & !mask) + } else if val.is_sign_negative() { + if (bits << 1) == 0 { + -0.0 // floor(-0) = -0 + } else { + -1.0 // floor(-1 < val < 0) = -1 + } + } else { + 0.0 // floor(0 < val < 1) = 0 + } +} + +#[test] +#[ignore] +fn floor_f32_exhaustive() { + for x in 0..u32::MAX { + let f = f32::from_bits(x) as f64; + assert_eq!(floor_manual(f).to_bits(), f64::floor(f).to_bits(), "{}", f); + } +} + +#[test] +fn floor_basic() { + let tests = [ + 0.0, + 0.1, + 0.5, + 1.0, + 1.01, + f64::INFINITY, + (1u64 << 53) as f64, + ((1u64 << 53) + 1) as f64, + ]; + for f in tests { + assert_eq!(floor_manual(f).to_bits(), f64::floor(f).to_bits(), "{}", f); + let f = -f; + assert_eq!(floor_manual(f).to_bits(), f64::floor(f).to_bits(), "{}", f); + } +} diff --git a/src/meta_ops.rs b/src/meta_ops.rs index 6fd0517a..c3d154c3 100644 --- a/src/meta_ops.rs +++ b/src/meta_ops.rs @@ -1,4 +1,5 @@ -use std::io::Write; +use alloc::{string::ToString, vec::Vec}; +use core::fmt::Write; use gc_arena::Collect; use thiserror::Error; @@ -424,10 +425,14 @@ pub fn tostring<'gc>( } } - Ok(match v { - v @ Value::String(_) => MetaResult::Value(v), - v => MetaResult::Value(ctx.intern(v.display().to_string().as_bytes()).into()), - }) + match v { + v @ Value::String(_) => Ok(MetaResult::Value(v)), + v => { + let string = v.display().to_string(); + let interned = ctx.intern(string.as_bytes()); + Ok(MetaResult::Value(interned.into())) + } + } } pub fn equal<'gc>( @@ -763,8 +768,8 @@ pub fn concat<'gc>( let mut bytes = Vec::new(); for value in [a, b] { match value { - Value::Integer(i) => write!(&mut bytes, "{}", i).unwrap(), - Value::Number(n) => write!(&mut bytes, "{}", n).unwrap(), + Value::Integer(i) => write!(VecWriter(&mut bytes), "{}", i).unwrap(), + Value::Number(n) => write!(VecWriter(&mut bytes), "{}", n).unwrap(), Value::String(s) => bytes.extend(s.as_bytes()), _ => return None, } @@ -813,8 +818,8 @@ pub fn concat_many<'gc>( let mut bytes = Vec::with_capacity(len); for value in values { match value { - Value::Integer(i) => write!(&mut bytes, "{}", i).unwrap(), - Value::Number(n) => write!(&mut bytes, "{}", n).unwrap(), + Value::Integer(i) => write!(VecWriter(&mut bytes), "{}", i).unwrap(), + Value::Number(n) => write!(VecWriter(&mut bytes), "{}", n).unwrap(), Value::String(s) => bytes.extend(s.as_bytes()), _ => unreachable!(), } @@ -877,8 +882,8 @@ pub fn concat_separated<'gc>( let mut iter = values.iter(); if let Some(val) = iter.next() { match val { - Value::Integer(i) => write!(&mut bytes, "{}", i).unwrap(), - Value::Number(n) => write!(&mut bytes, "{}", n).unwrap(), + Value::Integer(i) => write!(VecWriter(&mut bytes), "{}", i).unwrap(), + Value::Number(n) => write!(VecWriter(&mut bytes), "{}", n).unwrap(), Value::String(s) => bytes.extend(s.as_bytes()), _ => unreachable!(), } @@ -886,8 +891,8 @@ pub fn concat_separated<'gc>( while let Some(val) = iter.next() { bytes.extend(&*sep_str); match val { - Value::Integer(i) => write!(&mut bytes, "{}", i).unwrap(), - Value::Number(n) => write!(&mut bytes, "{}", n).unwrap(), + Value::Integer(i) => write!(VecWriter(&mut bytes), "{}", i).unwrap(), + Value::Number(n) => write!(VecWriter(&mut bytes), "{}", n).unwrap(), Value::String(s) => bytes.extend(s.as_bytes()), _ => unreachable!(), } @@ -929,6 +934,15 @@ pub fn concat_separated<'gc>( Ok(ConcatMetaResult::Call(func.into())) } +struct VecWriter<'a>(&'a mut Vec); + +impl core::fmt::Write for VecWriter<'_> { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.0.extend_from_slice(s.as_bytes()); + Ok(()) + } +} + #[must_use] struct PreparedCall { func: Option, diff --git a/src/registry.rs b/src/registry.rs index 981d8f03..d653f5c7 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -1,4 +1,4 @@ -use std::{any::TypeId, hash::BuildHasherDefault}; +use core::{any::TypeId, hash::BuildHasherDefault}; use ahash::AHasher; use gc_arena::{ diff --git a/src/stack.rs b/src/stack.rs index f0f72e16..cf658685 100644 --- a/src/stack.rs +++ b/src/stack.rs @@ -1,4 +1,5 @@ -use std::{ +use alloc::vec::Vec; +use core::{ iter, ops::{Bound, Index, IndexMut, RangeBounds}, slice::{self, SliceIndex}, diff --git a/src/stash.rs b/src/stash.rs index 784a2433..e811fc71 100644 --- a/src/stash.rs +++ b/src/stash.rs @@ -1,4 +1,4 @@ -use std::fmt; +use core::fmt; use gc_arena::{DynamicRoot, DynamicRootSet, Mutation, Rootable}; diff --git a/src/stdlib/base.rs b/src/stdlib/base.rs index 5a18ca80..32d8b5ad 100644 --- a/src/stdlib/base.rs +++ b/src/stdlib/base.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use gc_arena::Collect; diff --git a/src/stdlib/io.rs b/src/stdlib/io.rs index 39254fcd..ea793aed 100644 --- a/src/stdlib/io.rs +++ b/src/stdlib/io.rs @@ -1,7 +1,5 @@ -use std::{ - io::{self, Write}, - pin::Pin, -}; +use core::pin::Pin; +use std::io::{stdout, Write}; use gc_arena::Collect; @@ -28,7 +26,7 @@ pub fn load_io<'gc>(ctx: Context<'gc>) { _exec: Execution<'gc, '_>, mut stack: Stack<'gc, '_>, ) -> Result, Error<'gc>> { - let mut stdout = io::stdout(); + let mut stdout = stdout(); while let Some(value) = stack.pop_back() { match meta_ops::tostring(ctx, value)? { diff --git a/src/stdlib/math.rs b/src/stdlib/math.rs index 99ae3e65..0dbee24e 100644 --- a/src/stdlib/math.rs +++ b/src/stdlib/math.rs @@ -1,3 +1,5 @@ +use alloc::format; + use gc_arena::Mutation; use crate::{ @@ -27,10 +29,14 @@ pub fn load_math<'gc>(ctx: Context<'gc>) { load_baseline(ctx, math); load_cmp(ctx, math); + #[cfg(feature = "rng")] load_random(ctx, math); - load_trig(ctx, math); - load_float(ctx, math); + #[cfg(feature = "std")] + { + load_trig(ctx, math); + load_float(ctx, math); + } ctx.set_global("math", math); } @@ -186,6 +192,7 @@ pub fn load_cmp<'gc>(ctx: Context<'gc>, math: Table<'gc>) { ); } +#[cfg(feature = "std")] pub fn load_float<'gc>(ctx: Context<'gc>, math: Table<'gc>) { fn to_int(v: Value) -> Value { if let Some(i) = v.to_integer() { @@ -256,6 +263,7 @@ pub fn load_float<'gc>(ctx: Context<'gc>, math: Table<'gc>) { ); } +#[cfg(feature = "std")] pub fn load_trig<'gc>(ctx: Context<'gc>, math: Table<'gc>) { math.set_field( ctx, @@ -288,8 +296,10 @@ pub fn load_trig<'gc>(ctx: Context<'gc>, math: Table<'gc>) { math.set_field(ctx, "tan", callback("tan", &ctx, |_, v: f64| Some(v.tan()))); } +#[cfg(feature = "rng")] pub fn load_random<'gc>(ctx: Context<'gc>, math: Table<'gc>) { - use std::{cell::RefCell, rc::Rc}; + use alloc::rc::Rc; + use core::cell::RefCell; use rand::{rngs::SmallRng, Rng, SeedableRng}; diff --git a/src/stdlib/mod.rs b/src/stdlib/mod.rs index aa766153..57ba4c69 100644 --- a/src/stdlib/mod.rs +++ b/src/stdlib/mod.rs @@ -1,11 +1,16 @@ mod base; mod coroutine; -mod io; mod math; mod string; mod table; +#[cfg(feature = "std")] +mod io; + pub use self::{ - base::load_base, coroutine::load_coroutine, io::load_io, math::load_math, string::load_string, + base::load_base, coroutine::load_coroutine, math::load_math, string::load_string, table::load_table, }; + +#[cfg(feature = "std")] +pub use self::io::load_io; diff --git a/src/stdlib/string.rs b/src/stdlib/string.rs index 556537c8..03970a0f 100644 --- a/src/stdlib/string.rs +++ b/src/stdlib/string.rs @@ -1,3 +1,5 @@ +use alloc::vec::Vec; + use crate::{Callback, CallbackReturn, Context, FromValue, String, Table, Value}; pub fn load_string<'gc>(ctx: Context<'gc>) { @@ -100,7 +102,7 @@ pub fn load_string<'gc>(ctx: Context<'gc>) { ctx.set_global("string", string); } -fn sub(string: &[u8], i: i64, j: Option) -> Result<&[u8], std::num::TryFromIntError> { +fn sub(string: &[u8], i: i64, j: Option) -> Result<&[u8], core::num::TryFromIntError> { let i = match i { i if i > 0 => i.saturating_sub(1).try_into()?, 0 => 0, diff --git a/src/stdlib/table.rs b/src/stdlib/table.rs index 28a3cb7a..072da499 100644 --- a/src/stdlib/table.rs +++ b/src/stdlib/table.rs @@ -1,5 +1,4 @@ -use std::mem; -use std::pin::Pin; +use core::{mem, pin::Pin}; use anyhow::Context as _; use gc_arena::Collect; diff --git a/src/string.rs b/src/string.rs index 5472f58d..4e307cb4 100644 --- a/src/string.rs +++ b/src/string.rs @@ -1,5 +1,7 @@ -use std::{ - alloc, fmt, +use alloc::boxed::Box; +use core::{ + alloc::Layout, + fmt, hash::{BuildHasherDefault, Hash, Hasher}, ops, slice, str::{self, Utf8Error}, @@ -145,10 +147,8 @@ impl<'gc> String<'gc> { match self.0.buffer { Buffer::Indirect(p) => &(*p), Buffer::Inline(len) => { - let layout = alloc::Layout::new::(); - let (_, offset) = layout - .extend(alloc::Layout::array::(len).unwrap()) - .unwrap(); + let layout = Layout::new::(); + let (_, offset) = layout.extend(Layout::array::(len).unwrap()).unwrap(); let data = (Gc::as_ptr(self.0) as *const u8).offset(offset as isize) as *const u8; slice::from_raw_parts(data, len) diff --git a/src/table/raw.rs b/src/table/raw.rs index f83cfade..cd56f294 100644 --- a/src/table/raw.rs +++ b/src/table/raw.rs @@ -1,4 +1,4 @@ -use std::{fmt, hash::Hash, i64, mem}; +use core::{fmt, hash::Hash, mem}; use allocator_api2::vec; use gc_arena::{allocator_api::MetricsAlloc, Collect, Gc, Mutation}; diff --git a/src/table/table.rs b/src/table/table.rs index b89b3077..e1a832a5 100644 --- a/src/table/table.rs +++ b/src/table/table.rs @@ -1,6 +1,6 @@ -use std::{ +use core::{ hash::{Hash, Hasher}, - i64, mem, + mem, }; use gc_arena::{lock::RefLock, Collect, Gc, Mutation}; diff --git a/src/thread/executor.rs b/src/thread/executor.rs index 4f4630bd..9281c4bc 100644 --- a/src/thread/executor.rs +++ b/src/thread/executor.rs @@ -1,4 +1,4 @@ -use std::hash::{Hash, Hasher}; +use core::hash::{Hash, Hasher}; use allocator_api2::vec; use gc_arena::{allocator_api::MetricsAlloc, lock::RefLock, Collect, Gc, Mutation}; diff --git a/src/thread/thread.rs b/src/thread/thread.rs index d552bc4c..04b8d68d 100644 --- a/src/thread/thread.rs +++ b/src/thread/thread.rs @@ -1,4 +1,5 @@ -use std::{ +use alloc::format; +use core::{ cell::RefMut, hash::{Hash, Hasher}, }; diff --git a/src/types.rs b/src/types.rs index c5eb49d1..b51151d8 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,4 @@ -use std::fmt::{self, Debug}; +use core::fmt::{self, Debug}; use gc_arena::Collect; diff --git a/src/userdata.rs b/src/userdata.rs index d8b860e0..b7cc2f3b 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1,4 +1,4 @@ -use std::{ +use core::{ hash::{Hash, Hasher}, mem, }; diff --git a/src/value.rs b/src/value.rs index 6f6d7942..5c245116 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,4 +1,5 @@ -use std::{f64, fmt, i64}; +use alloc::string::ToString; +use core::fmt; use gc_arena::{Collect, Gc}; @@ -56,7 +57,7 @@ impl<'gc> Value<'gc> { struct ValueDisplay<'gc>(Value<'gc>); impl<'gc> fmt::Display for ValueDisplay<'gc> { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { Value::Nil => write!(fmt, "nil"), Value::Boolean(b) => write!(fmt, "{}", b), diff --git a/tests/callback.rs b/tests/callback.rs index 6670fd5f..3928c1bf 100644 --- a/tests/callback.rs +++ b/tests/callback.rs @@ -1,4 +1,4 @@ -use std::pin::Pin; +use core::pin::Pin; use gc_arena::Collect; use piccolo::{ diff --git a/tests/scripts.rs b/tests/scripts.rs index d60e20f7..3f43a066 100644 --- a/tests/scripts.rs +++ b/tests/scripts.rs @@ -5,7 +5,7 @@ use std::{ use piccolo::{io, Closure, Executor, ExternError, Lua}; -fn run_lua_code(name: &str, code: impl Read) -> Result<(), ExternError> { +fn run_lua_code(name: &str, code: &[u8]) -> Result<(), ExternError> { let mut lua = Lua::full(); let exec = lua.try_enter(|ctx| { @@ -24,14 +24,19 @@ fn run_tests(dir: &str) -> bool { let mut file_failed = false; for dir in read_dir(dir).expect("could not list dir contents") { let path = dir.expect("could not read dir entry").path(); - let file = io::buffered_read(File::open(&path).unwrap()).unwrap(); if let Some(ext) = path.extension() { if ext == "lua" { + let mut file = io::buffered_read(File::open(&path).unwrap()).unwrap(); + let mut source = Vec::new(); + file.read_to_end(&mut source).unwrap(); + let _ = writeln!(stdout(), "running {:?}", path); - if let Err(err) = run_lua_code(path.to_string_lossy().as_ref(), file) { + if let Err(err) = run_lua_code(path.to_string_lossy().as_ref(), &source) { let _ = writeln!(stdout(), "error encountered running: {:?}", err); file_failed = true; } + } else { + let _ = writeln!(stdout(), "skipping file {:?}", path); } } else { let _ = writeln!(stdout(), "skipping file {:?}", path); diff --git a/tests/sizes.rs b/tests/sizes.rs index 11098a3a..2434f19d 100644 --- a/tests/sizes.rs +++ b/tests/sizes.rs @@ -1,4 +1,4 @@ -use std::mem; +use core::mem; use piccolo::{opcode::OpCode, Callback, Closure, String, Table, Thread, UserData, Value}; diff --git a/tests/table.rs b/tests/table.rs index a55558b4..e7775e86 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -1,4 +1,4 @@ -use std::cmp::Ordering; +use core::cmp::Ordering; use piccolo::{Lua, Table, Value}; diff --git a/tests/tail_call_stack_panic.rs b/tests/tail_call_stack_panic.rs index d64356f3..fe300d2b 100644 --- a/tests/tail_call_stack_panic.rs +++ b/tests/tail_call_stack_panic.rs @@ -1,4 +1,6 @@ -use std::string::String as StdString; +extern crate alloc; + +use alloc::string::String as StdString; use piccolo::{meta_ops::MetaCallError, Closure, Executor, Lua}; diff --git a/util/src/freeze.rs b/util/src/freeze.rs index 58e57d3d..b53c6c07 100644 --- a/util/src/freeze.rs +++ b/util/src/freeze.rs @@ -1,4 +1,5 @@ -use std::{cell::RefCell, marker::PhantomData, mem, rc::Rc}; +use alloc::rc::Rc; +use core::{cell::RefCell, marker::PhantomData, mem}; use thiserror::Error; @@ -208,8 +209,21 @@ impl<'h, 'f, F: for<'a> Freeze<'a>> ScopeGuard for FreezeGuard<'h, 'f, F> { // // This is impossible to trigger safely without calling the private `set` / `unset` // methods manually. - eprintln!("freeze lock held during `FreezeGuard::unset`, aborting!"); - std::process::abort() + + struct PanicOnDrop; + impl Drop for PanicOnDrop { + fn drop(&mut self) { + panic!("intentionally double panicking to abort.") + } + } + + #[allow(unused)] + let guard = PanicOnDrop; + + panic!("freeze lock held during `FreezeGuard::unset`, aborting!"); + + #[allow(unreachable_code)] + drop(guard); } } } diff --git a/util/src/lib.rs b/util/src/lib.rs index e20d32da..d81104b1 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -1,3 +1,7 @@ +#![no_std] + +extern crate alloc; + pub mod freeze; pub mod user_methods; diff --git a/util/src/serde/de.rs b/util/src/serde/de.rs index 56e389f2..0102e154 100644 --- a/util/src/serde/de.rs +++ b/util/src/serde/de.rs @@ -1,4 +1,5 @@ -use std::fmt; +use alloc::string::{String, ToString}; +use core::fmt; use piccolo::{table::NextValue, Table, Value}; use serde::de; diff --git a/util/src/serde/ser.rs b/util/src/serde/ser.rs index 371348d4..56e86e83 100644 --- a/util/src/serde/ser.rs +++ b/util/src/serde/ser.rs @@ -1,4 +1,5 @@ -use std::fmt; +use alloc::string::{String, ToString}; +use core::fmt; use piccolo::{Context, Table, Value}; use serde::ser; diff --git a/util/src/user_methods.rs b/util/src/user_methods.rs index 403e0469..aa18af55 100644 --- a/util/src/user_methods.rs +++ b/util/src/user_methods.rs @@ -1,4 +1,4 @@ -use std::marker::PhantomData; +use core::marker::PhantomData; use gc_arena::{arena::Root, barrier, Collect, Rootable, Static}; use piccolo::{