Factor out a modules module and add main.rs (#5329)
* Factor out a modules module Signed-off-by: Nick Cameron <nrc@ncameron.org> * Very simple cargo run Signed-off-by: Nick Cameron <nrc@ncameron.org> --------- Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
@ -5,7 +5,8 @@ use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
|
||||
use crate::{
|
||||
execution::{ArtifactCommand, ArtifactGraph, Operation},
|
||||
lsp::IntoDiagnostic,
|
||||
source_range::{ModuleId, SourceRange},
|
||||
source_range::SourceRange,
|
||||
ModuleId,
|
||||
};
|
||||
|
||||
/// How did the KCL execution fail
|
||||
|
||||
@ -10,16 +10,17 @@ use crate::{
|
||||
annotations,
|
||||
cad_op::{OpArg, Operation},
|
||||
state::ModuleState,
|
||||
BodyType, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ModulePath, ModuleRepr,
|
||||
ProgramMemory, TagEngineInfo, TagIdentifier,
|
||||
BodyType, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory, TagEngineInfo,
|
||||
TagIdentifier,
|
||||
},
|
||||
modules::{ModuleId, ModulePath, ModuleRepr},
|
||||
parsing::ast::types::{
|
||||
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
|
||||
CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector, ItemVisibility,
|
||||
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NodeRef, NonCodeValue, ObjectExpression,
|
||||
PipeExpression, TagDeclarator, UnaryExpression, UnaryOperator,
|
||||
},
|
||||
source_range::{ModuleId, SourceRange},
|
||||
source_range::SourceRange,
|
||||
std::{
|
||||
args::{Arg, KwArgs},
|
||||
FunctionKind,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
//! The executor for the AST.
|
||||
|
||||
use std::{fmt, path::PathBuf, sync::Arc};
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
|
||||
@ -9,7 +9,9 @@ use cache::OldAstState;
|
||||
pub use cad_op::Operation;
|
||||
pub use exec_ast::FunctionParam;
|
||||
pub use geometry::*;
|
||||
pub(crate) use import::{import_foreign, send_to_engine as send_import_to_engine, ZOO_COORD_SYSTEM};
|
||||
pub(crate) use import::{
|
||||
import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
|
||||
use kcmc::{
|
||||
@ -26,15 +28,15 @@ pub use state::{ExecState, IdGenerator, MetaSettings};
|
||||
|
||||
use crate::{
|
||||
engine::EngineManager,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
errors::KclError,
|
||||
execution::{
|
||||
artifact::build_artifact_graph,
|
||||
cache::{CacheInformation, CacheResult},
|
||||
},
|
||||
fs::{FileManager, FileSystem},
|
||||
parsing::ast::types::{Expr, FunctionExpression, ImportPath, Node, NodeRef, Program},
|
||||
fs::FileManager,
|
||||
parsing::ast::types::{Expr, FunctionExpression, Node, NodeRef, Program},
|
||||
settings::types::UnitLength,
|
||||
source_range::{ModuleId, SourceRange},
|
||||
source_range::SourceRange,
|
||||
std::{args::Arg, StdLib},
|
||||
ExecError, KclErrorWithOutputs,
|
||||
};
|
||||
@ -162,118 +164,6 @@ pub enum BodyType {
|
||||
Block,
|
||||
}
|
||||
|
||||
/// Info about a module. Right now, this is pretty minimal. We hope to cache
|
||||
/// modules here in the future.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ModuleInfo {
|
||||
/// The ID of the module.
|
||||
id: ModuleId,
|
||||
/// Absolute path of the module's source file.
|
||||
path: ModulePath,
|
||||
repr: ModuleRepr,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Hash)]
|
||||
pub enum ModulePath {
|
||||
Local(std::path::PathBuf),
|
||||
Std(String),
|
||||
}
|
||||
|
||||
impl ModulePath {
|
||||
fn expect_path(&self) -> &std::path::PathBuf {
|
||||
match self {
|
||||
ModulePath::Local(p) => p,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn source(&self, fs: &FileManager, source_range: SourceRange) -> Result<String, KclError> {
|
||||
match self {
|
||||
ModulePath::Local(p) => fs.read_to_string(p, source_range).await,
|
||||
ModulePath::Std(_) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_import_path(path: &ImportPath, project_directory: &Option<PathBuf>) -> Self {
|
||||
match path {
|
||||
ImportPath::Kcl { filename: path } | ImportPath::Foreign { path } => {
|
||||
let resolved_path = if let Some(project_dir) = project_directory {
|
||||
project_dir.join(path)
|
||||
} else {
|
||||
std::path::PathBuf::from(path)
|
||||
};
|
||||
ModulePath::Local(resolved_path)
|
||||
}
|
||||
ImportPath::Std { path } => {
|
||||
// For now we only support importing from singly-nested modules inside std.
|
||||
assert_eq!(path.len(), 2);
|
||||
assert_eq!(&path[0], "std");
|
||||
|
||||
ModulePath::Std(path[1].clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ModulePath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ModulePath::Local(path) => path.display().fmt(f),
|
||||
ModulePath::Std(s) => write!(f, "std::{s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub enum ModuleRepr {
|
||||
Root,
|
||||
Kcl(Node<Program>),
|
||||
Foreign(import::PreImportedGeometry),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModuleLoader {
|
||||
/// The stack of import statements for detecting circular module imports.
|
||||
/// If this is empty, we're not currently executing an import statement.
|
||||
pub import_stack: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl ModuleLoader {
|
||||
pub(crate) fn cycle_check(&self, path: &ModulePath, source_range: SourceRange) -> Result<(), KclError> {
|
||||
if self.import_stack.contains(path.expect_path()) {
|
||||
return Err(KclError::ImportCycle(KclErrorDetails {
|
||||
message: format!(
|
||||
"circular import of modules is not allowed: {} -> {}",
|
||||
self.import_stack
|
||||
.iter()
|
||||
.map(|p| p.as_path().to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" -> "),
|
||||
path,
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn enter_module(&mut self, path: &ModulePath) {
|
||||
if let ModulePath::Local(ref path) = path {
|
||||
self.import_stack.push(path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn leave_module(&mut self, path: &ModulePath) {
|
||||
if let ModulePath::Local(ref path) = path {
|
||||
let popped = self.import_stack.pop().unwrap();
|
||||
assert_eq!(path, &popped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq, Copy)]
|
||||
#[ts(export)]
|
||||
@ -884,7 +774,7 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
use crate::errors::KclErrorDetails;
|
||||
use crate::{errors::KclErrorDetails, ModuleId};
|
||||
|
||||
/// Convenience function to get a JSON value from memory and unwrap.
|
||||
#[track_caller]
|
||||
|
||||
@ -9,11 +9,11 @@ use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
annotations, kcl_value, Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, ExecOutcome, ExecutorSettings,
|
||||
KclValue, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr, Operation, ProgramMemory, SolidLazyIds, UnitAngle,
|
||||
UnitLen,
|
||||
KclValue, Operation, ProgramMemory, SolidLazyIds, UnitAngle, UnitLen,
|
||||
},
|
||||
modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr},
|
||||
parsing::ast::types::NonCodeValue,
|
||||
source_range::{ModuleId, SourceRange},
|
||||
source_range::SourceRange,
|
||||
};
|
||||
|
||||
/// State for executing a program.
|
||||
|
||||
@ -65,6 +65,7 @@ mod fs;
|
||||
pub mod lint;
|
||||
mod log;
|
||||
mod lsp;
|
||||
mod modules;
|
||||
mod parsing;
|
||||
mod settings;
|
||||
#[cfg(test)]
|
||||
@ -87,9 +88,10 @@ pub use lsp::{
|
||||
copilot::Backend as CopilotLspBackend,
|
||||
kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
|
||||
};
|
||||
pub use modules::ModuleId;
|
||||
pub use parsing::ast::{modify::modify_ast_for_sketch, types::FormatOptions};
|
||||
pub use settings::types::{project::ProjectConfiguration, Configuration, UnitLength};
|
||||
pub use source_range::{ModuleId, SourceRange};
|
||||
pub use source_range::SourceRange;
|
||||
|
||||
// Rather than make executor public and make lots of it pub(crate), just re-export into a new module.
|
||||
// Ideally we wouldn't export these things at all, they should only be used for testing.
|
||||
|
||||
157
src/wasm-lib/kcl/src/modules.rs
Normal file
157
src/wasm-lib/kcl/src/modules.rs
Normal file
@ -0,0 +1,157 @@
|
||||
use std::{fmt, path::PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::PreImportedGeometry,
|
||||
fs::{FileManager, FileSystem},
|
||||
parsing::ast::types::{ImportPath, Node, Program},
|
||||
source_range::SourceRange,
|
||||
};
|
||||
|
||||
/// Identifier of a source file. Uses a u32 to keep the size small.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
pub struct ModuleId(u32);
|
||||
|
||||
impl ModuleId {
|
||||
pub fn from_usize(id: usize) -> Self {
|
||||
Self(u32::try_from(id).expect("module ID should fit in a u32"))
|
||||
}
|
||||
|
||||
pub fn as_usize(&self) -> usize {
|
||||
usize::try_from(self.0).expect("module ID should fit in a usize")
|
||||
}
|
||||
|
||||
/// Top-level file is the one being executed.
|
||||
/// Represented by module ID of 0, i.e. the default value.
|
||||
pub fn is_top_level(&self) -> bool {
|
||||
*self == Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModuleLoader {
|
||||
/// The stack of import statements for detecting circular module imports.
|
||||
/// If this is empty, we're not currently executing an import statement.
|
||||
pub import_stack: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl ModuleLoader {
|
||||
pub(crate) fn cycle_check(&self, path: &ModulePath, source_range: SourceRange) -> Result<(), KclError> {
|
||||
if self.import_stack.contains(path.expect_path()) {
|
||||
return Err(KclError::ImportCycle(KclErrorDetails {
|
||||
message: format!(
|
||||
"circular import of modules is not allowed: {} -> {}",
|
||||
self.import_stack
|
||||
.iter()
|
||||
.map(|p| p.as_path().to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" -> "),
|
||||
path,
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn enter_module(&mut self, path: &ModulePath) {
|
||||
if let ModulePath::Local(ref path) = path {
|
||||
self.import_stack.push(path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn leave_module(&mut self, path: &ModulePath) {
|
||||
if let ModulePath::Local(ref path) = path {
|
||||
let popped = self.import_stack.pop().unwrap();
|
||||
assert_eq!(path, &popped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn read_std(_mod_name: &str) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Info about a module. Right now, this is pretty minimal. We hope to cache
|
||||
/// modules here in the future.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ModuleInfo {
|
||||
/// The ID of the module.
|
||||
pub(crate) id: ModuleId,
|
||||
/// Absolute path of the module's source file.
|
||||
pub(crate) path: ModulePath,
|
||||
pub(crate) repr: ModuleRepr,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub enum ModuleRepr {
|
||||
Root,
|
||||
Kcl(Node<Program>),
|
||||
Foreign(PreImportedGeometry),
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Hash)]
|
||||
pub enum ModulePath {
|
||||
Local(PathBuf),
|
||||
Std(String),
|
||||
}
|
||||
|
||||
impl ModulePath {
|
||||
pub(crate) fn expect_path(&self) -> &PathBuf {
|
||||
match self {
|
||||
ModulePath::Local(p) => p,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn source(&self, fs: &FileManager, source_range: SourceRange) -> Result<String, KclError> {
|
||||
match self {
|
||||
ModulePath::Local(p) => fs.read_to_string(p, source_range).await,
|
||||
ModulePath::Std(name) => read_std(name)
|
||||
.ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Cannot find standard library module to import: std::{name}."),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})
|
||||
.map(str::to_owned),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_import_path(path: &ImportPath, project_directory: &Option<PathBuf>) -> Self {
|
||||
match path {
|
||||
ImportPath::Kcl { filename: path } | ImportPath::Foreign { path } => {
|
||||
let resolved_path = if let Some(project_dir) = project_directory {
|
||||
project_dir.join(path)
|
||||
} else {
|
||||
std::path::PathBuf::from(path)
|
||||
};
|
||||
ModulePath::Local(resolved_path)
|
||||
}
|
||||
ImportPath::Std { path } => {
|
||||
// For now we only support importing from singly-nested modules inside std.
|
||||
assert_eq!(path.len(), 2);
|
||||
assert_eq!(&path[0], "std");
|
||||
|
||||
ModulePath::Std(path[1].clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ModulePath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ModulePath::Local(path) => path.display().fmt(f),
|
||||
ModulePath::Std(s) => write!(f, "std::{s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@ pub mod types;
|
||||
|
||||
use crate::{
|
||||
parsing::ast::types::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject},
|
||||
source_range::ModuleId,
|
||||
ModuleId,
|
||||
};
|
||||
|
||||
impl BodyItem {
|
||||
|
||||
@ -15,8 +15,8 @@ use crate::{
|
||||
ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, Node, PipeExpression,
|
||||
PipeSubstitution, VariableDeclarator,
|
||||
},
|
||||
source_range::{ModuleId, SourceRange},
|
||||
Program,
|
||||
source_range::SourceRange,
|
||||
ModuleId, Program,
|
||||
};
|
||||
|
||||
type Point3d = kcmc::shared::Point3d<f64>;
|
||||
|
||||
@ -28,7 +28,8 @@ use crate::{
|
||||
execution::{annotations, KclValue, Metadata, TagIdentifier},
|
||||
parsing::{ast::digest::Digest, PIPE_OPERATOR},
|
||||
pretty::NumericSuffix,
|
||||
source_range::{ModuleId, SourceRange},
|
||||
source_range::SourceRange,
|
||||
ModuleId,
|
||||
};
|
||||
|
||||
mod condition;
|
||||
|
||||
@ -131,7 +131,7 @@ mod tests {
|
||||
ast::types::{Literal, LiteralValue},
|
||||
token::NumericSuffix,
|
||||
},
|
||||
source_range::ModuleId,
|
||||
ModuleId,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
||||
@ -4,7 +4,8 @@ use crate::{
|
||||
ast::types::{Node, Program},
|
||||
token::TokenStream,
|
||||
},
|
||||
source_range::{ModuleId, SourceRange},
|
||||
source_range::SourceRange,
|
||||
ModuleId,
|
||||
};
|
||||
|
||||
pub(crate) mod ast;
|
||||
|
||||
@ -18,8 +18,8 @@ use winnow::{
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
parsing::ast::types::{ItemVisibility, VariableKind},
|
||||
source_range::{ModuleId, SourceRange},
|
||||
CompilationError,
|
||||
source_range::SourceRange,
|
||||
CompilationError, ModuleId,
|
||||
};
|
||||
|
||||
mod tokeniser;
|
||||
|
||||
@ -13,7 +13,7 @@ use winnow::{
|
||||
use super::TokenStream;
|
||||
use crate::{
|
||||
parsing::token::{Token, TokenType},
|
||||
source_range::ModuleId,
|
||||
ModuleId,
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
|
||||
@ -7,7 +7,7 @@ use crate::{
|
||||
exec::ArtifactCommand,
|
||||
execution::{ArtifactGraph, Operation},
|
||||
parsing::ast::types::{Node, Program},
|
||||
source_range::ModuleId,
|
||||
ModuleId,
|
||||
};
|
||||
|
||||
/// Deserialize the data from a snapshot.
|
||||
|
||||
@ -2,26 +2,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
|
||||
|
||||
/// Identifier of a source file. Uses a u32 to keep the size small.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
pub struct ModuleId(u32);
|
||||
|
||||
impl ModuleId {
|
||||
pub fn from_usize(id: usize) -> Self {
|
||||
Self(u32::try_from(id).expect("module ID should fit in a u32"))
|
||||
}
|
||||
|
||||
pub fn as_usize(&self) -> usize {
|
||||
usize::try_from(self.0).expect("module ID should fit in a usize")
|
||||
}
|
||||
|
||||
/// Top-level file is the one being executed.
|
||||
/// Represented by module ID of 0, i.e. the default value.
|
||||
pub fn is_top_level(&self) -> bool {
|
||||
*self == Self::default()
|
||||
}
|
||||
}
|
||||
use crate::modules::ModuleId;
|
||||
|
||||
/// The first two items are the start and end points (byte offsets from the start of the file).
|
||||
/// The third item is whether the source range belongs to the 'main' file, i.e., the file currently
|
||||
|
||||
@ -795,7 +795,7 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
use crate::{parsing::ast::types::FormatOptions, source_range::ModuleId};
|
||||
use crate::{parsing::ast::types::FormatOptions, ModuleId};
|
||||
|
||||
#[test]
|
||||
fn test_recast_if_else_if_same() {
|
||||
|
||||
41
src/wasm-lib/src/main.rs
Normal file
41
src/wasm-lib/src/main.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use std::{env, fs::File, io::Read};
|
||||
|
||||
use kcl_lib::{ExecState, ExecutorContext, ExecutorSettings, Program};
|
||||
|
||||
// An extremely simple script, definitely not to be released or used for anything important, but
|
||||
// sometimes useful for debugging. It reads in a file specified on the command line and runs it.
|
||||
// It will report any errors in a developer-oriented way and discard the result.
|
||||
//
|
||||
// e.g., `cargo run -- foo.kcl`
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let mut args = env::args();
|
||||
args.next();
|
||||
let filename = args.next().unwrap_or_else(|| "main.kcl".to_owned());
|
||||
|
||||
let mut f = File::open(&filename).unwrap();
|
||||
let mut text = String::new();
|
||||
f.read_to_string(&mut text).unwrap();
|
||||
|
||||
let (program, errs) = Program::parse(&text).unwrap();
|
||||
if !errs.is_empty() {
|
||||
for e in errs {
|
||||
eprintln!("{e:#?}");
|
||||
}
|
||||
}
|
||||
let program = program.unwrap();
|
||||
|
||||
let project_directory = filename.rfind('/').map(|i| filename[..i].into());
|
||||
let ctx = ExecutorContext::new_with_client(
|
||||
ExecutorSettings {
|
||||
project_directory,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut exec_state = ExecState::new(&ctx.settings);
|
||||
ctx.run(&program, &mut exec_state).await.unwrap();
|
||||
}
|
||||
Reference in New Issue
Block a user