Skip to content

Commit 7c22ff5

Browse files
authored
Merge branch 'develop' into types/toTex-optional-options
2 parents e142bce + ad847cc commit 7c22ff5

File tree

6 files changed

+120
-70
lines changed

6 files changed

+120
-70
lines changed

AUTHORS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ thetazero <[email protected]>
178178
Alfonso Valenciana <[email protected]>
179179
180180
Glen Whitney <[email protected]>
181-
Divya Yeruva <[email protected]>
182181
Eternal-Rise <[email protected]>
183182
184183
NattapongSiri <[email protected]>
@@ -187,6 +186,7 @@ Abraham <[email protected]>
187186
Sam Estep <[email protected]>
188187
189188
189+
Divya Yeruva <[email protected]>
190190
simlaticak <[email protected]>
191191
Matt Vague <[email protected]>
192192

HISTORY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
- Fix #2526, #2529: improve TypeScript definitions of function `round`, `fix`,
66
`floor`, `ceil`, and `nthRoot`, and improved the number only implementations
77
of those functions (#2531, #2539). Thanks @simlaticak and @gwhitney.
8+
- Fix #2532: matrix index symbol `end` not working when used inside
9+
a sub-expression.
10+
- Fix #2524: In generating AUTHORS list, ignore a list of specific commits
11+
(e.g., to avoid spurious duplicates in list). (#2543)
812

913

1014
# 2022-04-19, version 10.5.0

docs/expressions/syntax.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,8 +549,11 @@ can be replaced by using indexes. Indexes are enclosed in square brackets, and
549549
contain a number or a range for each of the matrix dimensions. A range can have
550550
its start and/or end undefined. When the start is undefined, the range will start
551551
at 1, when the end is undefined, the range will end at the end of the matrix.
552+
552553
There is a context variable `end` available as well to denote the end of the
553-
matrix.
554+
matrix. This variable cannot be used in multiple nested indices. In that case,
555+
`end` will be resolved as the end of the innermost matrix. To solve this,
556+
resolving of the nested index needs to be split in two separate operations.
554557

555558
*IMPORTANT: matrix indexes and ranges work differently from the math.js indexes
556559
in JavaScript: They are one-based with an included upper-bound, similar to most

src/expression/node/IndexNode.js

Lines changed: 21 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
import { isBigNumber, isConstantNode, isNode, isRangeNode, isSymbolNode } from '../../utils/is.js'
21
import { map } from '../../utils/array.js'
3-
import { escape } from '../../utils/string.js'
4-
import { factory } from '../../utils/factory.js'
52
import { getSafeProperty } from '../../utils/customs.js'
3+
import { factory } from '../../utils/factory.js'
4+
import { isArray, isConstantNode, isMatrix, isNode, isString, typeOf } from '../../utils/is.js'
5+
import { escape } from '../../utils/string.js'
66

77
const name = 'IndexNode'
88
const dependencies = [
9-
'Range',
109
'Node',
1110
'size'
1211
]
1312

14-
export const createIndexNode = /* #__PURE__ */ factory(name, dependencies, ({ Range, Node, size }) => {
13+
export const createIndexNode = /* #__PURE__ */ factory(name, dependencies, ({ Node, size }) => {
1514
/**
1615
* @constructor IndexNode
1716
* @extends Node
@@ -71,66 +70,34 @@ export const createIndexNode = /* #__PURE__ */ factory(name, dependencies, ({ Ra
7170
// we can beforehand resolve the zero-based value
7271

7372
// optimization for a simple object property
74-
const evalDimensions = map(this.dimensions, function (range, i) {
75-
if (isRangeNode(range)) {
76-
if (range.needsEnd()) {
77-
// create a range containing end (like '4:end')
78-
const childArgNames = Object.create(argNames)
79-
childArgNames.end = true
80-
81-
const evalStart = range.start._compile(math, childArgNames)
82-
const evalEnd = range.end._compile(math, childArgNames)
83-
const evalStep = range.step
84-
? range.step._compile(math, childArgNames)
85-
: function () { return 1 }
86-
87-
return function evalDimension (scope, args, context) {
88-
const s = size(context).valueOf()
89-
const childArgs = Object.create(args)
90-
childArgs.end = s[i]
91-
92-
return createRange(
93-
evalStart(scope, childArgs, context),
94-
evalEnd(scope, childArgs, context),
95-
evalStep(scope, childArgs, context)
96-
)
97-
}
98-
} else {
99-
// create range
100-
const evalStart = range.start._compile(math, argNames)
101-
const evalEnd = range.end._compile(math, argNames)
102-
const evalStep = range.step
103-
? range.step._compile(math, argNames)
104-
: function () { return 1 }
105-
106-
return function evalDimension (scope, args, context) {
107-
return createRange(
108-
evalStart(scope, args, context),
109-
evalEnd(scope, args, context),
110-
evalStep(scope, args, context)
111-
)
112-
}
113-
}
114-
} else if (isSymbolNode(range) && range.name === 'end') {
115-
// SymbolNode 'end'
73+
const evalDimensions = map(this.dimensions, function (dimension, i) {
74+
const needsEnd = dimension
75+
.filter(node => node.isSymbolNode && node.name === 'end')
76+
.length > 0
77+
78+
if (needsEnd) {
79+
// SymbolNode 'end' is used inside the index,
80+
// like in `A[end]` or `A[end - 2]`
11681
const childArgNames = Object.create(argNames)
11782
childArgNames.end = true
11883

119-
const evalRange = range._compile(math, childArgNames)
84+
const _evalDimension = dimension._compile(math, childArgNames)
12085

12186
return function evalDimension (scope, args, context) {
87+
if (!isMatrix(context) && !isArray(context) && !isString(context)) {
88+
throw new TypeError('Cannot resolve "end": ' +
89+
'context must be a Matrix, Array, or string but is ' + typeOf(context))
90+
}
91+
12292
const s = size(context).valueOf()
12393
const childArgs = Object.create(args)
12494
childArgs.end = s[i]
12595

126-
return evalRange(scope, childArgs, context)
96+
return _evalDimension(scope, childArgs, context)
12797
}
12898
} else {
129-
// ConstantNode
130-
const evalRange = range._compile(math, argNames)
131-
return function evalDimension (scope, args, context) {
132-
return evalRange(scope, args, context)
133-
}
99+
// SymbolNode `end` not used
100+
return dimension._compile(math, argNames)
134101
}
135102
})
136103

@@ -265,14 +232,5 @@ export const createIndexNode = /* #__PURE__ */ factory(name, dependencies, ({ Ra
265232
: ('_{' + dimensions.join(',') + '}')
266233
}
267234

268-
// helper function to create a Range from start, step and end
269-
function createRange (start, end, step) {
270-
return new Range(
271-
isBigNumber(start) ? start.toNumber() : start,
272-
isBigNumber(end) ? end.toNumber() : end,
273-
isBigNumber(step) ? step.toNumber() : step
274-
)
275-
}
276-
277235
return IndexNode
278236
}, { isClass: true, isNode: true })

test/unit-tests/expression/node/AccessorNode.test.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,27 @@ describe('AccessorNode', function () {
7676
assert.deepStrictEqual(expr.evaluate(scope), [[3, 4]])
7777
})
7878

79+
it('should compile a AccessorNode with "end" in an expression', function () {
80+
const a = new SymbolNode('a')
81+
const index = new IndexNode([
82+
new OperatorNode(
83+
'-',
84+
'subtract',
85+
[
86+
new SymbolNode('end'),
87+
new ConstantNode(2)
88+
]
89+
)
90+
])
91+
const n = new AccessorNode(a, index)
92+
const expr = n.compile()
93+
94+
const scope = {
95+
a: [1, 2, 3, 4]
96+
}
97+
assert.deepStrictEqual(expr.evaluate(scope), 2)
98+
})
99+
79100
it('should compile a AccessorNode with a property', function () {
80101
const a = new SymbolNode('a')
81102
const index = new IndexNode([new ConstantNode('b')])
@@ -185,6 +206,46 @@ describe('AccessorNode', function () {
185206
assert.deepStrictEqual(expr.evaluate(scope), [[3, 4]])
186207
})
187208

209+
it('should use the inner context when using "end" in a nested index', function () {
210+
// A[B[end]]
211+
const node = new AccessorNode(
212+
new SymbolNode('A'),
213+
new IndexNode([
214+
new AccessorNode(
215+
new SymbolNode('B'),
216+
new IndexNode([
217+
new SymbolNode('end')
218+
])
219+
)
220+
])
221+
)
222+
223+
// here, end should resolve to the end of B, which is 3 (whilst the end of A is 6)
224+
const expr = node.compile()
225+
const scope = {
226+
A: [4, 5, 6, 7, 8, 9],
227+
B: [1, 2, 3]
228+
}
229+
assert.deepStrictEqual(expr.evaluate(scope), 6)
230+
})
231+
232+
it('should give a proper error message when using "end" inside the index of an object', function () {
233+
const obj = new SymbolNode('value')
234+
const index = new IndexNode([
235+
new SymbolNode('end')
236+
])
237+
const n = new AccessorNode(obj, index)
238+
const expr = n.compile()
239+
240+
assert.throws(function () {
241+
expr.evaluate({ value: { end: true } })
242+
}, /TypeError: Cannot resolve "end": context must be a Matrix, Array, or string but is Object/)
243+
244+
assert.throws(function () {
245+
expr.evaluate({ value: 42 })
246+
}, /TypeError: Cannot resolve "end": context must be a Matrix, Array, or string but is number/)
247+
})
248+
188249
it('should compile a AccessorNode with bignumber setting', function () {
189250
const a = new bigmath.SymbolNode('a')
190251
const b = new bigmath.ConstantNode(2)

tools/update-authors.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ class CaseIndifferentMap {
2525
const log = spawn(
2626
'git',
2727
// Inspect author name/email and body.
28-
['log', '--reverse', '--format=Author: %aN <%aE>\n%b'], {
28+
['log', '--reverse'],
29+
{
2930
stdio: ['inherit', 'pipe', 'inherit']
3031
})
3132
const rl = readline.createInterface({ input: log.stdout })
@@ -77,17 +78,40 @@ const mailmap = new CaseIndifferentMap()
7778

7879
const seen = new Set()
7980

81+
// Commits from which we do not want the author to pop up in the AUTHORS list,
82+
// for example because the commit was done with a wrong git user account
83+
const ignoreCommits = [
84+
'43d4551e7c19f51d30e71b35009437c7ec6491f0'
85+
]
86+
let currentCommit
87+
8088
// Support regular git author metadata, as well as `Author:` and
8189
// `Co-authored-by:` in the message body. Both have been used in the past
8290
// to indicate multiple authors per commit, with the latter standardized
8391
// by GitHub now.
8492
const authorRe =
85-
/(^Author:|^Co-authored-by:)\s+(?<author>[^<]+)\s+(?<email><[^>]+>)/i
93+
/(^Author:|^\s*Co-authored-by:)\s+(?<author>[^<]+)\s+(?<email><[^>]+>)/i
94+
95+
// Commit line regex. Example: "commit 123456"
96+
const commitRe = /^commit (?<commit>.+)$/i
97+
8698
rl.on('line', (line) => {
87-
const match = line.match(authorRe)
88-
if (!match) return
99+
const commitMatch = line.match(commitRe)
100+
if (commitMatch) {
101+
currentCommit = commitMatch.groups.commit
102+
return
103+
}
104+
105+
if (ignoreCommits.includes(currentCommit)) {
106+
return
107+
}
108+
109+
const authorMatch = line.match(authorRe)
110+
if (!authorMatch) {
111+
return
112+
}
89113

90-
const { author, email } = match.groups
114+
const { author, email } = authorMatch.groups
91115

92116
if (seen.has(email) ||
93117
/@chromium\.org/.test(email) ||

0 commit comments

Comments
 (0)