Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

script_path / script_dir improvements #375

Merged
merged 3 commits into from
Dec 4, 2024
Merged
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ The Koto project adheres to
- `:` placement following keys in maps is now more flexible.
([#368](https://github.com/koto-lang/koto/issues/368))

#### Core Library

- `koto.script_path` and `script_dir` are now functions.

#### API

- The line and column numbers referred to in spans are now zero-based.
Expand All @@ -80,6 +84,9 @@ The Koto project adheres to
- `From` impls for `KNumber` now saturate integer values that are out of the
target type's bounds, instead of wrapping.
- `KString` will now inline short strings to reduce allocations.
- `Koto::compile` and `compile_and_run` now take `CompileArgs` which include
compiler settings. The equivalent compiler settings have been removed from
`KotoSettings`.

#### Libs

Expand Down
24 changes: 3 additions & 21 deletions crates/bytecode/src/chunk.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use crate::InstructionReader;
use koto_memory::Ptr;
use koto_parser::{ConstantPool, Span};
use std::{
fmt::{self, Write},
path::{Path, PathBuf},
};
use koto_parser::{ConstantPool, KString, Span};
use std::fmt::{self, Write};

/// Debug information for a Koto program
#[derive(Clone, Debug, Default, PartialEq, Eq)]
Expand Down Expand Up @@ -56,27 +53,12 @@ pub struct Chunk {
/// The constant data associated with the chunk's bytecode
pub constants: ConstantPool,
/// The path of the program's source file
pub source_path: Option<PathBuf>,
pub source_path: Option<KString>,
/// Debug information associated with the chunk's bytecode
pub debug_info: DebugInfo,
}

impl Chunk {
/// Initializes a Chunk
pub fn new(
bytes: Box<[u8]>,
constants: ConstantPool,
source_path: Option<&Path>,
debug_info: DebugInfo,
) -> Self {
Self {
bytes,
constants,
source_path: source_path.map(Path::to_path_buf),
debug_info,
}
}

/// Returns a [String] displaying the instructions contained in the compiled [Chunk]
pub fn bytes_as_string(chunk: &Chunk) -> String {
let mut iter = chunk.bytes.iter();
Expand Down
12 changes: 7 additions & 5 deletions crates/bytecode/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,16 @@ impl CompileNodeOutput {

/// The settings used by the [Compiler]
pub struct CompilerSettings {
/// Causes all top level identifiers to be exported
/// Whether or not top-level identifiers should be automatically exported
///
/// Disabled by default.
/// The default behaviour in Koto is that `export` expressions are required to make a value
/// available outside of the current module.
///
/// This is used by the REPL to automatically export values so that they're available between
/// chunks.
/// This is used by the REPL, allowing for incremental compilation and execution of expressions
/// that need to share declared values.
pub export_top_level_ids: bool,
/// Causes the compiler to emit CheckType instructions when type hints are encountered.
/// When enabled, the compiler will emit type check instructions when type hints are encountered
/// that will be performed at runtime.
///
/// Enabled by default.
pub enable_type_checks: bool,
Expand Down
83 changes: 44 additions & 39 deletions crates/bytecode/src/loader.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{Chunk, Compiler, CompilerError, CompilerSettings};
use dunce::canonicalize;
use koto_memory::Ptr;
use koto_parser::{format_source_excerpt, Parser, Span};
use koto_parser::{format_source_excerpt, KString, Parser, Span};
use rustc_hash::FxHasher;
use std::{
collections::HashMap,
Expand Down Expand Up @@ -46,19 +46,19 @@ pub struct LoaderErrorSource {
/// The span in the script where the error occurred
pub span: Span,
/// The script's path
pub path: Option<PathBuf>,
pub path: Option<KString>,
}

impl LoaderError {
pub(crate) fn from_parser_error(
error: koto_parser::Error,
source: &str,
source_path: Option<&Path>,
source_path: Option<KString>,
) -> Self {
let source = LoaderErrorSource {
contents: source.into(),
span: error.span,
path: source_path.map(Path::to_path_buf),
path: source_path,
};
Self {
error: LoaderErrorKind::from(error).into(),
Expand All @@ -69,12 +69,12 @@ impl LoaderError {
pub(crate) fn from_compiler_error(
error: CompilerError,
source: &str,
source_path: Option<&Path>,
source_path: Option<KString>,
) -> Self {
let source = LoaderErrorSource {
contents: source.into(),
span: error.span,
path: source_path.map(Path::to_path_buf),
path: source_path,
};
Self {
error: LoaderErrorKind::from(error).into(),
Expand Down Expand Up @@ -136,7 +136,7 @@ impl Loader {
pub fn compile_script(
&mut self,
script: &str,
script_path: Option<&Path>,
script_path: Option<KString>,
settings: CompilerSettings,
) -> Result<Ptr<Chunk>, LoaderError> {
match Parser::parse(script) {
Expand All @@ -148,7 +148,13 @@ impl Loader {

debug_info.source = script.to_string();

Ok(Chunk::new(bytes, ast.consume_constants(), script_path, debug_info).into())
Ok(Chunk {
bytes,
constants: ast.consume_constants(),
source_path: script_path,
debug_info,
}
.into())
}
Err(e) => Err(LoaderError::from_parser_error(e, script, script_path)),
}
Expand All @@ -158,39 +164,34 @@ impl Loader {
pub fn compile_module(
&mut self,
module_name: &str,
current_script_path: Option<&Path>,
current_script_path: Option<impl AsRef<Path>>,
) -> Result<CompileModuleResult, LoaderError> {
let mut load_module_from_path = |module_path: PathBuf| {
let module_path = module_path.canonicalize()?;
let module_path = find_module(module_name, current_script_path)?;

match self.chunks.get(&module_path) {
Some(chunk) => Ok(CompileModuleResult {
chunk: chunk.clone(),
match self.chunks.get(&module_path) {
Some(chunk) => Ok(CompileModuleResult {
chunk: chunk.clone(),
path: module_path,
loaded_from_cache: true,
}),
None => {
let script = std::fs::read_to_string(&module_path)?;

let chunk = self.compile_script(
&script,
Some(module_path.clone().into()),
CompilerSettings::default(),
)?;

self.chunks.insert(module_path.clone(), chunk.clone());

Ok(CompileModuleResult {
chunk,
path: module_path,
loaded_from_cache: true,
}),
None => {
let script = std::fs::read_to_string(&module_path)?;

let chunk = self.compile_script(
&script,
Some(&module_path),
CompilerSettings::default(),
)?;

self.chunks.insert(module_path.clone(), chunk.clone());

Ok(CompileModuleResult {
chunk,
path: module_path,
loaded_from_cache: false,
})
}
loaded_from_cache: false,
})
}
};

let module_path = find_module(module_name, current_script_path)?;
load_module_from_path(module_path)
}
}

/// Clears the compiled module cache
Expand All @@ -211,7 +212,7 @@ pub struct CompileModuleResult {
/// provided then std::env::current_dir will be used.
pub fn find_module(
module_name: &str,
current_script_path: Option<&Path>,
current_script_path: Option<impl AsRef<Path>>,
) -> Result<PathBuf, LoaderError> {
// Get the directory of the provided script path, or the current working directory
let search_folder = match &current_script_path {
Expand All @@ -220,7 +221,10 @@ pub fn find_module(
if canonicalized.is_file() {
match canonicalized.parent() {
Some(parent_dir) => parent_dir.to_path_buf(),
None => return Err(LoaderErrorKind::FailedToGetPathParent(path.into()).into()),
None => {
let path = PathBuf::from(path.as_ref());
return Err(LoaderErrorKind::FailedToGetPathParent(path).into());
}
}
} else {
canonicalized
Expand All @@ -242,6 +246,7 @@ pub fn find_module(
.join("main")
.with_extension(extension);
if result.exists() {
let result = canonicalize(result)?;
Ok(result)
} else {
Err(LoaderErrorKind::UnableToFindModule(module_name.into()).into())
Expand Down
10 changes: 4 additions & 6 deletions crates/cli/docs/core_lib/koto.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,20 +217,18 @@ check! 10
## script_dir

```kototype
String or Null
|| -> String or Null
```

If a script is being executed then `script_dir` provides the directory that the
current script is contained in as a String, otherwise `script_dir` is Null.
Returns the path of the directory containing the current script, if available.

## script_path

```kototype
String or Null
|| -> String or Null
```

If a script is being executed then `script_path` provides the path of the
current script as a String, otherwise `script_path` is Null.
Returns the path of the file containing the current script, if available.

## size

Expand Down
21 changes: 8 additions & 13 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ use crossterm::tty::IsTty;
use koto::prelude::*;
use repl::{Repl, ReplSettings};
use rustyline::EditMode;
use std::{
env,
error::Error,
fs, io,
path::{Path, PathBuf},
};
use std::{env, error::Error, fs, io, path::PathBuf};

#[global_allocator]
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
Expand Down Expand Up @@ -139,7 +134,6 @@ fn main() -> Result<()> {
run_import_tests: args.run_import_tests,
..Default::default()
},
..Default::default()
};

let mut stdin = io::stdin();
Expand Down Expand Up @@ -167,13 +161,14 @@ fn main() -> Result<()> {

if let Some(script) = script {
let mut koto = Koto::with_settings(koto_settings);
if let Err(error) = koto.set_script_path(script_path.as_deref().map(Path::new)) {
bail!("{error}");
}

add_modules(&koto);

match koto.compile(&script) {
match koto.compile(CompileArgs {
script: &script,
script_path: script_path.map(KString::from),
compiler_settings: Default::default(),
}) {
Ok(chunk) => {
if args.show_bytecode {
println!("{}\n", &Chunk::bytes_as_string(&chunk));
Expand Down Expand Up @@ -266,10 +261,10 @@ fn load_config(config_path: Option<&String>) -> Result<Config> {

// Load the config file if it exists
if let Some(config_path) = config_path {
let script = fs::read_to_string(config_path).context("Failed to load the config file")?;
let script = fs::read_to_string(&config_path).context("Failed to load the config file")?;

let mut koto = Koto::new();
match koto.compile_and_run(&script) {
match koto.compile_and_run(CompileArgs::with_path(&script, config_path)) {
Ok(_) => {
let exports = koto.exports().data();
match exports.get("repl") {
Expand Down
7 changes: 1 addition & 6 deletions crates/cli/src/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,7 @@ fn history_path() -> Option<PathBuf> {
}

impl Repl {
pub fn with_settings(
repl_settings: ReplSettings,
mut koto_settings: KotoSettings,
) -> Result<Self> {
koto_settings.export_top_level_ids = true;

pub fn with_settings(repl_settings: ReplSettings, koto_settings: KotoSettings) -> Result<Self> {
let koto = Koto::with_settings(koto_settings);
super::add_modules(&koto);

Expand Down
2 changes: 1 addition & 1 deletion crates/koto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ koto_bytecode = { path = "../bytecode", version = "^0.15.0", default-features =
koto_parser = { path = "../parser", version = "^0.15.0", default-features = false }
koto_runtime = { path = "../runtime", version = "^0.15.0", default-features = false }

dunce = { workspace = true }
thiserror = { workspace = true }

[dev-dependencies]
Expand All @@ -28,6 +27,7 @@ koto_test_utils = { path = "../test_utils" }

anyhow = { workspace = true }
criterion2 = { workspace = true }
dunce = { workspace = true }
mimalloc = { workspace = true }

[[bench]]
Expand Down
2 changes: 1 addition & 1 deletion crates/koto/examples/poetry/scripts/guide.koto
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@main = ||
input_file =
io.extend_path koto.script_dir, '..', 'README.md'
io.extend_path koto.script_dir(), '..', 'README.md'
-> io.read_to_string
generator = poetry.new input_file

Expand Down
2 changes: 1 addition & 1 deletion crates/koto/examples/poetry/scripts/readme.koto
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@main = ||
input_file =
io.extend_path koto.script_dir, '..', '..', '..', '..', '..', 'docs', 'language_guide.md'
io.extend_path koto.script_dir(), '..', '..', '..', '..', '..', 'docs', 'language_guide.md'
-> io.read_to_string
generator = poetry.new input_file

Expand Down
Loading
Loading