Skip to content

Commit

Permalink
Merge pull request #279 from koto-lang/some-api-tweaks
Browse files Browse the repository at this point in the history
Some API tweaks
  • Loading branch information
irh authored Jan 24, 2024
2 parents 8a97f5d + e06c02b commit 18bfaca
Show file tree
Hide file tree
Showing 67 changed files with 1,099 additions and 1,025 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@ The Koto project adheres to
- The `+` operator has been reintroduced for tuples, lists, and maps.
- Raw strings are now supported. Any string prefixed with `r` will skip
character escaping and string interpolation.
- `as` is now available in import expressions for more ergonomic item renaming.
- `import` expressions can now use `as` for more ergonomic item renaming.
- Assignments can now be used in `while`/`until` conditions.

#### API

- The `koto_derive` crate has been introduced containing derive macros that make
it easier to implement `KotoObject`s.
- `Koto::run_instance_function` has been added.
- `Ptr`/`PtrMut` now have an associated `ref_count` function.

#### Libs

- A `regex` module has been added, thanks to [@jasal92](https://github.com/jasal82).

### Changed

Expand All @@ -36,6 +42,12 @@ The Koto project adheres to
- Objects can be compared with `null` on the LHS without having to implement
`KotoObject::equal` and/or `not_equal`.

#### API

- `Vm` has been renamed to `KotoVm` for the sake of clarity.
- `Value` has been renamed to `KValue` for consistency with the other core
runtime value types, and to avoid polluting the prelude with a generic name.

#### Internals

- The Koto runtime is now thread-safe by default, with the previous
Expand Down
8 changes: 4 additions & 4 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,15 +264,15 @@ fn load_config(config_path: Option<&String>) -> Result<Config> {
Ok(_) => {
let exports = koto.exports().data();
match exports.get("repl") {
Some(Value::Map(repl_config)) => {
Some(KValue::Map(repl_config)) => {
let repl_config = repl_config.data();
match repl_config.get("colored_output") {
Some(Value::Bool(value)) => config.colored_output = *value,
Some(KValue::Bool(value)) => config.colored_output = *value,
Some(_) => bail!("expected bool for colored_output setting"),
None => {}
}
match repl_config.get("edit_mode") {
Some(Value::Str(value)) => match value.as_str() {
Some(KValue::Str(value)) => match value.as_str() {
"emacs" => config.edit_mode = EditMode::Emacs,
"vi" => config.edit_mode = EditMode::Vi,
other => {
Expand All @@ -286,7 +286,7 @@ fn load_config(config_path: Option<&String>) -> Result<Config> {
None => {}
}
match repl_config.get("max_history") {
Some(Value::Number(value)) => match value.as_i64() {
Some(KValue::Number(value)) => match value.as_i64() {
value if value > 0 => config.max_history = value as usize,
_ => bail!("expected positive number for max_history setting"),
},
Expand Down
22 changes: 11 additions & 11 deletions crates/derive/src/koto_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pub(crate) fn generate_koto_lookup_entries(attr: TokenStream, item: TokenStream)

#[automatically_derived]
impl #runtime::KotoLookup for #struct_ident {
fn lookup(&self, key: &#runtime::ValueKey) -> Option<#runtime::Value> {
fn lookup(&self, key: &#runtime::ValueKey) -> Option<#runtime::KValue> {
#entries_map_name.with(|entries| entries.get(key).cloned())
}
}
Expand Down Expand Up @@ -131,17 +131,17 @@ fn wrap_method(
let wrapped_call = match return_type {
MethodReturnType::None => quote! {
#call;
Ok(#runtime::Value::Null)
Ok(#runtime::KValue::Null)
},
MethodReturnType::Value => quote! { Ok(#call) },
MethodReturnType::Result => call,
};

quote! {
match ctx.instance_and_args(
|i| matches!(i, #runtime::Value::Object(_)), #type_name)?
|i| matches!(i, #runtime::KValue::Object(_)), #type_name)?
{
(#runtime::Value::Object(o), #args_match) => {
(#runtime::KValue::Object(o), #args_match) => {
match o.#cast::<#struct_ident>() {
Ok(#instance) => {
#wrapped_call
Expand All @@ -159,17 +159,17 @@ fn wrap_method(
let wrapped_call = match return_type {
MethodReturnType::None => quote! {
#call;
Ok(#runtime::Value::Null)
Ok(#runtime::KValue::Null)
},
MethodReturnType::Value => quote! { Ok(#call) },
MethodReturnType::Result => call,
};

quote! {
match ctx.instance_and_args(
|i| matches!(i, #runtime::Value::Object(_)), #type_name)?
|i| matches!(i, #runtime::KValue::Object(_)), #type_name)?
{
(#runtime::Value::Object(o), extra_args) => { #wrapped_call }
(#runtime::KValue::Object(o), extra_args) => { #wrapped_call }
(_, other) => #runtime::type_error_with_slice(#type_name, other),
}
}
Expand All @@ -178,7 +178,7 @@ fn wrap_method(

let wrapper = quote! {
#[automatically_derived]
fn #wrapper_name(ctx: &mut #runtime::CallContext) -> #runtime::Result<#runtime::Value> {
fn #wrapper_name(ctx: &mut #runtime::CallContext) -> #runtime::Result<#runtime::KValue> {
#wrapper_body
}
};
Expand Down Expand Up @@ -211,14 +211,14 @@ fn lookup_insert(
.expect("failed to parse koto_method attribute");

quote! {
let f = #runtime::Value::NativeFunction(#runtime::KNativeFunction::new(#wrapper_name));
let f = #runtime::KValue::NativeFunction(#runtime::KNativeFunction::new(#wrapper_name));
#(result.insert(#fn_names.into(), f.clone());)*
}
} else {
quote! {
result.insert(
#fn_name.into(),
#runtime::Value::NativeFunction(#runtime::KNativeFunction::new(#wrapper_name)));
#runtime::KValue::NativeFunction(#runtime::KNativeFunction::new(#wrapper_name)));
}
}
}
Expand All @@ -234,7 +234,7 @@ fn detect_return_type(return_type: &ReturnType) -> MethodReturnType {
ReturnType::Default => MethodReturnType::None,
ReturnType::Type(_, ty) => match ty.as_ref() {
Type::Tuple(t) if t.elems.is_empty() => MethodReturnType::None,
Type::Path(p) if p.path.is_ident("Value") => MethodReturnType::Value,
Type::Path(p) if p.path.is_ident("KValue") => MethodReturnType::Value,
// Default to expecting a Result to be the return value
// Ideally we would detect that this is precisely koto_runtime::Result,
// but in practice type aliases may be used so we should just let the compiler complain
Expand Down
50 changes: 31 additions & 19 deletions crates/koto/src/koto.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::{prelude::*, Error, Result};
use crate::{prelude::*, Error, Ptr, Result};
use dunce::canonicalize;
use koto_bytecode::CompilerSettings;
use koto_runtime::ModuleImportedCallback;
use koto_runtime::{KotoVm, ModuleImportedCallback};
use std::path::PathBuf;

/// The main interface for the Koto language.
///
/// This provides a high-level API for compiling and executing Koto scripts in a Koto [Vm].
/// This provides a high-level API for compiling and executing Koto scripts in a Koto [Vm](KotoVm).
///
/// Example:
///
Expand All @@ -17,7 +17,7 @@ use std::path::PathBuf;
/// let mut koto = Koto::default();
///
/// match koto.compile_and_run("1 + 2")? {
/// Value::Number(result) => {
/// KValue::Number(result) => {
/// assert_eq!(result, 3);
/// }
/// other => panic!("Unexpected result: {}", koto.value_to_string(other)?),
Expand All @@ -27,7 +27,7 @@ use std::path::PathBuf;
/// }
/// ```
pub struct Koto {
runtime: Vm,
runtime: KotoVm,
run_tests: bool,
export_top_level_ids: bool,
script_path: Option<PathBuf>,
Expand All @@ -49,7 +49,7 @@ impl Koto {
/// Creates a new instance of Koto with the given settings
pub fn with_settings(settings: KotoSettings) -> Self {
Self {
runtime: Vm::with_settings(VmSettings {
runtime: KotoVm::with_settings(KotoVmSettings {
stdin: settings.stdin,
stdout: settings.stdout,
stderr: settings.stderr,
Expand Down Expand Up @@ -90,7 +90,7 @@ impl Koto {
}

/// Runs the chunk last compiled with [compile](Koto::compile)
pub fn run(&mut self) -> Result<Value> {
pub fn run(&mut self) -> Result<KValue> {
let chunk = self.chunk.clone();
match chunk {
Some(chunk) => self.run_chunk(chunk),
Expand All @@ -99,26 +99,38 @@ impl Koto {
}

/// A helper for calling [set_args](Koto::set_args) followed by [run](Koto::run).
pub fn run_with_args(&mut self, args: &[String]) -> Result<Value> {
pub fn run_with_args(&mut self, args: &[String]) -> Result<KValue> {
self.set_args(args)?;
self.run()
}

/// Compiles and runs a Koto script, and returns the script's result
///
/// This is equivalent to calling [compile](Self::compile) followed by [run](Self::run).
pub fn compile_and_run(&mut self, script: &str) -> Result<Value> {
pub fn compile_and_run(&mut self, script: &str) -> Result<KValue> {
self.compile(script)?;
self.run()
}

/// Runs a function with the given arguments
pub fn run_function(&mut self, function: Value, args: CallArgs) -> Result<Value> {
pub fn run_function(&mut self, function: KValue, args: CallArgs) -> Result<KValue> {
self.runtime
.run_function(function, args)
.map_err(|e| e.into())
}

/// Runs an instance function with the given arguments
pub fn run_instance_function(
&mut self,
instance: KValue,
function: KValue,
args: CallArgs,
) -> Result<KValue> {
self.runtime
.run_instance_function(instance, function, args)
.map_err(|e| e.into())
}

/// Runs a function in the runtime's exports map
///
/// ```
Expand All @@ -130,7 +142,7 @@ impl Koto {
/// koto.compile_and_run("export say_hello = |name| 'Hello, $name!'")?;
///
/// match koto.run_exported_function("say_hello", CallArgs::Single("World".into()))? {
/// Value::Str(result) => assert_eq!(result, "Hello, World!"),
/// KValue::Str(result) => assert_eq!(result, "Hello, World!"),
/// other => panic!(
/// "Unexpected result: {}",
/// koto.value_to_string(other)?
Expand All @@ -140,15 +152,15 @@ impl Koto {
/// Ok(())
/// }
/// ```
pub fn run_exported_function(&mut self, function_name: &str, args: CallArgs) -> Result<Value> {
pub fn run_exported_function(&mut self, function_name: &str, args: CallArgs) -> Result<KValue> {
match self.runtime.get_exported_function(function_name) {
Some(f) => self.run_function(f, args),
None => Err(Error::FunctionNotFound),
}
}

/// Converts a [Value] into a [String] by evaluating `@display` in the runtime
pub fn value_to_string(&mut self, value: Value) -> Result<String> {
/// Converts a [KValue] into a [String] by evaluating `@display` in the runtime
pub fn value_to_string(&mut self, value: KValue) -> Result<String> {
self.runtime.value_to_string(&value).map_err(|e| e.into())
}

Expand All @@ -161,7 +173,7 @@ impl Koto {

/// Sets the arguments that can be accessed from within the script via `koto.args()`
pub fn set_args(&mut self, args: &[String]) -> Result<()> {
use Value::{Map, Str, Tuple};
use KValue::{Map, Str, Tuple};

let koto_args = args
.iter()
Expand All @@ -187,7 +199,7 @@ impl Koto {

/// Sets the path of the current script, accessible via `koto.script_dir` / `koto.script_path`
pub fn set_script_path(&mut self, path: Option<PathBuf>) -> Result<()> {
use Value::{Map, Null, Str};
use KValue::{Map, Null, Str};

let (script_dir, script_path) = match &path {
Some(path) => {
Expand Down Expand Up @@ -220,13 +232,13 @@ impl Koto {
}
}

fn run_chunk(&mut self, chunk: Ptr<Chunk>) -> Result<Value> {
fn run_chunk(&mut self, chunk: Ptr<Chunk>) -> Result<KValue> {
let result = self.runtime.run(chunk)?;

if self.run_tests {
let maybe_tests = self.runtime.exports().get_meta_value(&MetaKey::Tests);
match maybe_tests {
Some(Value::Map(tests)) => {
Some(KValue::Map(tests)) => {
self.runtime.run_tests(tests)?;
}
Some(other) => {
Expand Down Expand Up @@ -317,7 +329,7 @@ impl KotoSettings {

impl Default for KotoSettings {
fn default() -> Self {
let default_vm_settings = VmSettings::default();
let default_vm_settings = KotoVmSettings::default();
Self {
run_tests: true,
run_import_tests: true,
Expand Down
4 changes: 2 additions & 2 deletions crates/koto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//! match koto.compile("1 + 2") {
//! Ok(_) => match koto.run() {
//! Ok(result) => match result {
//! Value::Number(n) => println!("{n}"), // 3.0
//! KValue::Number(n) => println!("{n}"), // 3.0
//! other => panic!("Unexpected result type: {}", other.type_as_string()),
//! },
//! Err(runtime_error) => {
Expand All @@ -35,7 +35,7 @@ pub mod prelude;
pub use koto_bytecode as bytecode;
pub use koto_parser as parser;
pub use koto_runtime as runtime;
pub use koto_runtime::derive;
pub use koto_runtime::{derive, Borrow, BorrowMut, Ptr, PtrMut};

pub use crate::error::{Error, Result};
pub use crate::koto::{Koto, KotoSettings};
2 changes: 1 addition & 1 deletion crates/koto/tests/docs_examples.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use koto::{prelude::*, runtime::Result};
use koto::{prelude::*, runtime::Result, Ptr, PtrMut};
use std::{
ops::Deref,
path::{Path, PathBuf},
Expand Down
2 changes: 1 addition & 1 deletion crates/koto/tests/koto_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use koto::prelude::*;
use koto::{prelude::*, PtrMut};
use std::{
fs::read_to_string,
path::{Path, PathBuf},
Expand Down
2 changes: 1 addition & 1 deletion crates/koto/tests/repl_mode_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! The exports map gets populated with any top-level assigned IDs, and is then made available to
//! each subsequent chunk.

use koto::{prelude::*, runtime::Result};
use koto::{prelude::*, runtime::Result, Ptr, PtrMut};

fn run_repl_mode_test(inputs_and_expected_outputs: &[(&str, &str)]) {
let output = PtrMut::from(String::new());
Expand Down
7 changes: 7 additions & 0 deletions crates/memory/src/arc/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ impl<T: ?Sized> Ptr<T> {
pub fn address(this: &Self) -> Address {
Arc::as_ptr(&this.0).into()
}

/// Returns the number of references to the allocated memory
///
/// Only strong references are counted, weak references don't get added to the result.
pub fn ref_count(this: &Self) -> usize {
Arc::strong_count(&this.0)
}
}

impl<T: Clone> Ptr<T> {
Expand Down
7 changes: 7 additions & 0 deletions crates/memory/src/rc/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ impl<T: ?Sized> Ptr<T> {
pub fn address(this: &Self) -> Address {
Rc::as_ptr(&this.0).into()
}

/// Returns the number of references to the allocated memory
///
/// Only strong references are counted, weak references don't get added to the result.
pub fn ref_count(this: &Self) -> usize {
Rc::strong_count(&this.0)
}
}

impl<T: Clone> Ptr<T> {
Expand Down
Loading

0 comments on commit 18bfaca

Please sign in to comment.