Compare commits
11 Commits
nested_dir
...
paultag/fa
Author | SHA1 | Date | |
---|---|---|---|
3f376b3762 | |||
b80a18fffb | |||
71e1556b2b | |||
7b7ed0adb7 | |||
32e907c099 | |||
361adc4043 | |||
f05b640a62 | |||
213dcf56ea | |||
a197261a3c | |||
a7ce2ddc9b | |||
3afebd80af |
@ -4,7 +4,7 @@ use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
use kcl_lib::{
|
||||
exec::{ArtifactCommand, DefaultPlanes, IdGenerator},
|
||||
ExecutionKind, KclError,
|
||||
KclError,
|
||||
};
|
||||
use kittycad_modeling_cmds::{
|
||||
self as kcmc,
|
||||
@ -25,7 +25,6 @@ pub struct EngineConnection {
|
||||
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>>,
|
||||
core_test: Arc<RwLock<String>>,
|
||||
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
||||
execution_kind: Arc<RwLock<ExecutionKind>>,
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
@ -37,7 +36,6 @@ impl EngineConnection {
|
||||
batch_end: Arc::new(RwLock::new(IndexMap::new())),
|
||||
core_test: result,
|
||||
default_planes: Default::default(),
|
||||
execution_kind: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -373,18 +371,6 @@ impl kcl_lib::EngineManager for EngineConnection {
|
||||
Arc::new(RwLock::new(Vec::new()))
|
||||
}
|
||||
|
||||
async fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.read().await;
|
||||
*guard
|
||||
}
|
||||
|
||||
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.write().await;
|
||||
let original = *guard;
|
||||
*guard = execution_kind;
|
||||
original
|
||||
}
|
||||
|
||||
async fn default_planes(
|
||||
&self,
|
||||
id_generator: &mut IdGenerator,
|
||||
|
@ -18,7 +18,6 @@ use tokio::sync::{mpsc, oneshot, RwLock};
|
||||
use tokio_tungstenite::tungstenite::Message as WsMsg;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::ExecutionKind;
|
||||
use crate::{
|
||||
engine::EngineManager,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
@ -50,8 +49,6 @@ pub struct EngineConnection {
|
||||
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
||||
/// If the server sends session data, it'll be copied to here.
|
||||
session_data: Arc<RwLock<Option<ModelingSessionData>>>,
|
||||
|
||||
execution_kind: Arc<RwLock<ExecutionKind>>,
|
||||
}
|
||||
|
||||
pub struct TcpRead {
|
||||
@ -343,7 +340,6 @@ impl EngineConnection {
|
||||
artifact_commands: Arc::new(RwLock::new(Vec::new())),
|
||||
default_planes: Default::default(),
|
||||
session_data,
|
||||
execution_kind: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -366,18 +362,6 @@ impl EngineManager for EngineConnection {
|
||||
self.artifact_commands.clone()
|
||||
}
|
||||
|
||||
async fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.read().await;
|
||||
*guard
|
||||
}
|
||||
|
||||
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.write().await;
|
||||
let original = *guard;
|
||||
*guard = execution_kind;
|
||||
original
|
||||
}
|
||||
|
||||
async fn default_planes(
|
||||
&self,
|
||||
id_generator: &mut IdGenerator,
|
||||
|
@ -16,7 +16,6 @@ use kittycad_modeling_cmds::{self as kcmc};
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::ExecutionKind;
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
exec::DefaultPlanes,
|
||||
@ -29,7 +28,6 @@ pub struct EngineConnection {
|
||||
batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
|
||||
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
|
||||
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
|
||||
execution_kind: Arc<RwLock<ExecutionKind>>,
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
@ -38,7 +36,6 @@ impl EngineConnection {
|
||||
batch: Arc::new(RwLock::new(Vec::new())),
|
||||
batch_end: Arc::new(RwLock::new(IndexMap::new())),
|
||||
artifact_commands: Arc::new(RwLock::new(Vec::new())),
|
||||
execution_kind: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -61,18 +58,6 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
self.artifact_commands.clone()
|
||||
}
|
||||
|
||||
async fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.read().await;
|
||||
*guard
|
||||
}
|
||||
|
||||
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.write().await;
|
||||
let original = *guard;
|
||||
*guard = execution_kind;
|
||||
original
|
||||
}
|
||||
|
||||
async fn default_planes(
|
||||
&self,
|
||||
_id_generator: &mut IdGenerator,
|
||||
|
@ -11,7 +11,6 @@ use uuid::Uuid;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{
|
||||
engine::ExecutionKind,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{ArtifactCommand, DefaultPlanes, IdGenerator},
|
||||
SourceRange,
|
||||
@ -48,7 +47,6 @@ pub struct EngineConnection {
|
||||
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
|
||||
responses: Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>,
|
||||
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
|
||||
execution_kind: Arc<RwLock<ExecutionKind>>,
|
||||
}
|
||||
|
||||
// Safety: WebAssembly will only ever run in a single-threaded context.
|
||||
@ -64,7 +62,6 @@ impl EngineConnection {
|
||||
batch_end: Arc::new(RwLock::new(IndexMap::new())),
|
||||
responses: Arc::new(RwLock::new(IndexMap::new())),
|
||||
artifact_commands: Arc::new(RwLock::new(Vec::new())),
|
||||
execution_kind: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -148,18 +145,6 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
self.artifact_commands.clone()
|
||||
}
|
||||
|
||||
async fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.read().await;
|
||||
*guard
|
||||
}
|
||||
|
||||
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.write().await;
|
||||
let original = *guard;
|
||||
*guard = execution_kind;
|
||||
original
|
||||
}
|
||||
|
||||
async fn default_planes(
|
||||
&self,
|
||||
_id_generator: &mut IdGenerator,
|
||||
@ -243,11 +228,6 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
.do_send_modeling_cmd(id, source_range, cmd, id_to_source_range)
|
||||
.await?;
|
||||
|
||||
// In isolated mode, we don't save the response.
|
||||
if self.execution_kind().await.is_isolated() {
|
||||
return Ok(ws_result);
|
||||
}
|
||||
|
||||
let mut responses = self.responses.write().await;
|
||||
responses.insert(id, ws_result.clone());
|
||||
drop(responses);
|
||||
|
@ -41,23 +41,6 @@ lazy_static::lazy_static! {
|
||||
pub static ref GRID_SCALE_TEXT_OBJECT_ID: uuid::Uuid = uuid::Uuid::parse_str("10782f33-f588-4668-8bcd-040502d26590").unwrap();
|
||||
}
|
||||
|
||||
/// The mode of execution. When isolated, like during an import, attempting to
|
||||
/// send a command results in an error.
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ExecutionKind {
|
||||
#[default]
|
||||
Normal,
|
||||
Isolated,
|
||||
}
|
||||
|
||||
impl ExecutionKind {
|
||||
pub fn is_isolated(&self) -> bool {
|
||||
matches!(self, ExecutionKind::Isolated)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
/// Get the batch of commands to be sent to the engine.
|
||||
@ -87,13 +70,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
std::mem::take(&mut *self.responses().write().await)
|
||||
}
|
||||
|
||||
/// Get the current execution kind.
|
||||
async fn execution_kind(&self) -> ExecutionKind;
|
||||
|
||||
/// Replace the current execution kind with a new value and return the
|
||||
/// existing value.
|
||||
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind;
|
||||
|
||||
/// Get the default planes.
|
||||
async fn default_planes(
|
||||
&self,
|
||||
@ -227,11 +203,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
source_range: SourceRange,
|
||||
cmd: &ModelingCmd,
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
// In isolated mode, we don't send the command to the engine.
|
||||
if self.execution_kind().await.is_isolated() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let req = WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
|
||||
cmd: cmd.clone(),
|
||||
cmd_id: id.into(),
|
||||
@ -252,11 +223,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
source_range: SourceRange,
|
||||
cmd: &ModelingCmd,
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
// In isolated mode, we don't send the command to the engine.
|
||||
if self.execution_kind().await.is_isolated() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let req = WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
|
||||
cmd: cmd.clone(),
|
||||
cmd_id: id.into(),
|
||||
|
@ -3,7 +3,6 @@ use std::collections::HashMap;
|
||||
use async_recursion::async_recursion;
|
||||
|
||||
use crate::{
|
||||
engine::ExecutionKind,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
annotations,
|
||||
@ -57,7 +56,7 @@ impl ExecutorContext {
|
||||
let old_units = exec_state.length_unit();
|
||||
exec_state.mod_local.settings.update_from_annotation(annotation)?;
|
||||
let new_units = exec_state.length_unit();
|
||||
if !self.engine.execution_kind().await.is_isolated() && old_units != new_units {
|
||||
if old_units != new_units {
|
||||
self.engine
|
||||
.set_units(new_units.into(), annotation.as_source_range())
|
||||
.await?;
|
||||
@ -112,10 +111,8 @@ impl ExecutorContext {
|
||||
prelude_range,
|
||||
)
|
||||
.await?;
|
||||
let (module_memory, module_exports) = self
|
||||
.exec_module_for_items(id, exec_state, ExecutionKind::Isolated, prelude_range)
|
||||
.await
|
||||
.unwrap();
|
||||
let (module_memory, module_exports) =
|
||||
self.exec_module_for_items(id, exec_state, prelude_range).await.unwrap();
|
||||
for name in module_exports {
|
||||
let item = exec_state
|
||||
.memory()
|
||||
@ -146,9 +143,8 @@ impl ExecutorContext {
|
||||
|
||||
match &import_stmt.selector {
|
||||
ImportSelector::List { items } => {
|
||||
let (env_ref, module_exports) = self
|
||||
.exec_module_for_items(module_id, exec_state, ExecutionKind::Isolated, source_range)
|
||||
.await?;
|
||||
let (env_ref, module_exports) =
|
||||
self.exec_module_for_items(module_id, exec_state, source_range).await?;
|
||||
for import_item in items {
|
||||
// Extract the item from the module.
|
||||
let item = exec_state
|
||||
@ -188,9 +184,8 @@ impl ExecutorContext {
|
||||
}
|
||||
}
|
||||
ImportSelector::Glob(_) => {
|
||||
let (env_ref, module_exports) = self
|
||||
.exec_module_for_items(module_id, exec_state, ExecutionKind::Isolated, source_range)
|
||||
.await?;
|
||||
let (env_ref, module_exports) =
|
||||
self.exec_module_for_items(module_id, exec_state, source_range).await?;
|
||||
for name in module_exports.iter() {
|
||||
let item = exec_state
|
||||
.memory()
|
||||
@ -361,7 +356,6 @@ impl ExecutorContext {
|
||||
&self,
|
||||
module_id: ModuleId,
|
||||
exec_state: &mut ExecState,
|
||||
exec_kind: ExecutionKind,
|
||||
source_range: SourceRange,
|
||||
) -> Result<(EnvironmentRef, Vec<String>), KclError> {
|
||||
let path = exec_state.global.module_infos[&module_id].path.clone();
|
||||
@ -372,7 +366,7 @@ impl ExecutorContext {
|
||||
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
|
||||
ModuleRepr::Kcl(_, Some((env_ref, items))) => Ok((*env_ref, items.clone())),
|
||||
ModuleRepr::Kcl(program, cache) => self
|
||||
.exec_module_from_ast(program, &path, exec_state, exec_kind, source_range)
|
||||
.exec_module_from_ast(program, &path, exec_state, source_range)
|
||||
.await
|
||||
.map(|(_, er, items)| {
|
||||
*cache = Some((er, items.clone()));
|
||||
@ -393,7 +387,6 @@ impl ExecutorContext {
|
||||
&self,
|
||||
module_id: ModuleId,
|
||||
exec_state: &mut ExecState,
|
||||
exec_kind: ExecutionKind,
|
||||
source_range: SourceRange,
|
||||
) -> Result<Option<KclValue>, KclError> {
|
||||
let path = exec_state.global.module_infos[&module_id].path.clone();
|
||||
@ -403,7 +396,7 @@ impl ExecutorContext {
|
||||
let result = match &repr {
|
||||
ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
|
||||
ModuleRepr::Kcl(program, _) => self
|
||||
.exec_module_from_ast(program, &path, exec_state, exec_kind, source_range)
|
||||
.exec_module_from_ast(program, &path, exec_state, source_range)
|
||||
.await
|
||||
.map(|(val, _, _)| val),
|
||||
ModuleRepr::Foreign(geom) => super::import::send_to_engine(geom.clone(), self)
|
||||
@ -421,7 +414,6 @@ impl ExecutorContext {
|
||||
program: &Node<Program>,
|
||||
path: &ModulePath,
|
||||
exec_state: &mut ExecState,
|
||||
exec_kind: ExecutionKind,
|
||||
source_range: SourceRange,
|
||||
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
|
||||
let old_units = exec_state.length_unit();
|
||||
@ -429,7 +421,6 @@ impl ExecutorContext {
|
||||
exec_state.global.mod_loader.enter_module(path);
|
||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||
exec_state.mut_memory().push_new_root_env();
|
||||
let original_execution = self.engine.replace_execution_kind(exec_kind).await;
|
||||
|
||||
let result = self
|
||||
.exec_program(program, exec_state, crate::execution::BodyType::Root(true))
|
||||
@ -439,10 +430,9 @@ impl ExecutorContext {
|
||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||
let env_ref = exec_state.mut_memory().pop_env();
|
||||
exec_state.global.mod_loader.leave_module(path);
|
||||
if !exec_kind.is_isolated() && new_units != old_units {
|
||||
if new_units != old_units {
|
||||
self.engine.set_units(old_units.into(), Default::default()).await?;
|
||||
}
|
||||
self.engine.replace_execution_kind(original_execution).await;
|
||||
|
||||
result
|
||||
.map_err(|err| {
|
||||
@ -479,7 +469,7 @@ impl ExecutorContext {
|
||||
Expr::Identifier(identifier) => {
|
||||
let value = exec_state.memory().get(&identifier.name, identifier.into())?.clone();
|
||||
if let KclValue::Module { value: module_id, meta } = value {
|
||||
self.exec_module_for_result(module_id, exec_state, ExecutionKind::Normal, metadata.source_range)
|
||||
self.exec_module_for_result(module_id, exec_state, metadata.source_range)
|
||||
.await?
|
||||
.unwrap_or_else(|| {
|
||||
// The module didn't have a return value. Currently,
|
||||
|
@ -475,7 +475,7 @@ impl ExecutorContext {
|
||||
|
||||
/// Returns true if we should not send engine commands for any reason.
|
||||
pub async fn no_engine_commands(&self) -> bool {
|
||||
self.is_mock() || self.engine.execution_kind().await.is_isolated()
|
||||
self.is_mock()
|
||||
}
|
||||
|
||||
pub async fn send_clear_scene(
|
||||
|
@ -81,7 +81,7 @@ pub mod walk;
|
||||
mod wasm;
|
||||
|
||||
pub use coredump::CoreDump;
|
||||
pub use engine::{EngineManager, ExecutionKind};
|
||||
pub use engine::EngineManager;
|
||||
pub use errors::{CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs};
|
||||
pub use execution::{
|
||||
bust_cache, clear_mem_cache, ExecOutcome, ExecState, ExecutorContext, ExecutorSettings, MetaSettings, Point2d,
|
||||
|
223
src/wasm-lib/kcl/src/walk/import_graph.rs
Normal file
223
src/wasm-lib/kcl/src/walk/import_graph.rs
Normal file
@ -0,0 +1,223 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
parsing::ast::types::{ImportPath, NodeRef, Program},
|
||||
walk::{Node, Visitable},
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
/// Specific dependency between two modules. The 0th element of this tuple
|
||||
/// is the "importing" module, the 1st is the "imported" module. The 0th
|
||||
/// module *depends on* the 1st module.
|
||||
type Dependency = (String, String);
|
||||
|
||||
type Graph = Vec<Dependency>;
|
||||
|
||||
/// Process a number of programs, returning the graph of dependencies.
|
||||
///
|
||||
/// This will (currently) return a list of lists of IDs that can be safely
|
||||
/// run concurrently. Each "stage" is blocking in this model, which will
|
||||
/// change in the future. Don't use this function widely, yet.
|
||||
pub fn import_graph<'a>(progs: HashMap<String, NodeRef<'a, Program>>) -> Result<Vec<Vec<String>>> {
|
||||
let mut graph = Graph::new();
|
||||
|
||||
for (name, program) in progs.iter() {
|
||||
graph.extend(
|
||||
import_dependencies(program)?
|
||||
.into_iter()
|
||||
.map(|dependency| (name.clone(), dependency))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
let all_modules: Vec<&str> = progs.keys().map(|v| v.as_str()).collect();
|
||||
topsort(&all_modules, graph)
|
||||
}
|
||||
|
||||
fn topsort(all_modules: &[&str], graph: Graph) -> Result<Vec<Vec<String>>> {
|
||||
let mut dep_map = HashMap::<String, Vec<String>>::new();
|
||||
|
||||
for (dependent, dependency) in graph.iter() {
|
||||
let mut dependencies = dep_map.remove(dependent).unwrap_or(vec![]);
|
||||
dependencies.push(dependency.to_owned());
|
||||
dep_map.insert(dependent.to_owned(), dependencies);
|
||||
}
|
||||
|
||||
// dep_map now contains reverse dependencies. For each module, it's a
|
||||
// list of what things are "waiting on it". A non-empty value for a key
|
||||
// means it's currently blocked.
|
||||
|
||||
let mut waiting_modules = all_modules.to_owned();
|
||||
let mut order = vec![];
|
||||
|
||||
loop {
|
||||
// Each pass through we need to find any modules which have nothing
|
||||
// "pointing at it" -- so-called reverse dependencies. This is an entry
|
||||
// that is either not in the dep_map OR an empty list.
|
||||
|
||||
let mut stage_modules: Vec<String> = vec![];
|
||||
|
||||
for module in &waiting_modules {
|
||||
let module = module.to_string();
|
||||
if dep_map.get(&module).map(|v| v.len()).unwrap_or(0) == 0 {
|
||||
// if it's None or empty, this is a node that we can process,
|
||||
// and remove from the graph.
|
||||
stage_modules.push(module.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
for stage_module in &stage_modules {
|
||||
// remove the ready-to-run module from the waiting list
|
||||
waiting_modules.retain(|v| *v != stage_module.as_str());
|
||||
|
||||
// remove any dependencies for the next run
|
||||
for (_, waiting_for) in dep_map.iter_mut() {
|
||||
waiting_for.retain(|v| v != stage_module);
|
||||
}
|
||||
}
|
||||
|
||||
if stage_modules.is_empty() {
|
||||
anyhow::bail!("imports are acyclic");
|
||||
}
|
||||
|
||||
// not strictly needed here, but perhaps helpful to avoid thinking
|
||||
// there's any implied ordering as well as helping to make tests
|
||||
// easier.
|
||||
stage_modules.sort();
|
||||
|
||||
order.push(stage_modules);
|
||||
|
||||
if waiting_modules.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(order)
|
||||
}
|
||||
|
||||
pub(crate) fn import_dependencies<'a>(prog: NodeRef<'a, Program>) -> Result<Vec<String>> {
|
||||
let ret = Arc::new(Mutex::new(vec![]));
|
||||
|
||||
fn walk<'tree>(ret: Arc<Mutex<Vec<String>>>, node: Node<'tree>) {
|
||||
if let Node::ImportStatement(is) = node {
|
||||
let dependency = match &is.path {
|
||||
ImportPath::Kcl { filename } => filename.to_string(),
|
||||
ImportPath::Foreign { path } => path.to_string(),
|
||||
ImportPath::Std { path } => path.join("::"),
|
||||
};
|
||||
|
||||
ret.lock().unwrap().push(dependency);
|
||||
}
|
||||
for child in node.children().iter() {
|
||||
walk(ret.clone(), *child)
|
||||
}
|
||||
}
|
||||
|
||||
walk(ret.clone(), prog.into());
|
||||
|
||||
let ret = ret.lock().unwrap().clone();
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! kcl {
|
||||
( $kcl:expr ) => {{
|
||||
$crate::parsing::top_level_parse($kcl).unwrap()
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn order_imports() {
|
||||
let mut modules = HashMap::new();
|
||||
|
||||
let a = kcl!("");
|
||||
modules.insert("a.kcl".to_owned(), &a);
|
||||
|
||||
let b = kcl!(
|
||||
"
|
||||
import \"a.kcl\"
|
||||
"
|
||||
);
|
||||
modules.insert("b.kcl".to_owned(), &b);
|
||||
|
||||
let order = import_graph(modules).unwrap();
|
||||
assert_eq!(vec![vec!["a.kcl".to_owned()], vec!["b.kcl".to_owned()]], order);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn order_imports_none() {
|
||||
let mut modules = HashMap::new();
|
||||
|
||||
let a = kcl!(
|
||||
"
|
||||
y = 2
|
||||
"
|
||||
);
|
||||
modules.insert("a.kcl".to_owned(), &a);
|
||||
|
||||
let b = kcl!(
|
||||
"
|
||||
x = 1
|
||||
"
|
||||
);
|
||||
modules.insert("b.kcl".to_owned(), &b);
|
||||
|
||||
let order = import_graph(modules).unwrap();
|
||||
assert_eq!(vec![vec!["a.kcl".to_owned(), "b.kcl".to_owned()]], order);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn order_imports_2() {
|
||||
let mut modules = HashMap::new();
|
||||
|
||||
let a = kcl!("");
|
||||
modules.insert("a.kcl".to_owned(), &a);
|
||||
|
||||
let b = kcl!(
|
||||
"
|
||||
import \"a.kcl\"
|
||||
"
|
||||
);
|
||||
modules.insert("b.kcl".to_owned(), &b);
|
||||
|
||||
let c = kcl!(
|
||||
"
|
||||
import \"a.kcl\"
|
||||
"
|
||||
);
|
||||
modules.insert("c.kcl".to_owned(), &c);
|
||||
|
||||
let order = import_graph(modules).unwrap();
|
||||
assert_eq!(
|
||||
vec![vec!["a.kcl".to_owned()], vec!["b.kcl".to_owned(), "c.kcl".to_owned()]],
|
||||
order
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn order_imports_cycle() {
|
||||
let mut modules = HashMap::new();
|
||||
|
||||
let a = kcl!(
|
||||
"
|
||||
import \"b.kcl\"
|
||||
"
|
||||
);
|
||||
modules.insert("a.kcl".to_owned(), &a);
|
||||
|
||||
let b = kcl!(
|
||||
"
|
||||
import \"a.kcl\"
|
||||
"
|
||||
);
|
||||
modules.insert("b.kcl".to_owned(), &b);
|
||||
|
||||
assert!(import_graph(modules).is_err());
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
mod ast_node;
|
||||
mod ast_visitor;
|
||||
mod ast_walk;
|
||||
mod import_graph;
|
||||
|
||||
pub use ast_node::Node;
|
||||
pub use ast_visitor::{Visitable, Visitor};
|
||||
pub use ast_walk::walk;
|
||||
pub use import_graph::import_graph;
|
||||
|
Reference in New Issue
Block a user