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

Add tokenIndex to SymbolNode #2796

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -221,6 +221,7 @@ Jaeu Jeong <wodndb@gmail.com>
cyavictor88 <100557319+cyavictor88@users.noreply.github.com>
David Contreras <david.contreras@guentner.com>
Jakub Riegel <jakubriegel112@gmail.com>
Matthew Canestraro <matthew.canestraro@tenet3.com>
Angus Comrie <accomrie@gmail.com>
TMTron <martin.trummer@tmtron.com>

Expand Down
83 changes: 62 additions & 21 deletions docs/expressions/expression_trees.md
Expand Up @@ -39,10 +39,11 @@ tree generated by `math.parse('sqrt(2 + x)')`.

All nodes have the following methods:

- `clone() : Node`
- `clone(options: MetaOptions) : Node`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The options are optional, can you describe that here and for all constructors in the docs?


Create a shallow clone of the node.
The node itself is cloned, its childs are not cloned.
The node itself is cloned, its childs are not cloned.
Information on available options can be found at [Type Definitions](expression_trees.md#type-definitions)

- `cloneDeep() : Node`

Expand Down Expand Up @@ -263,7 +264,10 @@ Each `Node` has the following properties:
- `type: string`

The type of the node, for example `'SymbolNode'` in case of a `SymbolNode`.


- `sources: SourceMapping[]`
An array of sources mapping this node back to its tokens in the parsed string.
The exact mapping will depend on the type of node and is listed in more detail for each node below.

## Nodes

Expand All @@ -276,14 +280,15 @@ namespace `math`.
Construction:

```
new AccessorNode(object: Node, index: IndexNode)
new AccessorNode(object: Node, index: IndexNode, meta: MetaOptions)
```

Properties:

- `object: Node`
- `index: IndexNode`
- `name: string` (read-only) The function or method name. Returns an empty string when undefined.
- `sources: SourceMapping[]` mappings to tokens defining this accessor. This will be `[` and `]` for array accessors, and `.` for dot notation accessors.

Examples:

Expand All @@ -301,12 +306,13 @@ const node2 = new math.AccessorNode(object, index)
Construction:

```
new ArrayNode(items: Node[])
new ArrayNode(items: Node[], meta: MetaOptions)
```

Properties:

- `items: Node[]`
- `sources: SourceMapping[]` mappings to the `[`, `]`, `,`, and `;` used to define this array in the parsed string

Examples:

Expand All @@ -325,8 +331,8 @@ const node2 = new math.ArrayNode([one, two, three])
Construction:

```
new AssignmentNode(object: SymbolNode, value: Node)
new AssignmentNode(object: SymbolNode | AccessorNode, index: IndexNode, value: Node)
new AssignmentNode(object: SymbolNode, value: Node, meta: MetaOptions)
new AssignmentNode(object: SymbolNode | AccessorNode, index: IndexNode, value: Node, meta: MetaOptions)
```

Properties:
Expand All @@ -335,6 +341,7 @@ Properties:
- `index: IndexNode | null`
- `value: Node`
- `name: string` (read-only) The function or method name. Returns an empty string when undefined.
- `sources: SourceMapping[]` mapping to the `=` defining this assignment node in the parsed string

Examples:

Expand All @@ -358,12 +365,13 @@ a semicolon).
Construction:

```
block = new BlockNode(Array.<{node: Node} | {node: Node, visible: boolean}>)
block = new BlockNode(Array.<{node: Node} | {node: Node, visible: boolean}>, meta: MetaOptions)
```

Properties:

- `blocks: Array.<{node: Node, visible: boolean}>`
- `sources: SourceMapping[]` mappings to each `;` token delimiting blocks in the parsed string

Examples:

