Skip to content

Commit

Permalink
Context variable globals
Browse files Browse the repository at this point in the history
  • Loading branch information
sagebind committed Feb 2, 2024
1 parent ac1dade commit ed17132
Show file tree
Hide file tree
Showing 11 changed files with 70 additions and 32 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
4 changes: 2 additions & 2 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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"
Expand Down
6 changes: 3 additions & 3 deletions runtime/src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub(crate) fn load_module() -> Result<Value, Exception> {
"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()),
Expand Down Expand Up @@ -91,8 +91,8 @@ async fn nil(_: &mut Fiber, _: Vec<Value>) -> Result<Value, Exception> {
Ok(Value::Nil)
}

async fn pwd(fiber: &mut Fiber, _: Vec<Value>) -> Result<Value, Exception> {
Ok(fiber.current_dir())
async fn pass(_fiber: &mut Fiber, args: Vec<Value>) -> Result<Value, Exception> {
Ok(args.first().cloned().unwrap_or(Value::Nil))
}

/// Throw an exception.
Expand Down
17 changes: 15 additions & 2 deletions runtime/src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SourceFile>) -> Result<Closure, Exception> {
Expand All @@ -29,6 +28,20 @@ pub(crate) fn compile(fiber: &mut Fiber, file: impl Into<SourceFile>) -> Result<
}
}

pub(crate) fn compile_anonymous_closure(file: impl Into<SourceFile>) -> Result<Closure, Exception> {
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<Closure, Exception> {
// Constructing a closure is quite easy since our interpreter is based
Expand Down Expand Up @@ -276,7 +289,7 @@ async fn evaluate_expr(fiber: &mut Fiber, expr: Expr) -> Result<Value, Exception
match expr {
Expr::Number(number) => 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,
Expand Down
30 changes: 18 additions & 12 deletions runtime/src/fiber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Gc<Scope>>,

Expand All @@ -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
}

Expand Down Expand Up @@ -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(),
};
Expand All @@ -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
Expand Down Expand Up @@ -199,7 +205,7 @@ impl Fiber {
}
}

Value::Nil
self.cvar_globals.get(name)
}

/// Force the garbage collector to run now.
Expand Down
6 changes: 6 additions & 0 deletions runtime/src/init.rt
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import 'builtins' for pass

$GLOBALS->modules = [
# A list of module loader functions.
loaders: []

# A map of module paths to their preloaded contents.
loaded: [:]
]

$GLOBALS->pwd = {
pass @cwd
}
2 changes: 1 addition & 1 deletion runtime/src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Gc<Scope>>,
}

Expand Down
2 changes: 1 addition & 1 deletion syntax/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
20 changes: 18 additions & 2 deletions syntax/src/ast.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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()
}
}
12 changes: 4 additions & 8 deletions syntax/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -16,7 +17,6 @@ pub fn parse(source_file: impl Into<SourceFile>) -> Result<Block, ParseError> {

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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -376,11 +373,10 @@ impl ParsableNode for RegexLiteral {
let regex_str = pair.as_str();
let regex_str = &regex_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()))
}
}

Expand Down

0 comments on commit ed17132

Please sign in to comment.