Skip to content
Draft
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
30 changes: 5 additions & 25 deletions src/expression/node/AccessorNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
isParenthesisNode,
isSymbolNode
} from '../../utils/is.js'
import { getSafeProperty } from '../../utils/customs.js'
import { factory } from '../../utils/factory.js'
import { accessFactory } from './utils/access.js'

Expand Down Expand Up @@ -61,17 +60,6 @@ export const createAccessorNode = /* #__PURE__ */ factory(name, dependencies, ({
this.index = index
}

// readonly property name
get name () {
if (this.index) {
return (this.index.isObjectProperty())
? this.index.getObjectProperty()
: ''
} else {
return this.object.name || ''
}
}

static name = name
get type () { return name }
get isAccessorNode () { return true }
Expand All @@ -93,19 +81,11 @@ export const createAccessorNode = /* #__PURE__ */ factory(name, dependencies, ({
const evalObject = this.object._compile(math, argNames)
const evalIndex = this.index._compile(math, argNames)

if (this.index.isObjectProperty()) {
const prop = this.index.getObjectProperty()
return function evalAccessorNode (scope, args, context) {
// get a property from an object evaluated using the scope.
return getSafeProperty(evalObject(scope, args, context), prop)
}
} else {
return function evalAccessorNode (scope, args, context) {
const object = evalObject(scope, args, context)
// we pass just object here instead of context:
const index = evalIndex(scope, args, object)
return access(object, index)
}
return function evalAccessorNode (scope, args, context) {
const object = evalObject(scope, args, context)
// we pass just object here instead of context:
const index = evalIndex(scope, args, object)
return access(object, index)
}
}

Expand Down
19 changes: 16 additions & 3 deletions src/expression/node/utils/access.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { errorTransform } from '../../transform/utils/errorTransform.js'
import { getSafeProperty } from '../../../utils/customs.js'
import { isDenseMatrix } from '../../../utils/is.js'

export function accessFactory ({ subset }) {
/**
Expand All @@ -23,11 +24,23 @@ export function accessFactory ({ subset }) {
// TODO: move getStringSubset into a separate util file, use that
return subset(object, index)
} else if (typeof object === 'object') {
if (!index.isObjectProperty()) {
throw new TypeError('Cannot apply a numeric index as object property')
if (index.isObjectProperty()) {
return getSafeProperty(object, index.getObjectProperty())
}

return getSafeProperty(object, index.getObjectProperty())
if (index._dimensions.length > 1) {
throw new SyntaxError('Cannot apply multi-element matrix as object property')
}

if (isDenseMatrix(index._dimensions[0])) {
const compiledIndex = index._dimensions[0].get([0])

// For some reason, the value in the generated Dense Matrix _data
// is always 1 less than the expected calculated value
return getSafeProperty(object, String(compiledIndex + 1))
}

throw new TypeError('Cannot apply unsupported value as object property')
} else {
throw new TypeError('Cannot apply index: unsupported type of object')
}
Expand Down
23 changes: 19 additions & 4 deletions src/expression/node/utils/assign.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { errorTransform } from '../../transform/utils/errorTransform.js'
import { setSafeProperty } from '../../../utils/customs.js'
import { isDenseMatrix } from '../../../utils/is.js'

export function assignFactory ({ subset, matrix }) {
/**
Expand Down Expand Up @@ -33,11 +34,25 @@ export function assignFactory ({ subset, matrix }) {
// TODO: move setStringSubset into a separate util file, use that
return subset(object, index, value)
} else if (typeof object === 'object') {
if (!index.isObjectProperty()) {
throw TypeError('Cannot apply a numeric index as object property')
if (index.isObjectProperty()) {
setSafeProperty(object, index.getObjectProperty(), value)
return object
}
setSafeProperty(object, index.getObjectProperty(), value)
return object

if (index._dimensions.length > 1) {
throw new SyntaxError('Cannot apply multi-element matrix as object property')
}

if (isDenseMatrix(index._dimensions[0])) {
const compiledIndex = index._dimensions[0].get([0])

// For some reason, the value in the generated Dense Matrix _data
// is always 1 less than the expected calculated value
setSafeProperty(object, String(compiledIndex + 1), value)
return object
}

throw new TypeError('Cannot apply unsupported value as object property')
} else {
throw new TypeError('Cannot apply index: unsupported type of object')
}
Expand Down
13 changes: 10 additions & 3 deletions src/expression/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -1615,11 +1615,18 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({
// parse key
if (state.token === '"' || state.token === "'") {
key = parseStringToken(state, state.token)
} else if (state.tokenType === TOKENTYPE.SYMBOL || (state.tokenType === TOKENTYPE.DELIMITER && state.token in NAMED_DELIMITERS)) {
key = state.token
} else if (state.tokenType === TOKENTYPE.SYMBOL || (state.tokenType === TOKENTYPE.DELIMITER && state.token in NAMED_DELIMITERS) || state.tokenType === TOKENTYPE.NUMBER) {
key = state.tokenType === TOKENTYPE.NUMBER ? String(Number(state.token)) : state.token
getToken(state)
} else if (state.token === '-') {
const minusNode = parseUnary(state)
if (isOperatorNode(minusNode) && minusNode.args.length === 1 && isConstantNode(minusNode.args[0]) && typeof minusNode.args[0].value === 'number') {
key = `-${minusNode.args[0].value}`
} else {
throw createSyntaxError(state, 'Numeric literal expected after "-" as object key')
}
} else {
throw createSyntaxError(state, 'Symbol or string expected as object key')
throw createSyntaxError(state, 'Symbol, numeric literal or string expected as object key')
}

// parse key/value separator
Expand Down
60 changes: 55 additions & 5 deletions test/unit-tests/expression/parse.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -912,11 +912,61 @@ describe('parse', function () {
assert.deepStrictEqual(scope, { obj: { foo: { bar: 2 } } })
})

it('should throw an error when trying to apply a matrix index as object property', function () {
const scope = { a: {} }
assert.throws(function () {
parseAndEval('a[2] = 6', scope)
}, /Cannot apply a numeric index as object property/)
it('should coerce numbers to string when trying to apply a numeric key in an object expression', function () {
assert.deepStrictEqual(parseAndEval('{2: 6}'), { 2: 6 })
assert.deepStrictEqual(parseAndEval('{-2: 6}'), { '-2': 6 })
})

it('should coerce numbers to string when trying to access an object expression property with matrix index', function () {
assert.strictEqual(parseAndEval('{2: 6}[2]'), 6)
assert.strictEqual(parseAndEval('{2.5: 6}[2.5]'), 6)
assert.strictEqual(parseAndEval('{5: 16}[3]'), undefined)
})

it('should coerce numbers to string when trying to set an object property with matrix index', function () {
const scope = { obj: {} }
const res1 = parseAndEval('obj[2] = 6', scope)
const res2 = parseAndEval('obj[-2.5] = {4: "haha"}', scope)
assert.strictEqual(res1, 6)
assert.strictEqual(res2, { 4: 'haha' })
assert.deepStrictEqual(scope, { obj: { 2: 6, '-2.5': { 4: 'haha' } } })
})

it('should accept operations / expressions in a matrix when trying to access an object expression property', function () {
assert.strictEqual(parseAndEval('{2: 6}[2 * 1]'), 6)
assert.strictEqual(parseAndEval('{31: 7 - 4}[0.2 + 0.8]'), undefined)
assert.strictEqual(parseAndEval('{6: "haha"}[multiply(2, 3)]'), 'haha')
assert.strictEqual(parseAndEval('{-4: 11 * 4}[-4]'), 44)
})

it('should accept operations / expressions in a matrix when trying to set an object property', function () {
const scope = { obj: {} }
const res1 = parseAndEval('obj[2^2] = 6', scope)
const res2 = parseAndEval('obj[multiply(2, add(3,1))] = "haha"', scope)
assert.strictEqual(res1, 6)
assert.strictEqual(res2, 'haha')
assert.deepStrictEqual(scope, { obj: { 4: 6, 8: 'haha' } })
})

it('should ignore leading zeros when trying to apply numeric keys in an object expression', function () {
assert.deepStrictEqual(parseAndEval('{02: 6}'), { 2: 6 })
assert.deepStrictEqual(parseAndEval('{0070: 6}'), { 70: 6 })
assert.deepStrictEqual(parseAndEval('{0.2: 6}'), { 0.2: 6 })
assert.deepStrictEqual(parseAndEval('{0010.0501: "haha"}'), { 10.0501: 'haha' })
})

it('should ignore leading zeros in a matrix index when trying to access an object expression property', function () {
assert.strictEqual(parseAndEval('{2: 6}[02]'), 6)
assert.strictEqual(parseAndEval('{70: 1 - 6}[0070]'), -5)
assert.strictEqual(parseAndEval('{0.2: 6}[000.2]'), 6)
assert.strictEqual(parseAndEval('{10.0501: "haha"}[0010.0501]'), 'haha')
})

it('should ignore leading zeros in a matrix index when trying to set an object property', function () {
const scope = { obj: {} }
const res = parseAndEval('obj[02] = 6', scope)
assert.strictEqual(res, 6)
assert.deepStrictEqual(scope, { obj: { 2: 6 } })
})

it('should set a nested matrix subset from an object property (1)', function () {
Expand Down