Skip to content

Commit

Permalink
Added two tiny stdlib modules and support for importing them.
Browse files Browse the repository at this point in the history
  • Loading branch information
JanEricNitschke committed Apr 27, 2024
1 parent cfe50f6 commit e7beee6
Show file tree
Hide file tree
Showing 20 changed files with 208 additions and 56 deletions.
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod config;
mod heap;
mod natives;
mod scanner;
mod stdlib;
mod types;
mod value;
mod vm;
Expand Down
3 changes: 1 addition & 2 deletions src/natives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ use crate::natives::list::{
};
use crate::natives::native_functions::{
assert_native, clock_native, delattr_native, getattr_native, hasattr_native, input_native,
is_int_native, len_native, print_native, rng_native, setattr_native, sleep_native, sqrt_native,
is_int_native, len_native, print_native, rng_native, setattr_native, sleep_native,
to_float_native, to_int_native, to_string_native, type_native,
};

pub fn define(vm: &mut VM) {
vm.define_native_function(&"clock", &[0], clock_native);
vm.define_native_function(&"assert", &[1], assert_native);
vm.define_native_function(&"sleep", &[1], sleep_native);
vm.define_native_function(&"sqrt", &[1], sqrt_native);
vm.define_native_function(&"input", &[1], input_native);
vm.define_native_function(&"float", &[1], to_float_native);
vm.define_native_function(&"int", &[1], to_int_native);
Expand Down
10 changes: 1 addition & 9 deletions src/natives/native_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::time::Duration;
use std::time::{SystemTime, UNIX_EPOCH};

use crate::{
value::{ias_f64, ias_u64, Number, Value},
value::{ias_u64, Number, Value},
vm::VM,
};

Expand Down Expand Up @@ -43,14 +43,6 @@ pub(super) fn assert_native(_vm: &mut VM, args: &mut [&mut Value]) -> Result<Val
}
}

pub(super) fn sqrt_native(_vm: &mut VM, args: &mut [&mut Value]) -> Result<Value, String> {
match &args[0] {
Value::Number(Number::Float(n)) => Ok(n.sqrt().into()),
Value::Number(Number::Integer(n)) => Ok((ias_f64(*n)).sqrt().into()),
x => Err(format!("'sqrt' expected numeric argument, got: {}", *x)),
}
}