Expand Down Expand Up @@ -395,14 +403,15 @@ const block2 = new BlockNode([
Construction:

```
new ConditionalNode(condition: Node, trueExpr: Node, falseExpr: Node)
new ConditionalNode(condition: Node, trueExpr: Node, falseExpr: Node, meta: MetaOptions)
```

Properties:

- `condition: Node`
- `trueExpr: Node`
- `falseExpr: Node`
- `sources: SourceMapping[]` mappings to the `?`, and `:` tokens defining this conditional in the parsed string

Examples:

Expand All @@ -422,12 +431,13 @@ const node2 = new math.ConditionalNode(condition, trueExpr, falseExpr)
Construction:

```
new ConstantNode(value: *)
new ConstantNode(value: *, meta: MetaOptions)
```

Properties:

- `value: *`
- `sources: SourceMapping[]` mapping to the token representing the constant in the parsed string.

Examples:

Expand All @@ -444,14 +454,15 @@ const node3 = new math.ConstantNode('foo')
Construction:

```
new FunctionAssignmentNode(name: string, params: string[], expr: Node)
new FunctionAssignmentNode(name: string, params: string[], expr: Node, meta: MetaOptions)
```

Properties:

- `name: string`
- `params: string[]`
- `expr: Node`
- `sources: SourceMapping[]` mapping to the `=` for this assignment in the parsed string

Examples:

Expand All @@ -470,13 +481,14 @@ const node2 = new math.FunctionAssignmentNode('f', ['x'], expr)
Construction:

```
new FunctionNode(fn: Node | string, args: Node[])
new FunctionNode(fn: Node | string, args: Node[], meta: MetaOptions)
```

Properties:

- `fn: Node | string` (read-only) The object or function name which to invoke.
- `args: Node[]`
- `sources: SourceMapping[]` mappings to the `(` and `)` defining this function, as well as any `,` delimiting its parameters.

Static functions:

Expand All @@ -498,8 +510,8 @@ const node3 = new math.FunctionNode(new SymbolNode('sqrt'), [four])
Construction:

```
new IndexNode(dimensions: Node[])
new IndexNode(dimensions: Node[], dotNotation: boolean)
new IndexNode(dimensions: Node[], meta: MetaOptions)
new IndexNode(dimensions: Node[], dotNotation: boolean, meta: MetaOptions)
```

Each dimension can be a single value, a range, or a property. The values of
Expand All @@ -514,6 +526,7 @@ Properties:

- `dimensions: Node[]`
- `dotNotation: boolean`
- `sources: SourceMapping[]` mappings to `,` delimiting items in an array index. If `dotNotation = true`, this will map to the constant following the `.` instead.

Examples:

Expand All @@ -535,12 +548,13 @@ const node2 = new math.AccessNode(A, index)
Construction:

```
new ObjectNode(properties: Object.<string, Node>)
new ObjectNode(properties: Object.<string, Node>, meta: MetaOptions)
```

Properties:

- `properties: Object.<string, Node>`
- `sources: SourceMapping[]` mappings to the `{`, `}`, `:`, and `,` tokens defining this object in the parsed string

Examples:

Expand All @@ -559,7 +573,7 @@ const node2 = new math.ObjectNode({a: a, b: b, c: c})
Construction:

```
new OperatorNode(op: string, fn: string, args: Node[], implicit: boolean = false)
new OperatorNode(op: string, fn: string, args: Node[], implicit: boolean = false, meta: MetaOptions)
```

Additional methods:
Expand Down Expand Up @@ -593,6 +607,7 @@ Properties:
- `fn: string`
- `args: Node[]`
- `implicit: boolean` True in case of an implicit multiplication, false otherwise
- `sources: SourceMapping[]` mapping to the `+` or `-` defining this unary operator in the parsed string

Examples:

Expand All @@ -609,12 +624,13 @@ const node2 = new math.OperatorNode('+', 'add', [a, b])
Construction:

```
new ParenthesisNode(content: Node)
new ParenthesisNode(content: Node, meta: MetaOptions)
```

Properties:

- `content: Node`
- `sources: SourceMapping[]` mappings to the `(` and `)` for this node in the parsed string

Examples:

Expand All @@ -630,15 +646,16 @@ const node2 = new math.ParenthesisNode(a)
Construction:

```
new RangeNode(start: Node, end: Node [, step: Node])
new RangeNode(start: Node, end: Node [, step: Node], meta: MetaOptions)
```

Properties:

- `start: Node`
- `end: Node`
- `step: Node | null`

- `sources: SourceMapping[]` mappings to the `:` defining this range node in the parsed string. There will be 1 or 2 mappings, depending on whether step size was defined for the range

Examples:

```js
Expand All @@ -659,7 +676,7 @@ const node4 = new math.RangeNode(zero, ten, two)
Construction:

```
new RelationalNode(conditionals: string[], params: Node[])
new RelationalNode(conditionals: string[], params: Node[], meta: MetaOptions)
```

`conditionals` is an array of strings, each of which may be 'smaller', 'larger', 'smallerEq', 'largerEq', 'equal', or 'unequal'. The `conditionals` array must contain exactly one fewer item than `params`.
Expand All @@ -668,6 +685,7 @@ Properties:

- `conditionals: string[]`
- `params: Node[]`
- `sources: SourceMapping[]` mappings to the relational symbol `<`, `>`, `==`, `>=`, or `<=` defining this node in the parsed string. This may include multiple mappings if multiple relationals are chained: `10 < x < 20`

A `RelationalNode` efficiently represents a chained conditional expression with two or more comparison operators, such as `10 < x <= 50`. The expression is equivalent to `10 < x and x <= 50`, except that `x` is evaluated only once, and evaluation stops (is "short-circuited") once any condition tests false. Operators that are subject to chaining are `<`, `>`, `<=`, `>=`, `==`, and `!=`. For backward compatibility, `math.parse` will return an `OperatorNode` if only a single conditional is present (such as `x > 2`).

Expand All @@ -689,12 +707,13 @@ const node2 = math.parse('10 < x <= 50')
Construction:

```
new SymbolNode(name: string)
new SymbolNode(name: string, meta: MetaOptions)
```

Properties:

- `name: string`
- `sources: SourceMapping[]` a single mapping to the symbol defining this node in the parsed string. The text will match whatever symbol is defined.

Static functions:

Expand All @@ -708,3 +727,25 @@ const node = math.parse('x')

const x = new math.SymbolNode('x')
```

## Type Definitions

A few node methods and properties have complex object structures as their parameters or return types. They are:

### MetaOptions

This object is passed as a final parameter in the constructor of any node, or as the parameter when calling `clone()`.

Properties:

- `sources: SourceMapping` sets the sources for the newly created or cloned node

### SourceMapping

Each node has an array of `SourceMapping` objects which map back to the node's corresponding tokens in the original source string

Properties:

- `text: string` the token representing this node in the parsed string
- `index; number` the index of the token in the parsed string

12 changes: 8 additions & 4 deletions src/expression/node/AccessorNode.js
Expand Up @@ -47,9 +47,10 @@ export const createAccessorNode = /* #__PURE__ */ factory(name, dependencies, ({
* @param {Node} object The object from which to retrieve
* a property or subset.
* @param {IndexNode} index IndexNode containing ranges
* @param {MetaOptions} object with additional options for building this node
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for clarity: to make this valid JSDoc, the lines like:

@param {MetaOptions} object with additional options for building this node

Should be changed to:

@param {MetaOptions} meta                     object with additional options for building this node

And if meta is optional:

@param {MetaOptions} [meta]                   object with additional options for building this node

*/
constructor (object, index) {
super()
constructor (object, index, meta = {}) {
super(meta)
if (!isNode(object)) {
throw new TypeError('Node expected for parameter "object"')
}
Expand Down Expand Up @@ -133,10 +134,13 @@ export const createAccessorNode = /* #__PURE__ */ factory(name, dependencies, ({

/**
* Create a clone of this node, a shallow copy
* @param {MetaOptions} object with additional options for cloning this node
* @return {AccessorNode}
*/
clone () {
return new AccessorNode(this.object, this.index)
clone (meta = {}) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value for meta, an empty object {}, does not correspond with the official TypeScript definition where sources is a require property.

I think it is best to have sources are required property. I would also like to get rid of mutating the input meta via meta.sources = ..., so I think we can rewrite the clone methods to something like:

    /**
     * Create a clone of this node, a shallow copy
     * @param {MetaOptions} [meta] object with additional options for cloning this node
     * @return {AccessorNode}
     */
    clone (meta) {
      const cloned = new AccessorNode(this.object, this.index, meta ?? { sources: this.sources })
      return cloned
    }

Having to construct a default meta object via { sources: this.sources } is a bit odd. Would it make sense to just keep the meta object as it is instead of destructuring it in the Node constructor, and having to re-construct it in the methods clone before we can pass it to another constructor? I.e we could think about:

  const defaultMetaOptions = {
    sources = []
  }

  class Node {
    /**
     * @constructor Node
     * A generic node, the parent of other AST nodes
     * @param {MetaOptions} [meta] object with additional options for building this node
     */
    constructor (meta = defaultMetaOptions) {
      this.meta = meta
    }
    // ...
  }

meta.sources = meta.sources || this.sources
const cloned = new AccessorNode(this.object, this.index, meta)
return cloned
}

/**
Expand Down
12 changes: 8 additions & 4 deletions src/expression/node/ArrayNode.js
Expand Up @@ -14,9 +14,10 @@ export const createArrayNode = /* #__PURE__ */ factory(name, dependencies, ({ No
* @extends {Node}
* Holds an 1-dimensional array with items
* @param {Node[]} [items] 1 dimensional array with items
* @param {MetaOptions} object with additional options for building this node
*/
constructor (items) {
super()
constructor (items, meta = {}) {
super(meta)
this.items = items || []

// validate input
Expand Down Expand Up @@ -91,10 +92,13 @@ export const createArrayNode = /* #__PURE__ */ factory(name, dependencies, ({ No

/**
* Create a clone of this node, a shallow copy
* @param {MetaOptions} object with additional options for cloning this node
* @return {ArrayNode}
*/
clone () {
return new ArrayNode(this.items.slice(0))
clone (meta = {}) {
meta.sources = meta.sources || this.sources
const cloned = new ArrayNode(this.items.slice(0), meta)
return cloned
}

/**
Expand Down
12 changes: 8 additions & 4 deletions src/expression/node/AssignmentNode.js
Expand Up @@ -65,9 +65,10 @@ export const createAssignmentNode = /* #__PURE__ */ factory(name, dependencies,
* global scope.
* @param {Node} value
* The value to be assigned
* @param {MetaOptions} object with additional options for building this node
*/
constructor (object, index, value) {
super()
constructor (object, index, value, meta = {}) {
super(meta)
this.object = object
this.index = value ? index : null
this.value = value || index
Expand Down Expand Up @@ -228,10 +229,13 @@ export const createAssignmentNode = /* #__PURE__ */ factory(name, dependencies,

/**
* Create a clone of this node, a shallow copy
* @param {MetaOptions} object with additional options for cloning this node
* @return {AssignmentNode}
*/
clone () {
return new AssignmentNode(this.object, this.index, this.value)
clone (meta = {}) {
meta.sources = meta.sources || this.sources
const cloned = new AssignmentNode(this.object, this.index, this.value, meta)
return cloned
}

/**
Expand Down