Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ repository = "https://github.com/kyren/piccolo"
[workspace.dependencies]
ahash = "0.8"
allocator-api2 = "0.2"
thiserror = "1"
anyhow = "1.0"
gc-arena = { git = "https://github.com/kyren/gc-arena", rev = "5a7534b883b703f23cfb8c3cfdf033460aa77ea9", features = ["allocator-api2", "hashbrown"] }
hashbrown = { version = "0.14", features = ["raw"] }
rand = { version = "0.8", features = ["small_rng"] }
serde = "1.0"
thiserror = "1.0"

piccolo = { path = "./", version = "0.3.3" }

Expand Down
36 changes: 18 additions & 18 deletions examples/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ use clap::{crate_description, crate_name, crate_version, Arg, ArgAction, Command
use rustyline::DefaultEditor;

use piccolo::{
compiler::{ParseError, ParseErrorKind},
io, meta_ops, Callback, CallbackReturn, Closure, Executor, ExternError, Function, Lua,
StashedExecutor,
RuntimeError, StashedExecutor,
};

fn run_code(lua: &mut Lua, executor: &StashedExecutor, code: &str) -> Result<(), ExternError> {
// Compile and set up the function
lua.try_enter(|ctx| {
let closure = match Closure::load(ctx, None, ("return ".to_owned() + code).as_bytes()) {
Ok(closure) => closure,
Err(_) => Closure::load(ctx, None, code.as_bytes())?,
Ok(c) => c,
Err(_) => match Closure::load(ctx, None, code.as_bytes()) {
Ok(c) => c,
Err(e) => return Err(e.into()),
},
};
let function = Function::compose(
&ctx,
Expand All @@ -37,15 +40,24 @@ fn run_code(lua: &mut Lua, executor: &StashedExecutor, code: &str) -> Result<(),
Ok(())
})?;

lua.execute::<()>(executor)
lua.finish(executor).map_err(RuntimeError::new)?;

lua.try_enter(|ctx| match ctx.fetch(executor).take_result::<()>(ctx) {
Ok(Ok(())) => Ok(()),
Ok(Err(err)) => {
eprintln!("{}", err.into_error_with_stack_trace(ctx.fetch(executor)));
Ok(())
}
Err(e) => Err(RuntimeError::new(e).into()),
})
}

fn run_repl(lua: &mut Lua) -> Result<(), Box<dyn StdError>> {
let mut editor = DefaultEditor::new()?;
let executor = lua.enter(|ctx| ctx.stash(Executor::new(ctx)));

loop {
let mut prompt = "> ";
let prompt = "> ";
let mut line = String::new();

loop {
Expand All @@ -60,18 +72,6 @@ fn run_repl(lua: &mut Lua) -> Result<(), Box<dyn StdError>> {
}

match run_code(lua, &executor, &line) {
Err(err)
if !read_empty
&& matches!(
err.root_cause().downcast_ref::<ParseError>(),
Some(ParseError {
kind: ParseErrorKind::EndOfStream { .. },
..
})
) =>
{
prompt = ">> ";
}
Err(e) => {
editor.add_history_entry(line)?;
eprintln!("{}", e);
Expand Down
66 changes: 56 additions & 10 deletions src/closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::hash::{Hash, Hasher};

use allocator_api2::{boxed, vec, SliceExt};
use gc_arena::{allocator_api::MetricsAlloc, lock::Lock, Collect, Gc, Mutation};
use std::{error::Error as StdError, fmt};
use thiserror::Error;

use crate::{
Expand All @@ -12,17 +13,44 @@ use crate::{
Constant, Context, String, Table, Value,
};

// Note: These errors must not have #[error(transparent)] so that
// anyhow::Error::root_cause and downcasting work as expected by the
// interpreter. (Even though that gives slightly cleaner error messages).
#[derive(Debug, Error)]
// Note: We format compiler errors like Lua: "<chunk>:<line>: <message>"
#[derive(Debug, Clone)]
pub enum CompilerError {
#[error("parse error")]
Parsing(#[from] compiler::ParseError),
#[error("compile error")]
Compilation(#[from] compiler::CompileError),
Parsing {
chunk: std::string::String,
line: LineNumber,
message: std::string::String,
},
Compilation {
chunk: std::string::String,
line: LineNumber,
message: std::string::String,
},
}

impl fmt::Display for CompilerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CompilerError::Parsing {
chunk,
line,
message,
} => {
write!(f, "{}:{}: {}", chunk, line, message)
}
CompilerError::Compilation {
chunk,
line,
message,
} => {
write!(f, "{}:{}: {}", chunk, line, message)
}
}
}
}

impl StdError for CompilerError {}

/// A compiled Lua function.
///
/// In Lua jargon, a "prototype" is only executable code, it has none of its "upvalues" set and
Expand Down Expand Up @@ -130,8 +158,26 @@ impl<'gc> FunctionPrototype<'gc> {

let interner = Interner(ctx);

let chunk = compiler::parse_chunk(source, interner)?;
let compiled_function = compiler::compile_chunk(&chunk, interner)?;
let chunk = match compiler::parse_chunk(source, interner) {
Ok(c) => c,
Err(e) => {
return Err(CompilerError::Parsing {
chunk: source_name.to_string(),
line: e.line_number,
message: e.to_string(),
})
}
};
let compiled_function = match compiler::compile_chunk(&chunk, interner) {
Ok(c) => c,
Err(e) => {
return Err(CompilerError::Compilation {
chunk: source_name.to_string(),
line: e.line_number,
message: e.kind.to_string(),
})
}
};

Ok(FunctionPrototype::from_compiled(
&ctx,
Expand Down
29 changes: 11 additions & 18 deletions src/compiler/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,24 +211,12 @@ impl<S: AsRef<[u8]>> fmt::Debug for Token<S> {
}
}

fn print_char(c: u8) -> char {
char::from_u32(c as u32).unwrap_or(char::REPLACEMENT_CHARACTER)
}

#[derive(Debug, Error)]
pub enum LexError {
#[error("short string not finished, expected matching {}", print_char(*.0))]
#[error("unfinished string")]
UnfinishedShortString(u8),
#[error("unexpected character: {}", print_char(*.0))]
UnexpectedCharacter(u8),
#[error("hexadecimal digit expected")]
HexDigitExpected,
#[error("missing '{{' in \\u{{xxxx}} escape")]
EscapeUnicodeStart,
#[error("missing '}}' in \\u{{xxxx}} escape")]
EscapeUnicodeEnd,
#[error("invalid unicode value in \\u{{xxxx}} escape")]
EscapeUnicodeInvalid,
#[error("\\ddd escape out of 0-255 range")]
EscapeDecimalTooLarge,
#[error("invalid escape sequence")]
Expand Down Expand Up @@ -480,7 +468,7 @@ where
Token::Name(self.take_string())
}
} else {
return Err(LexError::UnexpectedCharacter(c));
return Err(LexError::InvalidEscape);
}
}
}))
Expand Down Expand Up @@ -623,28 +611,33 @@ where

b'u' => {
if self.peek(1)? != Some(b'{') {
return Err(LexError::EscapeUnicodeStart);
return Err(LexError::InvalidEscape);
}
self.advance(2);

let mut u: u32 = 0;
let mut saw_digit = false;
loop {
if let Some(c) = self.peek(0)? {
if c == b'}' {
if !saw_digit {
return Err(LexError::HexDigitExpected);
}
self.advance(1);
break;
} else if let Some(h) = from_hex_digit(c) {
saw_digit = true;
u = (u << 4) | h as u32;
self.advance(1);
} else {
return Err(LexError::EscapeUnicodeEnd);
return Err(LexError::HexDigitExpected);
}
} else {
return Err(LexError::EscapeUnicodeEnd);
return Err(LexError::UnfinishedShortString(start_quote));
}
}

let c = char::from_u32(u).ok_or(LexError::EscapeUnicodeInvalid)?;
let c = char::from_u32(u).ok_or(LexError::InvalidEscape)?;
let mut buf = [0; 4];
for &b in c.encode_utf8(&mut buf).as_bytes() {
self.string_buffer.push(b);
Expand Down
Loading