Skip to content

Commit

Permalink
Add tests for various source mappings
Browse files Browse the repository at this point in the history
  • Loading branch information
matthew-canestraro committed Mar 15, 2023
1 parent f940d5e commit e17be1e
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 4 deletions.
15 changes: 11 additions & 4 deletions src/expression/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,7 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({
function parseBlock (state) {
let node
const blocks = []
const sources = []
let visible

if (state.token !== '' && state.token !== '\n' && state.token !== ';') {
Expand All @@ -643,6 +644,8 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({

// TODO: simplify this loop
while (state.token === '\n' || state.token === ';') { // eslint-disable-line no-unmodified-loop-condition
sources.push(tokenSource(state))

if (blocks.length === 0 && node) {
visible = (state.token !== ';')
blocks.push({ node, visible })
Expand All @@ -661,7 +664,7 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({
}

if (blocks.length > 0) {
return withSources(new BlockNode(blocks), [{ index: 0, text: state.expression }])
return withSources(new BlockNode(blocks), sources)
} else {
if (!node) {
node = withSources(new ConstantNode(undefined), [{ index: 0, text: '' }])
Expand Down Expand Up @@ -987,7 +990,9 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({

if (state.token === ':') {
// implicit start=1 (one-based)
node = withSources(new ConstantNode(1), tokenSource(state))
const implicitSource = tokenSource(state)
implicitSource.text = ''
node = withSources(new ConstantNode(1), [implicitSource])
} else {
// explicit start
node = parseAddSubtract(state)
Expand All @@ -997,15 +1002,17 @@ export const createParse = /* #__PURE__ */ factory(name, dependencies, ({
// we ignore the range operator when a conditional operator is being processed on the same level
params.push(node)

const sources = [tokenSource(state)]
const sources = []
// parse step and end
while (state.token === ':' && params.length < 3) { // eslint-disable-line no-unmodified-loop-condition
sources.push(tokenSource(state))
getTokenSkipNewline(state)

if (state.token === ')' || state.token === ']' || state.token === ',' || state.token === '') {
// implicit end
params.push(withSources(new SymbolNode('end'), [tokenSource(state)]))
const implicitSource = tokenSource(state)
implicitSource.text = ''
params.push(withSources(new SymbolNode('end'), [implicitSource]))
} else {
// explicit end
params.push(parseAddSubtract(state))
Expand Down
227 changes: 227 additions & 0 deletions test/unit-tests/expression/parse.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2241,4 +2241,231 @@ describe('parse', function () {

assert.strictEqual(mathClone.evaluate('2'), 2)
})

describe('sources', function () {
it('adds sources for constants', function () {
const parsed = math.parse('4')
assert.deepStrictEqual(parsed.sources, [{ index: 0, text: '4' }])
})

it('adds sources for symbols', function () {
const parsed = math.parse('foo')
assert.deepStrictEqual(parsed.sources, [{ index: 0, text: 'foo' }])
})

it('adds sources for operators', function () {
const parsed = math.parse('1 + 2')
assert.deepStrictEqual(parsed.sources, [{ index: 2, text: '+' }])
assert.deepStrictEqual(parsed.args[0].sources, [{ index: 0, text: '1' }])
assert.deepStrictEqual(parsed.args[1].sources, [{ index: 4, text: '2' }])
})

it('adds sources for blocks', function () {
const parsed = math.parse('1 + 1; 2 + 2\n3 + 3')

// should have a source for each block delimiter
const expected = [
{ index: 5, text: ';'},
{ index: 12, text: '\n'},
]

assert.deepStrictEqual(parsed.sources, expected)
})

it('adds sources for 1D matrices', function () {
const parsed = math.parse('[1, 2, 3]')

// should have a source for brackets and item delimiters
const expected = [
{ index: 0, text: '['},
{ index: 2, text: ','},
{ index: 5, text: ','},
{ index: 8, text: ']'},
]

assert.deepStrictEqual(parsed.sources, expected)
})

it('adds sources for 2D matrices', function () {
const parsed = math.parse('[1, 2; 3, 4]')

// outer matrix has sources for brackets and row delimeters
const expected = [
{ index: 0, text: '['},
{ index: 5, text: ';'},
{ index: 11, text: ']'},
]

assert.deepStrictEqual(parsed.sources, expected)

// inner matrices only have sources for item delimeters
assert.deepStrictEqual(parsed.items[0].sources, [{ index: 2, text: ','}])
assert.deepStrictEqual(parsed.items[1].sources, [{ index: 8, text: ','}])
})

it('adds sources for empty matrices', function () {
const parsed = math.parse('[]')

// should have a source for brackets and item delimiters
const expected = [
{ index: 0, text: '['},
{ index: 1, text: ']'},
]

assert.deepStrictEqual(parsed.sources, expected)
})

it('adds sources for ranges', function () {
const parsed = math.parse('1:2:3')

assert.deepStrictEqual(parsed.start.sources, [{ index: 0, text: '1'}])
assert.deepStrictEqual(parsed.step.sources, [{ index: 2, text: '2'}])
assert.deepStrictEqual(parsed.end.sources, [{ index: 4, text: '3'}])

const delimiters = [
{ index: 1, text: ':'},
{ index: 3, text: ':'},
]

assert.deepStrictEqual(parsed.sources, delimiters)

// implicit start and end sources point to where the value would be

const implicitStart = math.parse(':1')
assert.deepStrictEqual(implicitStart.start.sources, [{ index: 0, text: ''}])

const implicitEnd = math.parse('1:')
assert.deepStrictEqual(implicitEnd.end.sources, [{ index: 2, text: ''}])
})

it('adds sources for parentheses', function () {

// should properly match outer and inner parentheses
const outerParen = math.parse('( 1 + (2 + 3))')
const outerSources = [
{ index: 0, text: '('},
{ index: 13, text: ')'},
]
assert.deepStrictEqual(outerParen.sources, outerSources)

const innerParen = outerParen.content.args[1]
const innerSources = [
{ index: 6, text: '('},
{ index: 12, text: ')'},
]
assert.deepStrictEqual(innerParen.sources, innerSources)
})

it('adds sources for the conditional operator', function () {

// should properly match outer and inner conditional delimeters
const outerCond = math.parse('true ? (false ? 1 : 2) : 3')
const outerSources = [
{ index: 5, text: '?'},
{ index: 23, text: ':'},
]
assert.deepStrictEqual(outerCond.sources, outerSources)

const innerCond = outerCond.trueExpr.content
const innerSources = [
{ index: 14, text: '?'},
{ index: 18, text: ':'},
]
assert.deepStrictEqual(innerCond.sources, innerSources)
})

it('adds sources for assignments', function () {
const parsed = math.parse('val = 42')
assert.deepStrictEqual(parsed.sources, [{ index: 4, text: '=' }])
})

it('adds sources for percents', function () {
const parsed = math.parse('13%')
assert.deepStrictEqual(parsed.sources, [{ index: 2, text: '%' }])
})

it('adds sources for implicit multiplication', function () {
const parsed = math.parse('2a')

// index is where the multiplication symbol would be
assert.deepStrictEqual(parsed.sources, [{ index: 1, text: '' }])
})

it('adds sources for conversions', function () {
const parsedTo = math.parse('1 foot to in')
assert.deepStrictEqual(parsedTo.sources, [{ index: 7, text: 'to' }])

const parsedIn = math.parse('in in 1 foot')
assert.deepStrictEqual(parsedIn.sources, [{ index: 3, text: 'in' }])
})

it('adds sources for unary operators', function () {
const unaryPlus = math.parse('+1')
const unaryMinus = math.parse('-1')
assert.deepStrictEqual(unaryPlus.sources, [{ index: 0, text: '+' }])
assert.deepStrictEqual(unaryMinus.sources, [{ index: 0, text: '-' }])
})

it('adds sources for power operators', function () {
const parsed = math.parse('2^4')
assert.deepStrictEqual(parsed.sources, [{ index: 1, text: '^' }])
})

it('adds sources for constants', function () {
const parsedTrue = math.parse('true')
assert.deepStrictEqual(parsedTrue.sources, [{ index: 0, text: 'true'}])

const parsedNull = math.parse('null')
assert.deepStrictEqual(parsedNull.sources, [{ index: 0, text: 'null'}])

const parsedInfinity = math.parse('Infinity')
assert.deepStrictEqual(parsedInfinity.sources, [{ index: 0, text: 'Infinity'}])

const parsedNaN = math.parse('NaN')
assert.deepStrictEqual(parsedNaN.sources, [{ index: 0, text: 'NaN'}])
})

it('adds sources for function calls', function () {
const parsed = math.parse('foo(1, 2)')

// should have sources for parens and each param delimeter
const sources = [
{ index: 3, text: '(' },
{ index: 5, text: ',' },
{ index: 8, text: ')' },
]
assert.deepStrictEqual(parsed.sources, sources)
})

it('adds sources for string literals', function () {
const singleQuote = math.parse("'hello'")
const singleSources = [
{ index: 0, text: "'" },
{ index: 6, text: "'" },
]
assert.deepStrictEqual(singleQuote.sources, singleSources)

const doubleQuote = math.parse('"hello"')
const doubleSources = [
{ index: 0, text: '"' },
{ index: 6, text: '"' },
]
assert.deepStrictEqual(doubleQuote.sources, doubleSources)
})

it('adds sources for objects', function () {
const parsed = math.parse('{ foo: 13, bar: 25 }')

// sources include brackets, key-value delimiters, and entry delimeters
const expected = [
{ index: 0, text: "{" },
{ index: 5, text: ":" },
{ index: 9, text: "," },
{ index: 14, text: ":" },
{ index: 19, text: "}" },
]
assert.deepStrictEqual(parsed.sources, expected)
})

})
})

0 comments on commit e17be1e

Please sign in to comment.