Skip to content
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
Binary file modified public/kcl-samples/screenshots/ball-joint-rod-end.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 10 additions & 12 deletions rust/kcl-lib/src/execution/exec_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use crate::{
parsing::ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
BinaryPart, BodyItem, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility, LiteralIdentifier,
LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef, ObjectExpression, PipeExpression, Program,
TagDeclarator, Type, UnaryExpression, UnaryOperator,
LiteralValue, MemberExpression, Name, Node, NodeRef, ObjectExpression, PipeExpression, Program, TagDeclarator,
Type, UnaryExpression, UnaryOperator,
},
source_range::SourceRange,
std::args::TyF64,
Expand Down Expand Up @@ -738,7 +738,7 @@ impl ExecutorContext {
Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state)?,
Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state, self).await?,
Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
Expr::LabelledExpression(expr) => {
Expand Down Expand Up @@ -824,7 +824,7 @@ impl BinaryPart {
BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state),
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
}
Expand Down Expand Up @@ -930,16 +930,14 @@ impl Node<Name> {
}

impl Node<MemberExpression> {
fn get_result(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
let property = Property::try_from(self.computed, self.property.clone(), exec_state, self.into())?;
let object = match &self.object {
// TODO: Don't use recursion here, use a loop.
MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?,
MemberObject::Identifier(identifier) => {
let value = exec_state.stack().get(&identifier.name, identifier.into())?;
value.clone()
}
let meta = Metadata {
source_range: SourceRange::from(self),
};
let object = ctx
.execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
.await?;

// Check the property and object match -- e.g. ints for arrays, strs for objects.
match (object, property, self.computed) {
Expand Down
2 changes: 1 addition & 1 deletion rust/kcl-lib/src/execution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2064,7 +2064,7 @@ notPipeSub = 1 |> identity(!%))";
// a runtime error instead.
parse_execute(code11).await.unwrap_err(),
KclError::new_syntax(KclErrorDetails::new(
"There was an unexpected !. Try removing it.".to_owned(),
"There was an unexpected `!`. Try removing it.".to_owned(),
vec![SourceRange::new(56, 57, ModuleId::default())],
))
);
Expand Down
11 changes: 0 additions & 11 deletions rust/kcl-lib/src/lsp/kcl/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,17 +247,6 @@ impl ObjectProperty {
}
}

impl MemberObject {
fn get_hover_value_for_position(&self, pos: usize, code: &str, opts: &HoverOpts) -> Option<Hover> {
match self {
MemberObject::MemberExpression(member_expression) => {
member_expression.get_hover_value_for_position(pos, code, opts)
}
MemberObject::Identifier(_identifier) => None,
}
}
}

impl MemberExpression {
fn get_hover_value_for_position(&self, pos: usize, code: &str, opts: &HoverOpts) -> Option<Hover> {
let object_source_range: SourceRange = self.object.clone().into();
Expand Down
11 changes: 1 addition & 10 deletions rust/kcl-lib/src/parsing/ast/digest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::parsing::ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryPart, BodyItem,
CallExpressionKw, DefaultParamVal, ElseIf, Expr, ExpressionStatement, FunctionExpression, FunctionType, Identifier,
IfExpression, ImportItem, ImportSelector, ImportStatement, ItemVisibility, KclNone, LabelledExpression, Literal,
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, ObjectExpression, ObjectProperty, Parameter,
LiteralIdentifier, LiteralValue, MemberExpression, Name, ObjectExpression, ObjectProperty, Parameter,
PipeExpression, PipeSubstitution, PrimitiveType, Program, ReturnStatement, TagDeclarator, Type, TypeDeclaration,
UnaryExpression, VariableDeclaration, VariableDeclarator, VariableKind,
};
Expand Down Expand Up @@ -167,15 +167,6 @@ impl BinaryPart {
}
}

impl MemberObject {
pub fn compute_digest(&mut self) -> Digest {
match self {
MemberObject::MemberExpression(me) => me.compute_digest(),
MemberObject::Identifier(id) => id.compute_digest(),
}
}
}

impl LiteralIdentifier {
pub fn compute_digest(&mut self) -> Digest {
match self {
Expand Down
11 changes: 1 addition & 10 deletions rust/kcl-lib/src/parsing/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pub(crate) mod digest;
pub mod types;

use crate::{
parsing::ast::types::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject},
parsing::ast::types::{BinaryPart, BodyItem, Expr, LiteralIdentifier},
ModuleId,
};

Expand Down Expand Up @@ -57,15 +57,6 @@ impl BinaryPart {
}
}

impl MemberObject {
pub fn module_id(&self) -> ModuleId {
match self {
MemberObject::MemberExpression(member_expression) => member_expression.module_id,
MemberObject::Identifier(identifier) => identifier.module_id,
}
}
}

impl LiteralIdentifier {
pub fn module_id(&self) -> ModuleId {
match self {
Expand Down
50 changes: 2 additions & 48 deletions rust/kcl-lib/src/parsing/ast/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2756,47 +2756,6 @@ impl ObjectProperty {
}
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum MemberObject {
MemberExpression(BoxNode<MemberExpression>),
Identifier(BoxNode<Identifier>),
}

impl MemberObject {
pub fn start(&self) -> usize {
match self {
MemberObject::MemberExpression(member_expression) => member_expression.start,
MemberObject::Identifier(identifier) => identifier.start,
}
}

pub fn end(&self) -> usize {
match self {
MemberObject::MemberExpression(member_expression) => member_expression.end,
MemberObject::Identifier(identifier) => identifier.end,
}
}

pub(crate) fn contains_range(&self, range: &SourceRange) -> bool {
let sr = SourceRange::from(self);
sr.contains_range(range)
}
}

impl From<MemberObject> for SourceRange {
fn from(obj: MemberObject) -> Self {
Self::new(obj.start(), obj.end(), obj.module_id())
}
}

impl From<&MemberObject> for SourceRange {
fn from(obj: &MemberObject) -> Self {
Self::new(obj.start(), obj.end(), obj.module_id())
}
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
Expand Down Expand Up @@ -2842,7 +2801,7 @@ impl From<&LiteralIdentifier> for SourceRange {
#[ts(export)]
#[serde(tag = "type")]
pub struct MemberExpression {
pub object: MemberObject,
pub object: Expr,
pub property: LiteralIdentifier,
pub computed: bool,

Expand All @@ -2864,12 +2823,7 @@ impl Node<MemberExpression> {
impl MemberExpression {
/// Rename all identifiers that have the old name to the new given name.
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
match &mut self.object {
MemberObject::MemberExpression(ref mut member_expression) => {
member_expression.rename_identifiers(old_name, new_name)
}
MemberObject::Identifier(ref mut identifier) => identifier.rename(old_name, new_name),
}
self.object.rename_identifiers(old_name, new_name);

match &mut self.property {
LiteralIdentifier::Identifier(ref mut identifier) => identifier.rename(old_name, new_name),
Expand Down
16 changes: 2 additions & 14 deletions rust/kcl-lib/src/parsing/ast/types/path.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde::Serialize;

use super::{BodyItem, Expr, MemberObject, Node, Program};
use super::{BodyItem, Expr, Node, Program};
use crate::SourceRange;

/// A traversal path through the AST to a node.
Expand Down Expand Up @@ -248,7 +248,7 @@ impl NodePath {
Expr::MemberExpression(node) => {
if node.object.contains_range(&range) {
path.push(Step::MemberExpressionObject);
return Self::from_member_expr_object(&node.object, range, path);
return NodePath::from_expr(&node.object, range, path);
}
if node.property.contains_range(&range) {
path.push(Step::MemberExpressionProperty);
Expand Down Expand Up @@ -313,18 +313,6 @@ impl NodePath {
Some(path)
}

fn from_member_expr_object(mut expr: &MemberObject, range: SourceRange, mut path: NodePath) -> Option<NodePath> {
while let MemberObject::MemberExpression(node) = expr {
if !node.object.contains_range(&range) {
break;
}
path.push(Step::MemberExpressionObject);
expr = &node.object;
}

Some(path)
}

pub fn is_empty(&self) -> bool {
self.steps.is_empty()
}
Expand Down
50 changes: 33 additions & 17 deletions rust/kcl-lib/src/parsing/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ use crate::{
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
BoxNode, CallExpressionKw, CommentStyle, DefaultParamVal, ElseIf, Expr, ExpressionStatement,
FunctionExpression, FunctionType, Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement,
ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name,
Node, NodeList, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter,
ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression, Name, Node,
NodeList, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter,
PipeExpression, PipeSubstitution, PrimitiveType, Program, ReturnStatement, Shebang, TagDeclarator, Type,
TypeDeclaration, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
},
Expand Down Expand Up @@ -1326,10 +1326,7 @@ fn member_expression_subscript(i: &mut TokenSlice) -> ModalResult<(LiteralIdenti

/// Get a property of an object, or an index of an array, or a member of a collection.
/// Can be arbitrarily nested, e.g. `people[i]['adam'].age`.
fn member_expression(i: &mut TokenSlice) -> ModalResult<Node<MemberExpression>> {
// This is an identifier, followed by a sequence of members (aka properties)
// First, the identifier.
let id = nameable_identifier.context(expected("the identifier of the object whose property you're trying to access, e.g. in 'shape.size.width', 'shape' is the identifier")).parse_next(i)?;
fn build_member_expression(object: Expr, i: &mut TokenSlice) -> ModalResult<Node<MemberExpression>> {
// Now a sequence of members.
let member = alt((member_expression_dot, member_expression_subscript)).context(expected("a member/property, e.g. size.x and size['height'] and size[0] are all different ways to access a member/property of 'size'"));
let mut members: Vec<_> = repeat(1.., member)
Expand All @@ -1340,11 +1337,11 @@ fn member_expression(i: &mut TokenSlice) -> ModalResult<Node<MemberExpression>>
// It's safe to call remove(0), because the vec is created from repeat(1..),
// which is guaranteed to have >=1 elements.
let (property, end, computed) = members.remove(0);
let start = id.start;
let module_id = id.module_id;
let start = object.start();
let module_id = object.module_id();
let initial_member_expression = Node::new(
MemberExpression {
object: MemberObject::Identifier(Box::new(id)),
object,
computed,
property,
digest: None,
Expand All @@ -1362,7 +1359,7 @@ fn member_expression(i: &mut TokenSlice) -> ModalResult<Node<MemberExpression>>
.fold(initial_member_expression, |accumulated, (property, end, computed)| {
Node::new(
MemberExpression {
object: MemberObject::MemberExpression(Box::new(accumulated)),
object: Expr::MemberExpression(Box::new(accumulated)),
computed,
property,
digest: None,
Expand Down Expand Up @@ -2085,8 +2082,7 @@ fn unnecessarily_bracketed(i: &mut TokenSlice) -> ModalResult<Expr> {
}

fn expr_allowed_in_pipe_expr(i: &mut TokenSlice) -> ModalResult<Expr> {
alt((
member_expression.map(Box::new).map(Expr::MemberExpression),
let parsed_expr = alt((
bool_value.map(Box::new).map(Expr::Literal),
tag.map(Box::new).map(Expr::TagDeclarator),
literal.map(Expr::Literal),
Expand All @@ -2100,14 +2096,19 @@ fn expr_allowed_in_pipe_expr(i: &mut TokenSlice) -> ModalResult<Expr> {
unnecessarily_bracketed,
))
.context(expected("a KCL expression (but not a pipe expression)"))
.parse_next(i)
.parse_next(i)?;

let maybe_member = build_member_expression(parsed_expr.clone(), i);
if let Ok(mem) = maybe_member {
return Ok(Expr::MemberExpression(Box::new(mem)));
}
Ok(parsed_expr)
}

fn possible_operands(i: &mut TokenSlice) -> ModalResult<Expr> {
let mut expr = alt((
unary_expression.map(Box::new).map(Expr::UnaryExpression),
bool_value.map(Box::new).map(Expr::Literal),
member_expression.map(Box::new).map(Expr::MemberExpression),
literal.map(Expr::Literal),
fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),
name.map(Box::new).map(Expr::Name),
Expand All @@ -2118,6 +2119,10 @@ fn possible_operands(i: &mut TokenSlice) -> ModalResult<Expr> {
"a KCL value which can be used as an argument/operand to an operator",
))
.parse_next(i)?;
let maybe_member = build_member_expression(expr.clone(), i);
if let Ok(mem) = maybe_member {
expr = Expr::MemberExpression(Box::new(mem));
}

let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?;
if let Some((_, _, ty)) = ty {
Expand Down Expand Up @@ -3258,7 +3263,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> ModalResult<Node<CallExpressionKw>> {
return Err(ErrMode::Cut(
CompilationError::fatal(
SourceRange::from(&tok),
format!("There was an unexpected {}. Try removing it.", tok.value),
format!("There was an unexpected `{}`. Try removing it.", tok.value),
)
.into(),
));
Expand Down Expand Up @@ -4427,7 +4432,7 @@ z(-[["#,
assert_err(
r#"z
(--#"#,
"There was an unexpected -. Try removing it.",
"There was an unexpected `-`. Try removing it.",
[3, 4],
);
}
Expand Down Expand Up @@ -4997,6 +5002,17 @@ type foo = fn(fn, f: fn(number(_))): [fn([any]): string]
assert_no_err(some_program_string);
}

#[test]
fn test_parse_fn_call_then_field() {
let some_program_string = "myFunction().field";
let module_id = ModuleId::default();
let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap(); // Updated import path
let actual = expression.parse(tokens.as_slice()).unwrap();
let Expr::MemberExpression(_expr) = actual else {
panic!("expected member expression")
};
}

#[test]
fn test_parse_array_missing_closing_bracket() {
let some_program_string = r#"
Expand Down Expand Up @@ -5185,7 +5201,7 @@ bar = 1
.cause
.as_ref()
.expect("Found an error, but there was no cause. Add a cause.");
assert_eq!(cause.message, "There was an unexpected }. Try removing it.",);
assert_eq!(cause.message, "There was an unexpected `}`. Try removing it.",);
assert_eq!(cause.source_range.start(), expected_src_start);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,20 @@ expression: actual
"computed": false,
"end": 45,
"object": {
"abs_path": false,
"commentStart": 40,
"end": 43,
"name": "obj",
"name": {
"commentStart": 40,
"end": 43,
"name": "obj",
"start": 40,
"type": "Identifier"
},
"path": [],
"start": 40,
"type": "Identifier",
"type": "Identifier"
"type": "Name",
"type": "Name"
},
"property": {
"commentStart": 44,
Expand Down
Loading
Loading