pub(super) fn input_native(vm: &mut VM, args: &mut [&mut Value]) -> Result<Value, String> {
match &args[0] {
Value::String(prompt) => {
Expand Down
14 changes: 14 additions & 0 deletions src/stdlib/math.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use crate::value::{ias_f64, ModuleContents, Number, Value};
use crate::vm::VM;

pub(super) fn sqrt_native(_vm: &mut VM, args: &mut [&mut Value]) -> Result<Value, String> {
match &args[0] {
Value::Number(Number::Float(n)) => Ok(n.sqrt().into()),
Value::Number(Number::Integer(n)) => Ok((ias_f64(*n)).sqrt().into()),
x => Err(format!("'sqrt' expected numeric argument, got: {}", *x)),
}
}

pub(super) fn module() -> ModuleContents {
vec![("sqrt", &[1], sqrt_native)]
}
7 changes: 7 additions & 0 deletions src/stdlib/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod math;

use crate::vm::VM;

pub fn register(vm: &mut VM) {
vm.register_stdlib_module(&"math", math::module());
}
1 change: 1 addition & 0 deletions src/stdlib/zen.gen
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
print("Zen of generic or something??");
1 change: 1 addition & 0 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ impl std::fmt::Display for NativeMethod {

pub type NativeFunctionImpl = fn(&mut VM, &mut [&mut Value]) -> Result<Value, String>;
pub type NativeMethodImpl = fn(&mut VM, &mut Value, &mut [&mut Value]) -> Result<Value, String>;
pub type ModuleContents = Vec<(&'static str, &'static [u8], NativeFunctionImpl)>;

const fn always_equals<T>(_: &T, _: &T) -> bool {
true
Expand Down
196 changes: 158 additions & 38 deletions src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ use crate::{
config,
heap::{ClosureId, FunctionId, Heap, ModuleId, StringId, UpvalueId},
scanner::Scanner,
stdlib,
value::{
Class, Closure, Function, Instance, List, Module, NativeFunction, NativeFunctionImpl,
NativeMethod, NativeMethodImpl, Number, Upvalue, Value,
Class, Closure, Function, Instance, List, Module, ModuleContents, NativeFunction,
NativeFunctionImpl, NativeMethod, NativeMethodImpl, Number, Upvalue, Value,
},
};

Expand Down Expand Up @@ -555,6 +556,7 @@ pub struct VM {
modules: Vec<ModuleId>,
path: PathBuf,
builtins: HashMap<StringId, Global>,
stdlib: HashMap<StringId, ModuleContents>,
}

impl VM {
Expand All @@ -568,6 +570,7 @@ impl VM {
modules: Vec::new(),
path,
builtins: HashMap::default(),
stdlib: HashMap::default(),
}
}

Expand Down Expand Up @@ -611,6 +614,7 @@ impl VM {

// Need to have the first module loaded before defining natives
natives::define(self);
stdlib::register(self);

self.run()
} else {
Expand Down Expand Up @@ -725,24 +729,7 @@ impl VM {
}
};

let file_path = self.modules.last().map_or_else(
|| PathBuf::from(&*string_id),
|module| {
let mut path = module.path.clone();
path.pop();
path.push(&*string_id);
path
},
);

let file_path = match file_path.strip_prefix("./") {
Ok(file_path) => file_path.to_owned(),
Err(_) => file_path,
};
let file_path = match file_path.canonicalize() {
Ok(file_path) => file_path,
Err(_) => file_path,
};
let file_path = self.clean_filepath(string_id);

let name = if let Some(stem) = file_path.file_stem() {
stem.to_str().unwrap().to_string()
Expand All @@ -759,34 +746,157 @@ impl VM {
}
}

match std::fs::read(&file_path) {
Err(_) => {
runtime_error!(
self,
"Could not find the file to be imported. Attempted path {:?}",
file_path.to_slash_lossy()
);
return Some(InterpretResult::RuntimeError);
}
Ok(contents) => {
if let Some(function) = self.compile(&contents, &name) {
let function = self.heap.add_function(function);
let function_id = function.as_function();
let closure = Closure::new(*function_id, true, self.modules.last().copied());
let mut generic_stdlib_path = PathBuf::from(file!());
generic_stdlib_path.pop();
generic_stdlib_path.push("stdlib");
generic_stdlib_path.push(format!("{name}.gen"));
let generic_stdlib_path = match generic_stdlib_path.canonicalize() {
Ok(path) => path,
Err(_) => generic_stdlib_path,
};

self.add_closure_to_modules(&closure, file_path, names_to_import, alias);
// User defined generic module
if let Ok(contents) = std::fs::read(&file_path) {
if let Some(value) =
self.import_generic_module(&contents, &name, file_path, names_to_import, alias)
{
return Some(value);
}
} else if let Ok(contents) = std::fs::read(generic_stdlib_path) {
// stdlib generic module
if let Some(value) =
self.import_generic_module(&contents, &name, file_path, names_to_import, alias)
{
return Some(value);
}
} else if let Some(stdlib_functions) = self.stdlib.get(&string_id).cloned() {
// These clones are only necessary because this is extracted into a function.
// If they cause performance issues this can be inlined or turned into a macro.
if let Some(value) = self.import_rust_stdlib(
string_id,
file_path,
alias,
&stdlib_functions,
names_to_import,
) {
return Some(value);
}
} else {
runtime_error!(
self,
"Could not find the file to be imported. Attempted path {:?} and stdlib.",
file_path.to_slash_lossy()
);
return Some(InterpretResult::RuntimeError);
}
None
}

let value_id = self.heap.add_closure(closure);
self.stack_push(value_id);
self.execute_call(value_id, 0);
fn import_rust_stdlib(
&mut self,
string_id: StringId,
file_path: PathBuf,
alias: Option<StringId>,
stdlib_functions: &ModuleContents,
names_to_import: Option<Vec<StringId>>,
) -> Option<InterpretResult> {
let mut module = Module::new(
string_id,
file_path,
None,
alias.map_or(string_id, |alias| alias),
);
for (name, arity, fun) in stdlib_functions {
let name_id = self.heap.string_id(name);
self.heap
.strings_by_name
.insert((*name).to_string(), name_id);
let value = self.heap.add_native_function(NativeFunction {
name: name_id,
arity,
fun: *fun,
});
module.globals.insert(
name_id,
Global {
value,
mutable: false,
},
);
}
// Stdlib rust module
// Add all the functions to the modules globals
// If we only want to import some functions then we just move them
// from the new module to the current globals, the module then gets dropped.
if let Some(names_to_import) = names_to_import {
for name in names_to_import {
if let Some(global) = module.globals.remove(&name) {
self.globals().insert(name, global);
} else {
runtime_error!(self, "Could not find name to import.");
return Some(InterpretResult::RuntimeError);
}
}
} else {
// Otherwise we add the whole module to the current globals.
let module_id = self.heap.add_module(module);
self.globals().insert(
string_id,
Global {
value: module_id,
mutable: false,
},
);
}
None
}

fn import_generic_module(
&mut self,
contents: &[u8],
name: &str,
file_path: PathBuf,
names_to_import: Option<Vec<StringId>>,
alias: Option<StringId>,
) -> Option<InterpretResult> {
if let Some(function) = self.compile(contents, name) {
let function = self.heap.add_function(function);
let function_id = function.as_function();
let closure = Closure::new(*function_id, true, self.modules.last().copied());

self.add_closure_to_modules(&closure, file_path, names_to_import, alias);

let value_id = self.heap.add_closure(closure);
self.stack_push(value_id);
self.execute_call(value_id, 0);
} else {
return Some(InterpretResult::RuntimeError);
}
None
}

#[allow(clippy::option_if_let_else)]
fn clean_filepath(&mut self, string_id: StringId) -> PathBuf {
let file_path = self.modules.last().map_or_else(
|| PathBuf::from(&*string_id),
|module| {
let mut path = module.path.clone();
path.pop();
path.push(&*string_id);
path
},
);

let file_path = match file_path.strip_prefix("./") {
Ok(file_path) => file_path.to_owned(),
Err(_) => file_path,
};
match file_path.canonicalize() {
Ok(file_path) => file_path,
Err(_) => file_path,
}
}

fn binary_op<T: Into<Value>>(&mut self, op: BinaryOp<T>, int_only: bool) -> bool {
let slice_start = self.stack.len() - 2;

Expand Down Expand Up @@ -1679,6 +1789,16 @@ impl VM {
);
}

pub fn register_stdlib_module<T: ToString>(
&mut self,
name: &T,
functions: Vec<(&'static str, &'static [u8], NativeFunctionImpl)>,
) {
let name_id = self.heap.string_id(name);
self.heap.strings_by_name.insert(name.to_string(), name_id);
self.stdlib.insert(name_id, functions);
}

pub fn define_native_class<T: ToString>(&mut self, name: &T) {
let name_id = self.heap.string_id(name);
self.heap.strings_by_name.insert(name.to_string(), name_id);
Expand Down
1 change: 1 addition & 0 deletions test/import/import_generic_stdlib.gen
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "zen"; # expect: Zen of generic or something??
3 changes: 3 additions & 0 deletions test/import/import_rust_stdlib.gen
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import "math";

print(math); # expect: <module math>
2 changes: 1 addition & 1 deletion test/import/invalid_path.gen
Original file line number Diff line number Diff line change
@@ -1 +1 @@
import "nope.gen"; # expect runtime error: Could not find the file to be imported. Attempted path "test/import/nope.gen"
import "nope.gen"; # expect runtime error: Could not find the file to be imported. Attempted path "test/import/nope.gen" and stdlib.
1 change: 1 addition & 0 deletions test/import/nested/nested_rust_stdlib.gen
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "math";
3 changes: 3 additions & 0 deletions test/import/nested_rust_stdlib.gen
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import "nested/nested_rust_stdlib.gen";

print(math); # expect runtime error: Undefined variable 'math'.
2 changes: 2 additions & 0 deletions test/native/sqrt_arity.gen → test/math/sqrt_arity.gen
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from "math" import sqrt;

print(sqrt(9)); # expect: 3.0

sqrt(1, 2); # expect runtime error: Native function 'sqrt' expected 1 argument, got 2.
2 changes: 2 additions & 0 deletions test/native/sqrt_bool.gen → test/math/sqrt_bool.gen
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from "math" import sqrt;

print(sqrt(9)); # expect: 3.0

sqrt(true); # expect runtime error: 'sqrt' expected numeric argument, got: true
5 changes: 5 additions & 0 deletions test/math/sqrt_nil.gen
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import "math";

print(math.sqrt(9)); # expect: 3.0

math.sqrt(nil); # expect runtime error: 'sqrt' expected numeric argument, got: nil
5 changes: 5 additions & 0 deletions test/math/sqrt_string.gen
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import "math";

print(math.sqrt(9)); # expect: 3.0

math.sqrt("s"); # expect runtime error: 'sqrt' expected numeric argument, got: s
3 changes: 0 additions & 3 deletions test/native/sqrt_nil.gen

This file was deleted.

3 changes: 0 additions & 3 deletions test/native/sqrt_string.gen

This file was deleted.

1 change: 1 addition & 0 deletions tool/bin/test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ void _defineTestSuites() {
"test/import/foo.gen": "skip",
"test/import/circular/circ2.gen": "skip",
"test/import/circular/circ3.gen": "skip",
"test/import/nested/nested_rust_stdlib.gen": "skip",
};

// JVM doesn't correctly implement IEEE equality on boxed doubles.
Expand Down

0 comments on commit e7beee6

Please sign in to comment.