diff --git a/Cargo.lock b/Cargo.lock index 49f8cff..8609895 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1017,7 +1017,7 @@ dependencies = [ "log", "pest", "pest_derive", - "regex-syntax", + "regex", "serde", "stderrlog", "test-generator", diff --git a/Cargo.toml b/Cargo.toml index 2e95dd5..3287454 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ edition = "2021" dirs = "5.0" directories = "5.0" log = "0.4" +regex = "1.10" scopeguard = "1.2" serde = "1" tokio = "1.2" diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 8d76af1..d8d1e79 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "riptide-runtime" description = "The Riptide programming language interpreter" -rust-version = "1.70.0" +rust-version = "1.75.0" version.workspace = true authors.workspace = true license.workspace = true @@ -13,7 +13,7 @@ bstr = "1.9" dirs.workspace = true futures = "0.3" log.workspace = true -regex = "1.10" +regex.workspace = true riptide-syntax.path = "../syntax" scopeguard.workspace = true tokio-pipe = "0.2" diff --git a/runtime/src/builtins.rs b/runtime/src/builtins.rs index 07145ec..6ae3cda 100644 --- a/runtime/src/builtins.rs +++ b/runtime/src/builtins.rs @@ -21,7 +21,7 @@ pub(crate) fn load_module() -> Result { "load" => Value::ForeignFn(load.into()), "nil" => Value::ForeignFn(nil.into()), "nth" => Value::ForeignFn(nth.into()), - "pwd" => Value::ForeignFn(pwd.into()), + "pass" => Value::ForeignFn(pass.into()), "throw" => Value::ForeignFn(throw.into()), "try" => Value::ForeignFn(try_fn.into()), "typeof" => Value::ForeignFn(type_of.into()), @@ -91,8 +91,8 @@ async fn nil(_: &mut Fiber, _: Vec) -> Result { Ok(Value::Nil) } -async fn pwd(fiber: &mut Fiber, _: Vec) -> Result { - Ok(fiber.current_dir()) +async fn pass(_fiber: &mut Fiber, args: Vec) -> Result { + Ok(args.first().cloned().unwrap_or(Value::Nil)) } /// Throw an exception. diff --git a/runtime/src/eval.rs b/runtime/src/eval.rs index 65eab63..f11922f 100644 --- a/runtime/src/eval.rs +++ b/runtime/src/eval.rs @@ -16,7 +16,6 @@ use super::{ }; use gc::Gc; use futures::future::try_join_all; -use regex::bytes::Regex; /// Compile the given source code as a closure. pub(crate) fn compile(fiber: &mut Fiber, file: impl Into) -> Result { @@ -29,6 +28,20 @@ pub(crate) fn compile(fiber: &mut Fiber, file: impl Into) -> Result< } } +pub(crate) fn compile_anonymous_closure(file: impl Into) -> Result { + let file = file.into(); + let file_name = file.name().to_string(); + + match parse(file) { + Ok(block) => Ok(Closure { + block, + scope: None, + name: None, + }), + Err(e) => throw!("error parsing {}: {}", file_name, e), + } +} + /// Compile a block into an executable closure. fn compile_block(fiber: &mut Fiber, block: Block) -> Result { // Constructing a closure is quite easy since our interpreter is based @@ -276,7 +289,7 @@ async fn evaluate_expr(fiber: &mut Fiber, expr: Expr) -> Result Ok(Value::Number(number)), Expr::String(string) => Ok(Value::from(string)), - Expr::Regex(RegexLiteral(src)) => Ok(Value::Regex(Regex::new(&src).unwrap())), + Expr::Regex(RegexLiteral(src)) => Ok(Value::Regex(src)), Expr::CvarReference(cvar) => evaluate_cvar(fiber, cvar).await, Expr::CvarScope(cvar_scope) => evaluate_cvar_scope(fiber, cvar_scope).await, Expr::Substitution(substitution) => evaluate_substitution(fiber, substitution).await, diff --git a/runtime/src/fiber.rs b/runtime/src/fiber.rs index 960ea25..9ca0595 100644 --- a/runtime/src/fiber.rs +++ b/runtime/src/fiber.rs @@ -33,6 +33,10 @@ pub struct Fiber { /// Table where global values are stored that are not on the stack. globals: Table, + /// Default global values for context variables. This holds the values of + /// context variables that have not been set by any scope. + cvar_globals: Table, + /// Call stack of functions being executed by this fiber. pub(crate) stack: Vec>, @@ -47,12 +51,22 @@ impl Fiber { pid: next_pid(), module_index: Rc::new(ModuleIndex::default()), globals: Default::default(), + cvar_globals: Default::default(), stack: Vec::new(), io: io_cx, }; log::debug!("root fiber {} created", fiber.pid); + match std::env::current_dir() { + Ok(cwd) => { + fiber.cvar_globals.set("cwd", cwd); + } + Err(e) => { + log::warn!("failed to set initial cwd: {}", e); + } + } + fiber } @@ -82,6 +96,7 @@ impl Fiber { pid: next_pid(), module_index: self.module_index.clone(), globals: self.globals.clone(), + cvar_globals: self.cvar_globals.clone(), stack: self.stack.clone(), io: self.io.try_clone().unwrap(), }; @@ -93,17 +108,8 @@ impl Fiber { /// Get the fiber's current working directory. pub fn current_dir(&self) -> Value { - // First check the `@cwd` context variable. - let mut cwd = self.get_cvar("cwd"); - - // If not set, check the process-wide (default) working directory. - if cwd.is_nil() { - if let Ok(path) = std::env::current_dir() { - cwd = path.into(); - } - } - - cwd + // The working dir is just implemented as the `@cwd` context variable. + self.get_cvar("cwd") } /// Get the current exit code for the runtime. If no exit has been @@ -199,7 +205,7 @@ impl Fiber { } } - Value::Nil + self.cvar_globals.get(name) } /// Force the garbage collector to run now. diff --git a/runtime/src/init.rt b/runtime/src/init.rt index 69c5452..0f230de 100644 --- a/runtime/src/init.rt +++ b/runtime/src/init.rt @@ -1,3 +1,5 @@ +import 'builtins' for pass + $GLOBALS->modules = [ # A list of module loader functions. loaders: [] @@ -5,3 +7,7 @@ $GLOBALS->modules = [ # A map of module paths to their preloaded contents. loaded: [:] ] + +$GLOBALS->pwd = { + pass @cwd +} diff --git a/runtime/src/scope.rs b/runtime/src/scope.rs index 98f5b50..64d78a9 100644 --- a/runtime/src/scope.rs +++ b/runtime/src/scope.rs @@ -21,7 +21,7 @@ pub(crate) struct Scope { /// on creation of the scope. pub(crate) cvars: Table, - /// The lexically parent scope to this one. + /// The lexical parent scope to this one. pub(crate) parent: Option>, } diff --git a/syntax/Cargo.toml b/syntax/Cargo.toml index f7a3c23..5bf9b7b 100644 --- a/syntax/Cargo.toml +++ b/syntax/Cargo.toml @@ -9,7 +9,7 @@ edition.workspace = true [dependencies] pest = "2.7" pest_derive = "2.7" -regex-syntax = "0.8" +regex.workspace = true [dev-dependencies] difference = "2.0" diff --git a/syntax/src/ast.rs b/syntax/src/ast.rs index 18f9916..5af48b2 100644 --- a/syntax/src/ast.rs +++ b/syntax/src/ast.rs @@ -1,6 +1,7 @@ //! Abstract syntax tree definitions for the language syntax. use crate::source::Span; +use regex::bytes::Regex; use std::fmt; macro_rules! derive_debug_enum_transparent { @@ -193,11 +194,26 @@ pub enum InterpolatedStringPart { Substitution(Substitution), } -#[derive(Clone, Debug, PartialEq)] -pub struct RegexLiteral(pub String); +/// A regular expression literal. +/// +/// Regular expressions written in source code are always validated and parsed +/// as part of the syntax parsing routine, and converted into a regular +/// expression AST as part of the overall program AST. +/// +/// This also can be used as a runtime optimizations, as regex literals do not +/// have to be re-parsed every time they are used without any effort from the +/// user. They can be executed directly from AST memory. +#[derive(Clone, Debug)] +pub struct RegexLiteral(pub Regex); impl fmt::Display for RegexLiteral { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } + +impl PartialEq for RegexLiteral { + fn eq(&self, other: &Self) -> bool { + self.0.as_str() == other.0.as_str() + } +} diff --git a/syntax/src/parser.rs b/syntax/src/parser.rs index 476d6c1..3a8ab4b 100644 --- a/syntax/src/parser.rs +++ b/syntax/src/parser.rs @@ -5,6 +5,7 @@ use crate::{ source::{SourceFile, Span}, }; use pest::iterators::Pair; +use regex::bytes::Regex; /// Attempt to parse a source file into an abstract syntax tree. /// @@ -16,7 +17,6 @@ pub fn parse(source_file: impl Into) -> Result { let mut ctx = ParsingContext { source_file: source_file.clone(), - regex_parser: regex_syntax::Parser::new(), }; let mut pair = match grammar::parse(source_file.source_text(), Rule::program) { @@ -51,9 +51,6 @@ struct ParsingContext { /// Source file currently being parsed. This is provided so that the AST /// can fetch span information. source_file: SourceFile, - - /// Regular expression parser. - regex_parser: regex_syntax::Parser, } impl ParsingContext { @@ -376,11 +373,10 @@ impl ParsableNode for RegexLiteral { let regex_str = pair.as_str(); let regex_str = ®ex_str[1..regex_str.len()-1]; - if let Err(e) = ctx.regex_parser.parse(regex_str) { - return Err(ParseError::new(ctx.span(&pair), e.to_string())); + match Regex::new(regex_str) { + Ok(regex) => Ok(RegexLiteral(regex)), + Err(e) => Err(ParseError::new(ctx.span(&pair), e.to_string())) } - - Ok(RegexLiteral(regex_str.into())) } }