Move the wasm lib, and cleanup rust directory and all references (#5585)
* git mv src/wasm-lib rust Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv wasm-lib to workspace Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv kcl-lib Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv derive docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * resolve file paths Signed-off-by: Jess Frazelle <github@jessfraz.com> * clippy Signed-off-by: Jess Frazelle <github@jessfraz.com> * move more shit Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix more paths Signed-off-by: Jess Frazelle <github@jessfraz.com> * make yarn build:wasm work Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix scripts Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> * better references Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix cargo ci Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix reference Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix more ci Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * cargo sort Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix script Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix Signed-off-by: Jess Frazelle <github@jessfraz.com> * fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix a dep Signed-off-by: Jess Frazelle <github@jessfraz.com> * sort Signed-off-by: Jess Frazelle <github@jessfraz.com> * remove unused deps Signed-off-by: Jess Frazelle <github@jessfraz.com> * Revert "remove unused deps" This reverts commit fbabdb062e275fd5cbc1476f8480a1afee15d972. * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * deps; Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
319
rust/kcl-lib/src/walk/ast_node.rs
Normal file
319
rust/kcl-lib/src/walk/ast_node.rs
Normal file
@ -0,0 +1,319 @@
|
||||
use crate::{
|
||||
parsing::ast::types::{self, NodeRef},
|
||||
source_range::SourceRange,
|
||||
};
|
||||
|
||||
/// The "Node" type wraps all the AST elements we're able to find in a KCL
|
||||
/// file. Tokens we walk through will be one of these.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Node<'a> {
|
||||
Program(NodeRef<'a, types::Program>),
|
||||
|
||||
ImportStatement(NodeRef<'a, types::ImportStatement>),
|
||||
ExpressionStatement(NodeRef<'a, types::ExpressionStatement>),
|
||||
VariableDeclaration(NodeRef<'a, types::VariableDeclaration>),
|
||||
ReturnStatement(NodeRef<'a, types::ReturnStatement>),
|
||||
|
||||
VariableDeclarator(NodeRef<'a, types::VariableDeclarator>),
|
||||
|
||||
Literal(NodeRef<'a, types::Literal>),
|
||||
TagDeclarator(NodeRef<'a, types::TagDeclarator>),
|
||||
Identifier(NodeRef<'a, types::Identifier>),
|
||||
BinaryExpression(NodeRef<'a, types::BinaryExpression>),
|
||||
FunctionExpression(NodeRef<'a, types::FunctionExpression>),
|
||||
CallExpression(NodeRef<'a, types::CallExpression>),
|
||||
CallExpressionKw(NodeRef<'a, types::CallExpressionKw>),
|
||||
PipeExpression(NodeRef<'a, types::PipeExpression>),
|
||||
PipeSubstitution(NodeRef<'a, types::PipeSubstitution>),
|
||||
ArrayExpression(NodeRef<'a, types::ArrayExpression>),
|
||||
ArrayRangeExpression(NodeRef<'a, types::ArrayRangeExpression>),
|
||||
ObjectExpression(NodeRef<'a, types::ObjectExpression>),
|
||||
MemberExpression(NodeRef<'a, types::MemberExpression>),
|
||||
UnaryExpression(NodeRef<'a, types::UnaryExpression>),
|
||||
IfExpression(NodeRef<'a, types::IfExpression>),
|
||||
ElseIf(&'a types::ElseIf),
|
||||
LabelledExpression(NodeRef<'a, types::LabelledExpression>),
|
||||
Ascription(NodeRef<'a, types::Ascription>),
|
||||
|
||||
Parameter(&'a types::Parameter),
|
||||
|
||||
ObjectProperty(NodeRef<'a, types::ObjectProperty>),
|
||||
|
||||
KclNone(&'a types::KclNone),
|
||||
}
|
||||
|
||||
impl Node<'_> {
|
||||
/// Return the digest of the [Node], pulling the underlying Digest from
|
||||
/// the AST types.
|
||||
///
|
||||
/// The Digest type may change over time.
|
||||
pub fn digest(&self) -> Option<[u8; 32]> {
|
||||
match self {
|
||||
Node::Program(n) => n.digest,
|
||||
Node::ImportStatement(n) => n.digest,
|
||||
Node::ExpressionStatement(n) => n.digest,
|
||||
Node::VariableDeclaration(n) => n.digest,
|
||||
Node::ReturnStatement(n) => n.digest,
|
||||
Node::VariableDeclarator(n) => n.digest,
|
||||
Node::Literal(n) => n.digest,
|
||||
Node::TagDeclarator(n) => n.digest,
|
||||
Node::Identifier(n) => n.digest,
|
||||
Node::BinaryExpression(n) => n.digest,
|
||||
Node::FunctionExpression(n) => n.digest,
|
||||
Node::CallExpression(n) => n.digest,
|
||||
Node::CallExpressionKw(n) => n.digest,
|
||||
Node::PipeExpression(n) => n.digest,
|
||||
Node::PipeSubstitution(n) => n.digest,
|
||||
Node::ArrayExpression(n) => n.digest,
|
||||
Node::ArrayRangeExpression(n) => n.digest,
|
||||
Node::ObjectExpression(n) => n.digest,
|
||||
Node::MemberExpression(n) => n.digest,
|
||||
Node::UnaryExpression(n) => n.digest,
|
||||
Node::Parameter(p) => p.digest,
|
||||
Node::ObjectProperty(n) => n.digest,
|
||||
Node::IfExpression(n) => n.digest,
|
||||
Node::ElseIf(n) => n.digest,
|
||||
Node::KclNone(n) => n.digest,
|
||||
Node::LabelledExpression(n) => n.digest,
|
||||
Node::Ascription(n) => n.digest,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check to see if this [Node] points to the same underlying specific
|
||||
/// borrowed object as another [Node]. This is not the same as `Eq` or
|
||||
/// even `PartialEq` -- anything that is `true` here is absolutely `Eq`,
|
||||
/// but it's possible this node is `Eq` to another with this being `false`.
|
||||
///
|
||||
/// This merely indicates that this [Node] specifically is the exact same
|
||||
/// borrowed object as [Node].
|
||||
pub fn ptr_eq(&self, other: Node) -> bool {
|
||||
unsafe { std::ptr::eq(self.ptr(), other.ptr()) }
|
||||
}
|
||||
|
||||
unsafe fn ptr(&self) -> *const () {
|
||||
match self {
|
||||
Node::Program(n) => *n as *const _ as *const (),
|
||||
Node::ImportStatement(n) => *n as *const _ as *const (),
|
||||
Node::ExpressionStatement(n) => *n as *const _ as *const (),
|
||||
Node::VariableDeclaration(n) => *n as *const _ as *const (),
|
||||
Node::ReturnStatement(n) => *n as *const _ as *const (),
|
||||
Node::VariableDeclarator(n) => *n as *const _ as *const (),
|
||||
Node::Literal(n) => *n as *const _ as *const (),
|
||||
Node::TagDeclarator(n) => *n as *const _ as *const (),
|
||||
Node::Identifier(n) => *n as *const _ as *const (),
|
||||
Node::BinaryExpression(n) => *n as *const _ as *const (),
|
||||
Node::FunctionExpression(n) => *n as *const _ as *const (),
|
||||
Node::CallExpression(n) => *n as *const _ as *const (),
|
||||
Node::CallExpressionKw(n) => *n as *const _ as *const (),
|
||||
Node::PipeExpression(n) => *n as *const _ as *const (),
|
||||
Node::PipeSubstitution(n) => *n as *const _ as *const (),
|
||||
Node::ArrayExpression(n) => *n as *const _ as *const (),
|
||||
Node::ArrayRangeExpression(n) => *n as *const _ as *const (),
|
||||
Node::ObjectExpression(n) => *n as *const _ as *const (),
|
||||
Node::MemberExpression(n) => *n as *const _ as *const (),
|
||||
Node::UnaryExpression(n) => *n as *const _ as *const (),
|
||||
Node::Parameter(p) => *p as *const _ as *const (),
|
||||
Node::ObjectProperty(n) => *n as *const _ as *const (),
|
||||
Node::IfExpression(n) => *n as *const _ as *const (),
|
||||
Node::ElseIf(n) => *n as *const _ as *const (),
|
||||
Node::KclNone(n) => *n as *const _ as *const (),
|
||||
Node::LabelledExpression(n) => *n as *const _ as *const (),
|
||||
Node::Ascription(n) => *n as *const _ as *const (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returned during source_range conversion.
|
||||
#[derive(Debug)]
|
||||
pub enum AstNodeError {
|
||||
/// Returned if we try and [SourceRange] a [types::KclNone].
|
||||
NoSourceForAKclNone,
|
||||
}
|
||||
|
||||
impl TryFrom<&Node<'_>> for SourceRange {
|
||||
type Error = AstNodeError;
|
||||
|
||||
fn try_from(node: &Node) -> Result<Self, Self::Error> {
|
||||
Ok(match node {
|
||||
Node::Program(n) => SourceRange::from(*n),
|
||||
Node::ImportStatement(n) => SourceRange::from(*n),
|
||||
Node::ExpressionStatement(n) => SourceRange::from(*n),
|
||||
Node::VariableDeclaration(n) => SourceRange::from(*n),
|
||||
Node::ReturnStatement(n) => SourceRange::from(*n),
|
||||
Node::VariableDeclarator(n) => SourceRange::from(*n),
|
||||
Node::Literal(n) => SourceRange::from(*n),
|
||||
Node::TagDeclarator(n) => SourceRange::from(*n),
|
||||
Node::Identifier(n) => SourceRange::from(*n),
|
||||
Node::BinaryExpression(n) => SourceRange::from(*n),
|
||||
Node::FunctionExpression(n) => SourceRange::from(*n),
|
||||
Node::CallExpression(n) => SourceRange::from(*n),
|
||||
Node::CallExpressionKw(n) => SourceRange::from(*n),
|
||||
Node::PipeExpression(n) => SourceRange::from(*n),
|
||||
Node::PipeSubstitution(n) => SourceRange::from(*n),
|
||||
Node::ArrayExpression(n) => SourceRange::from(*n),
|
||||
Node::ArrayRangeExpression(n) => SourceRange::from(*n),
|
||||
Node::ObjectExpression(n) => SourceRange::from(*n),
|
||||
Node::MemberExpression(n) => SourceRange::from(*n),
|
||||
Node::UnaryExpression(n) => SourceRange::from(*n),
|
||||
Node::Parameter(p) => SourceRange::from(&p.identifier),
|
||||
Node::ObjectProperty(n) => SourceRange::from(*n),
|
||||
Node::IfExpression(n) => SourceRange::from(*n),
|
||||
Node::LabelledExpression(n) => SourceRange::from(*n),
|
||||
Node::Ascription(n) => SourceRange::from(*n),
|
||||
|
||||
// This is broken too
|
||||
Node::ElseIf(n) => SourceRange::new(n.cond.start(), n.cond.end(), n.cond.module_id()),
|
||||
|
||||
// The KclNone type here isn't an actual node, so it has no
|
||||
// start/end information.
|
||||
Node::KclNone(_) => return Err(Self::Error::NoSourceForAKclNone),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tree> From<&'tree types::BodyItem> for Node<'tree> {
|
||||
fn from(node: &'tree types::BodyItem) -> Self {
|
||||
match node {
|
||||
types::BodyItem::ImportStatement(v) => v.as_ref().into(),
|
||||
types::BodyItem::ExpressionStatement(v) => v.into(),
|
||||
types::BodyItem::VariableDeclaration(v) => v.as_ref().into(),
|
||||
types::BodyItem::ReturnStatement(v) => v.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tree> From<&'tree types::Expr> for Node<'tree> {
|
||||
fn from(node: &'tree types::Expr) -> Self {
|
||||
match node {
|
||||
types::Expr::Literal(lit) => lit.as_ref().into(),
|
||||
types::Expr::TagDeclarator(tag) => tag.as_ref().into(),
|
||||
types::Expr::Identifier(id) => id.as_ref().into(),
|
||||
types::Expr::BinaryExpression(be) => be.as_ref().into(),
|
||||
types::Expr::FunctionExpression(fe) => fe.as_ref().into(),
|
||||
types::Expr::CallExpression(ce) => ce.as_ref().into(),
|
||||
types::Expr::CallExpressionKw(ce) => ce.as_ref().into(),
|
||||
types::Expr::PipeExpression(pe) => pe.as_ref().into(),
|
||||
types::Expr::PipeSubstitution(ps) => ps.as_ref().into(),
|
||||
types::Expr::ArrayExpression(ae) => ae.as_ref().into(),
|
||||
types::Expr::ArrayRangeExpression(are) => are.as_ref().into(),
|
||||
types::Expr::ObjectExpression(oe) => oe.as_ref().into(),
|
||||
types::Expr::MemberExpression(me) => me.as_ref().into(),
|
||||
types::Expr::UnaryExpression(ue) => ue.as_ref().into(),
|
||||
types::Expr::IfExpression(e) => e.as_ref().into(),
|
||||
types::Expr::LabelledExpression(e) => e.as_ref().into(),
|
||||
types::Expr::AscribedExpression(e) => e.as_ref().into(),
|
||||
types::Expr::None(n) => n.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tree> From<&'tree types::BinaryPart> for Node<'tree> {
|
||||
fn from(node: &'tree types::BinaryPart) -> Self {
|
||||
match node {
|
||||
types::BinaryPart::Literal(lit) => lit.as_ref().into(),
|
||||
types::BinaryPart::Identifier(id) => id.as_ref().into(),
|
||||
types::BinaryPart::BinaryExpression(be) => be.as_ref().into(),
|
||||
types::BinaryPart::CallExpression(ce) => ce.as_ref().into(),
|
||||
types::BinaryPart::CallExpressionKw(ce) => ce.as_ref().into(),
|
||||
types::BinaryPart::UnaryExpression(ue) => ue.as_ref().into(),
|
||||
types::BinaryPart::MemberExpression(me) => me.as_ref().into(),
|
||||
types::BinaryPart::IfExpression(e) => e.as_ref().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tree> From<&'tree types::MemberObject> for Node<'tree> {
|
||||
fn from(node: &'tree types::MemberObject) -> Self {
|
||||
match node {
|
||||
types::MemberObject::MemberExpression(me) => me.as_ref().into(),
|
||||
types::MemberObject::Identifier(id) => id.as_ref().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tree> From<&'tree types::LiteralIdentifier> for Node<'tree> {
|
||||
fn from(node: &'tree types::LiteralIdentifier) -> Self {
|
||||
match node {
|
||||
types::LiteralIdentifier::Identifier(id) => id.as_ref().into(),
|
||||
types::LiteralIdentifier::Literal(lit) => lit.as_ref().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from {
|
||||
($node:ident, $t: ident) => {
|
||||
impl<'a> From<NodeRef<'a, types::$t>> for Node<'a> {
|
||||
fn from(v: NodeRef<'a, types::$t>) -> Self {
|
||||
Node::$t(v)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_from_ref {
|
||||
($node:ident, $t: ident) => {
|
||||
impl<'a> From<&'a types::$t> for Node<'a> {
|
||||
fn from(v: &'a types::$t) -> Self {
|
||||
Node::$t(v)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_from!(Node, Program);
|
||||
impl_from!(Node, ImportStatement);
|
||||
impl_from!(Node, ExpressionStatement);
|
||||
impl_from!(Node, VariableDeclaration);
|
||||
impl_from!(Node, ReturnStatement);
|
||||
impl_from!(Node, VariableDeclarator);
|
||||
impl_from!(Node, Literal);
|
||||
impl_from!(Node, TagDeclarator);
|
||||
impl_from!(Node, Identifier);
|
||||
impl_from!(Node, BinaryExpression);
|
||||
impl_from!(Node, FunctionExpression);
|
||||
impl_from!(Node, CallExpression);
|
||||
impl_from!(Node, CallExpressionKw);
|
||||
impl_from!(Node, PipeExpression);
|
||||
impl_from!(Node, PipeSubstitution);
|
||||
impl_from!(Node, ArrayExpression);
|
||||
impl_from!(Node, ArrayRangeExpression);
|
||||
impl_from!(Node, ObjectExpression);
|
||||
impl_from!(Node, MemberExpression);
|
||||
impl_from!(Node, UnaryExpression);
|
||||
impl_from!(Node, ObjectProperty);
|
||||
impl_from_ref!(Node, Parameter);
|
||||
impl_from!(Node, IfExpression);
|
||||
impl_from!(Node, ElseIf);
|
||||
impl_from!(Node, LabelledExpression);
|
||||
impl_from!(Node, Ascription);
|
||||
impl_from!(Node, KclNone);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! kcl {
|
||||
( $kcl:expr ) => {{
|
||||
$crate::parsing::top_level_parse($kcl).unwrap()
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_ptr_eq() {
|
||||
let program = kcl!(
|
||||
"
|
||||
const foo = 1
|
||||
const bar = foo + 1
|
||||
|
||||
fn myfn = () => {
|
||||
const foo = 2
|
||||
sin(foo)
|
||||
}
|
||||
"
|
||||
);
|
||||
|
||||
let foo: Node = (&program.body[0]).into();
|
||||
assert!(foo.ptr_eq((&program.body[0]).into()));
|
||||
assert!(!foo.ptr_eq((&program.body[1]).into()));
|
||||
}
|
||||
}
|
202
rust/kcl-lib/src/walk/ast_visitor.rs
Normal file
202
rust/kcl-lib/src/walk/ast_visitor.rs
Normal file
@ -0,0 +1,202 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::walk::Node;
|
||||
|
||||
/// Walk-specific trait adding the ability to traverse the KCL AST.
|
||||
///
|
||||
/// This trait is implemented on [Node] to handle the fairly tricky bit of
|
||||
/// recursing into the AST in a single place, as well as helpers for traversing
|
||||
/// the tree. for callers to use.
|
||||
pub trait Visitable<'tree> {
|
||||
/// Return a `Vec<Node>` for all *direct* children of this AST node. This
|
||||
/// should only contain direct descendants.
|
||||
fn children(&self) -> Vec<Node<'tree>>;
|
||||
|
||||
/// Return `self` as a [Node]. Generally speaking, the [Visitable] trait
|
||||
/// is only going to be implemented on [Node], so this is purely used by
|
||||
/// helpers that are generic over a [Visitable] and want to deref back
|
||||
/// into a [Node].
|
||||
fn node(&self) -> Node<'tree>;
|
||||
|
||||
/// Call the provided [Visitor] in order to Visit `self`. This will
|
||||
/// only be called on `self` -- the [Visitor] is responsible for
|
||||
/// recursing into any children, if desired.
|
||||
fn visit<VisitorT>(&self, visitor: VisitorT) -> Result<bool, VisitorT::Error>
|
||||
where
|
||||
VisitorT: Visitor<'tree>,
|
||||
{
|
||||
visitor.visit_node(self.node())
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait used to enable visiting members of KCL AST.
|
||||
///
|
||||
/// Implementing this trait enables the implementer to be invoked over
|
||||
/// members of KCL AST by using the [Visitable::visit] function on
|
||||
/// a [Node].
|
||||
pub trait Visitor<'tree> {
|
||||
/// Error type returned by the [Self::visit] function.
|
||||
type Error;
|
||||
|
||||
/// Visit a KCL AST [Node].
|
||||
///
|
||||
/// In general, implementers likely wish to check to see if a Node is what
|
||||
/// they're looking for, and either descend into that [Node]'s children (by
|
||||
/// calling [Visitable::children] on [Node] to get children nodes,
|
||||
/// calling [Visitable::visit] on each node of interest), or perform
|
||||
/// some action.
|
||||
fn visit_node(&self, node: Node<'tree>) -> Result<bool, Self::Error>;
|
||||
}
|
||||
|
||||
impl<'a, FnT, ErrorT> Visitor<'a> for FnT
|
||||
where
|
||||
FnT: Fn(Node<'a>) -> Result<bool, ErrorT>,
|
||||
{
|
||||
type Error = ErrorT;
|
||||
|
||||
fn visit_node(&self, n: Node<'a>) -> Result<bool, ErrorT> {
|
||||
self(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tree> Visitable<'tree> for Node<'tree> {
|
||||
fn node(&self) -> Node<'tree> {
|
||||
*self
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Node<'tree>> {
|
||||
match self {
|
||||
Node::Program(n) => n.body.iter().map(|node| node.into()).collect(),
|
||||
Node::ExpressionStatement(n) => {
|
||||
vec![(&n.expression).into()]
|
||||
}
|
||||
Node::BinaryExpression(n) => {
|
||||
vec![(&n.left).into(), (&n.right).into()]
|
||||
}
|
||||
Node::FunctionExpression(n) => {
|
||||
let mut children = n.params.iter().map(|v| v.into()).collect::<Vec<Node>>();
|
||||
children.push((&n.body).into());
|
||||
children
|
||||
}
|
||||
Node::CallExpression(n) => {
|
||||
let mut children = n.arguments.iter().map(|v| v.into()).collect::<Vec<Node>>();
|
||||
children.insert(0, (&n.callee).into());
|
||||
children
|
||||
}
|
||||
Node::CallExpressionKw(n) => {
|
||||
let mut children = n.unlabeled.iter().map(|v| v.into()).collect::<Vec<Node>>();
|
||||
|
||||
// TODO: this is wrong but it's what the old walk code was doing.
|
||||
// We likely need a real LabeledArg AST node, but I don't
|
||||
// want to tango with it since it's a lot deeper than
|
||||
// adding it to the enum.
|
||||
children.extend(n.arguments.iter().map(|v| (&v.arg).into()).collect::<Vec<Node>>());
|
||||
children
|
||||
}
|
||||
Node::PipeExpression(n) => n.body.iter().map(|v| v.into()).collect(),
|
||||
Node::ArrayExpression(n) => n.elements.iter().map(|v| v.into()).collect(),
|
||||
Node::ArrayRangeExpression(n) => {
|
||||
vec![(&n.start_element).into(), (&n.end_element).into()]
|
||||
}
|
||||
Node::ObjectExpression(n) => n.properties.iter().map(|v| v.into()).collect(),
|
||||
Node::MemberExpression(n) => {
|
||||
vec![(&n.object).into(), (&n.property).into()]
|
||||
}
|
||||
Node::IfExpression(n) => {
|
||||
let mut children = n.else_ifs.iter().map(|v| v.into()).collect::<Vec<Node>>();
|
||||
children.insert(0, n.cond.as_ref().into());
|
||||
children.push(n.final_else.as_ref().into());
|
||||
children
|
||||
}
|
||||
Node::VariableDeclaration(n) => vec![(&n.declaration).into()],
|
||||
Node::ReturnStatement(n) => {
|
||||
vec![(&n.argument).into()]
|
||||
}
|
||||
Node::VariableDeclarator(n) => {
|
||||
vec![(&n.id).into(), (&n.init).into()]
|
||||
}
|
||||
Node::UnaryExpression(n) => {
|
||||
vec![(&n.argument).into()]
|
||||
}
|
||||
Node::Parameter(n) => {
|
||||
vec![(&n.identifier).into()]
|
||||
}
|
||||
Node::ObjectProperty(n) => {
|
||||
vec![(&n.value).into()]
|
||||
}
|
||||
Node::ElseIf(n) => {
|
||||
vec![(&n.cond).into(), n.then_val.as_ref().into()]
|
||||
}
|
||||
Node::LabelledExpression(e) => {
|
||||
vec![(&e.expr).into(), (&e.label).into()]
|
||||
}
|
||||
Node::Ascription(e) => {
|
||||
vec![(&e.expr).into()]
|
||||
}
|
||||
Node::PipeSubstitution(_)
|
||||
| Node::TagDeclarator(_)
|
||||
| Node::Identifier(_)
|
||||
| Node::ImportStatement(_)
|
||||
| Node::KclNone(_)
|
||||
| Node::Literal(_) => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::*;
|
||||
|
||||
macro_rules! kcl {
|
||||
( $kcl:expr ) => {{
|
||||
$crate::parsing::top_level_parse($kcl).unwrap()
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_crows() {
|
||||
let program = kcl!(
|
||||
"\
|
||||
const crow1 = 1
|
||||
const crow2 = 2
|
||||
|
||||
fn crow3() {
|
||||
const crow4 = 3
|
||||
crow5()
|
||||
}
|
||||
"
|
||||
);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct CountCrows {
|
||||
n: Box<Mutex<usize>>,
|
||||
}
|
||||
|
||||
impl<'tree> Visitor<'tree> for &CountCrows {
|
||||
type Error = ();
|
||||
|
||||
fn visit_node(&self, node: Node<'tree>) -> Result<bool, Self::Error> {
|
||||
if let Node::VariableDeclarator(vd) = node {
|
||||
if vd.id.name.starts_with("crow") {
|
||||
*self.n.lock().unwrap() += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for child in node.children().iter() {
|
||||
if !child.visit(*self)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
let prog: Node = (&program).into();
|
||||
let count_crows: CountCrows = Default::default();
|
||||
Visitable::visit(&prog, &count_crows).unwrap();
|
||||
assert_eq!(*count_crows.n.lock().unwrap(), 4);
|
||||
}
|
||||
}
|
85
rust/kcl-lib/src/walk/ast_walk.rs
Normal file
85
rust/kcl-lib/src/walk/ast_walk.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use super::ast_visitor::{Visitable, Visitor};
|
||||
use crate::{
|
||||
parsing::ast::types::{NodeRef, Program},
|
||||
walk::Node,
|
||||
};
|
||||
|
||||
/// *DEPRECATED* Walk trait.
|
||||
///
|
||||
/// This was written before [Visitor], which is the better way to traverse
|
||||
/// a AST.
|
||||
///
|
||||
/// This trait continues to exist in order to not change all the linter
|
||||
/// as we refine the walk code.
|
||||
///
|
||||
/// This, internally, uses the new [Visitor] trait, and is only provided as
|
||||
/// a stub until we migrate all existing code off this trait.
|
||||
pub trait Walker<'a> {
|
||||
/// Walk will visit every element of the AST, recursing through the
|
||||
/// whole tree.
|
||||
fn walk(&self, n: Node<'a>) -> Result<bool>;
|
||||
}
|
||||
|
||||
impl<'tree, VisitorT> Walker<'tree> for VisitorT
|
||||
where
|
||||
VisitorT: Visitor<'tree>,
|
||||
VisitorT: Clone,
|
||||
anyhow::Error: From<VisitorT::Error>,
|
||||
VisitorT::Error: Send,
|
||||
VisitorT::Error: Sync,
|
||||
{
|
||||
fn walk(&self, n: Node<'tree>) -> Result<bool> {
|
||||
if !n.visit(self.clone())? {
|
||||
return Ok(false);
|
||||
}
|
||||
for child in n.children() {
|
||||
if !Self::walk(self, child)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the Walker against all [Node]s in a [Program].
|
||||
pub fn walk<'a, WalkT>(prog: NodeRef<'a, Program>, f: WalkT) -> Result<bool>
|
||||
where
|
||||
WalkT: Walker<'a>,
|
||||
{
|
||||
let prog: Node = prog.into();
|
||||
f.walk(prog)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! kcl {
|
||||
( $kcl:expr ) => {{
|
||||
$crate::parsing::top_level_parse($kcl).unwrap()
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stop_walking() {
|
||||
let program = kcl!(
|
||||
"
|
||||
const foo = 1
|
||||
const bar = 2
|
||||
"
|
||||
);
|
||||
|
||||
walk(&program, |node| {
|
||||
if let Node::VariableDeclarator(vd) = node {
|
||||
if vd.id.name == "foo" {
|
||||
return Ok::<bool, anyhow::Error>(false);
|
||||
}
|
||||
panic!("walk didn't stop");
|
||||
}
|
||||
Ok(true)
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
226
rust/kcl-lib/src/walk/import_graph.rs
Normal file
226
rust/kcl-lib/src/walk/import_graph.rs
Normal file
@ -0,0 +1,226 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
parsing::ast::types::{ImportPath, NodeRef, Program},
|
||||
walk::{Node, Visitable},
|
||||
};
|
||||
|
||||
/// 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.
|
||||
#[allow(clippy::iter_over_hash_type)]
|
||||
pub fn import_graph(progs: HashMap<String, NodeRef<'_, 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)
|
||||
}
|
||||
|
||||
#[allow(clippy::iter_over_hash_type)]
|
||||
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_default();
|
||||
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(prog: NodeRef<'_, Program>) -> Result<Vec<String>> {
|
||||
let ret = Arc::new(Mutex::new(vec![]));
|
||||
|
||||
fn walk(ret: Arc<Mutex<Vec<String>>>, node: Node<'_>) {
|
||||
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);
|
||||
|
||||
import_graph(modules).unwrap_err();
|
||||
}
|
||||
}
|
9
rust/kcl-lib/src/walk/mod.rs
Normal file
9
rust/kcl-lib/src/walk/mod.rs
Normal file
@ -0,0 +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