@khanacademy/kas 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/LICENSE.txt +21 -0
- package/README.md +94 -0
- package/dist/es/index.js +2 -0
- package/dist/es/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.flow +2 -0
- package/dist/index.js.map +1 -0
- package/experimenter.html +75 -0
- package/package.json +38 -0
- package/src/__genfiles__/parser.js +840 -0
- package/src/__genfiles__/unitparser.js +679 -0
- package/src/__tests__/checking-form_test.js +76 -0
- package/src/__tests__/comparing_test.js +322 -0
- package/src/__tests__/compilation_test.js +97 -0
- package/src/__tests__/evaluating_test.js +73 -0
- package/src/__tests__/index_test.js +364 -0
- package/src/__tests__/parsing_test.js +480 -0
- package/src/__tests__/rendering_test.js +272 -0
- package/src/__tests__/transforming_test.js +331 -0
- package/src/__tests__/units_test.js +188 -0
- package/src/compare.js +69 -0
- package/src/index.js +2 -0
- package/src/nodes.js +3504 -0
- package/src/parser-generator.js +212 -0
- package/src/unitvalue.jison +161 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import _ from "underscore";
|
|
2
|
+
|
|
3
|
+
import * as KAS from "../index.js";
|
|
4
|
+
|
|
5
|
+
expect.extend({
|
|
6
|
+
toHaveNorm(input: string, reference: string) {
|
|
7
|
+
var actual = KAS.parse(input).expr.normalize().print();
|
|
8
|
+
var expected = KAS.parse(reference).expr.normalize().print();
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
pass: actual === expected,
|
|
12
|
+
message: () => `${input} is the same as ${reference}`,
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
toHaveStripNorm(input: string, reference: string) {
|
|
16
|
+
var actual = KAS.parse(input).expr.strip().normalize().print();
|
|
17
|
+
var expected = KAS.parse(reference).expr.strip().normalize().print();
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
pass: actual === expected,
|
|
21
|
+
message: () => `${input} is the same as ${reference}`,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("checking form", () => {
|
|
27
|
+
test("normalize", () => {
|
|
28
|
+
expect("ab").toHaveNorm("ba");
|
|
29
|
+
expect("(ab)c").toHaveNorm("(cb)a");
|
|
30
|
+
|
|
31
|
+
var forms = [
|
|
32
|
+
"(6x+1)(x-1)",
|
|
33
|
+
"(1+6x)(x-1)",
|
|
34
|
+
"(6x+1)(-1+x)",
|
|
35
|
+
"(1+6x)(-1+x)",
|
|
36
|
+
"(x-1)(6x+1)",
|
|
37
|
+
"(x-1)(1+6x)",
|
|
38
|
+
"(-1+x)(6x+1)",
|
|
39
|
+
"(-1+x)(1+6x)"
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
_.each(forms, function(form) {
|
|
43
|
+
expect(forms[0]).toHaveNorm(form);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("strip then normalize", () => {
|
|
48
|
+
expect("ab").toHaveStripNorm("ba");
|
|
49
|
+
expect("(ab)c").toHaveStripNorm("(cb)a");
|
|
50
|
+
|
|
51
|
+
var forms = [
|
|
52
|
+
"(6x+1)(x-1)",
|
|
53
|
+
"(1+6x)(x-1)",
|
|
54
|
+
"(6x+1)(-1+x)",
|
|
55
|
+
"(1+6x)(-1+x)",
|
|
56
|
+
"(-6x-1)(-x+1)",
|
|
57
|
+
"(-1-6x)(-x+1)",
|
|
58
|
+
"(-6x-1)(1-x)",
|
|
59
|
+
"(-1-6x)(1-x)",
|
|
60
|
+
"(x-1)(6x+1)",
|
|
61
|
+
"(x-1)(1+6x)",
|
|
62
|
+
"(-1+x)(6x+1)",
|
|
63
|
+
"(-1+x)(1+6x)",
|
|
64
|
+
"(-x+1)(-6x-1)",
|
|
65
|
+
"(-x+1)(-1-6x)",
|
|
66
|
+
"(1-x)(-6x-1)",
|
|
67
|
+
"(1-x)(-1-6x)",
|
|
68
|
+
"-(6x+1)(1-x)",
|
|
69
|
+
"-(-6x-1)(x-1)"
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
_.each(forms, function(form) {
|
|
73
|
+
expect(forms[0]).toHaveStripNorm(form);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import _ from "underscore";
|
|
2
|
+
|
|
3
|
+
import * as KAS from "../index.js";
|
|
4
|
+
|
|
5
|
+
expect.extend({
|
|
6
|
+
toEqualExpr(input: string, expected: string) {
|
|
7
|
+
const inputExpr = KAS.parse(input, {functions: ["f", "g", "h"]}).expr;
|
|
8
|
+
const expectedExpr = KAS.parse(expected, {
|
|
9
|
+
functions: ["f", "g", "h"],
|
|
10
|
+
}).expr;
|
|
11
|
+
|
|
12
|
+
const actual = KAS.compare(inputExpr, expectedExpr, {form: false});
|
|
13
|
+
|
|
14
|
+
if (this.isNot) {
|
|
15
|
+
return actual.equal
|
|
16
|
+
? {pass: true}
|
|
17
|
+
: {
|
|
18
|
+
pass: false,
|
|
19
|
+
message: () => `${input} is NOT the same as ${expected}`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return actual.equal
|
|
23
|
+
? {pass: true}
|
|
24
|
+
: {
|
|
25
|
+
pass: false,
|
|
26
|
+
message: () => `${input} is the same as ${expected}`,
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
toEqualExprAndForm(input: string, expected: string) {
|
|
30
|
+
const inputExpr = KAS.parse(input, {functions: ["f", "g", "h"]}).expr;
|
|
31
|
+
const expectedExpr = KAS.parse(expected, {
|
|
32
|
+
functions: ["f", "g", "h"],
|
|
33
|
+
}).expr;
|
|
34
|
+
|
|
35
|
+
const actual = KAS.compare(inputExpr, expectedExpr, {form: true});
|
|
36
|
+
|
|
37
|
+
if (this.isNot) {
|
|
38
|
+
return actual.equal
|
|
39
|
+
? {pass: true}
|
|
40
|
+
: {
|
|
41
|
+
pass: false,
|
|
42
|
+
message: () => `${input} is NOT the same as ${expected}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return actual.equal
|
|
46
|
+
? {pass: true}
|
|
47
|
+
: {
|
|
48
|
+
pass: false,
|
|
49
|
+
message: () => `${input} is the same as ${expected}`,
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("comparing", () => {
|
|
55
|
+
test("evaluate only", () => {
|
|
56
|
+
expect("2+2").toEqualExpr("4");
|
|
57
|
+
expect("a(b+c)").toEqualExpr("ab+ac");
|
|
58
|
+
expect("a/b").toEqualExpr("a*b^-1");
|
|
59
|
+
|
|
60
|
+
expect("1.2^2").toEqualExpr("1.44");
|
|
61
|
+
expect("1.3^2").toEqualExpr("1.69");
|
|
62
|
+
expect("1.4^2").toEqualExpr("1.96");
|
|
63
|
+
expect("1.5^2").toEqualExpr("2.25");
|
|
64
|
+
|
|
65
|
+
expect("1.2345^2").toEqualExpr("1.52399025");
|
|
66
|
+
expect("1.2345*1.2345").toEqualExpr("1.52399025");
|
|
67
|
+
expect("(1+.2345)^2").toEqualExpr("1.52399025");
|
|
68
|
+
expect("(-5)^(1/3)").toEqualExpr("-1.709975946");
|
|
69
|
+
expect("(-5)^(2/6)").toEqualExpr("-1.709975946");
|
|
70
|
+
expect("(-5)^(4/3)").toEqualExpr("8.549879733");
|
|
71
|
+
expect("(-5)^(-1/3)").toEqualExpr("-0.584803547");
|
|
72
|
+
|
|
73
|
+
expect("(-5)^(1/5)").toEqualExpr("-1.379729661");
|
|
74
|
+
expect("(-5)^(0.2)").toEqualExpr("-1.379729661");
|
|
75
|
+
|
|
76
|
+
expect("x^(1/5)").toEqualExpr("x^(0.2)");
|
|
77
|
+
expect("x^(8/5)").toEqualExpr("x^(1.6)");
|
|
78
|
+
|
|
79
|
+
expect("(1-x)(-1-6x)").toEqualExpr("(6x+1)(x-1)");
|
|
80
|
+
expect("y=x").not.toEqualExpr("x");
|
|
81
|
+
expect("x").not.toEqualExpr("y=x");
|
|
82
|
+
expect("y=x").toEqualExpr("y=x");
|
|
83
|
+
expect("y=x").toEqualExpr("x=y");
|
|
84
|
+
expect("y=x").toEqualExpr("-y=-x");
|
|
85
|
+
expect("y=x").toEqualExpr("-x=-y");
|
|
86
|
+
expect("y=x").not.toEqualExpr("y=-x");
|
|
87
|
+
expect("y=x").not.toEqualExpr("-y=x");
|
|
88
|
+
expect("y=x").not.toEqualExpr("y=/=x");
|
|
89
|
+
expect("y<x").toEqualExpr("x>y");
|
|
90
|
+
expect("y<=x").toEqualExpr("x>=y");
|
|
91
|
+
expect("y>x").toEqualExpr("x<y");
|
|
92
|
+
expect("y>x").not.toEqualExpr("x>y");
|
|
93
|
+
expect("y>=x").toEqualExpr("x<=y");
|
|
94
|
+
expect("a+b<c-d").toEqualExpr("a+b-c+d<0");
|
|
95
|
+
|
|
96
|
+
expect("y=mx+b").toEqualExpr("-b-mx=-y");
|
|
97
|
+
expect("y=mx+b").toEqualExpr("y-b=mx");
|
|
98
|
+
|
|
99
|
+
// all of these normalize to the same expression, set to zero
|
|
100
|
+
const forms = [
|
|
101
|
+
"y=2x-5",
|
|
102
|
+
"2x-5=y",
|
|
103
|
+
"2x-y=5",
|
|
104
|
+
"(y+5)/2=x",
|
|
105
|
+
"(y+5)/x=2",
|
|
106
|
+
"1/2(y+5)=x",
|
|
107
|
+
".5(y+5)=x",
|
|
108
|
+
"y-3=2(x-4)",
|
|
109
|
+
"2y=4x-10",
|
|
110
|
+
"yz=2xz-5z",
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
_.each(forms, (form) => {
|
|
114
|
+
expect(forms[0]).toEqualExpr(form);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect("3y=2x-15").toEqualExpr("3/2(y+5)=x");
|
|
118
|
+
|
|
119
|
+
const forms2 = ["1/3p-3=114", "1/3p=117", "p=351", "p-351=0"];
|
|
120
|
+
|
|
121
|
+
_.each(forms2, (form) => {
|
|
122
|
+
expect(forms2[0]).toEqualExpr(form);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect("x").toEqualExpr("xy/y");
|
|
126
|
+
expect("e^x").toEqualExpr("e^x");
|
|
127
|
+
expect("e^x").not.toEqualExpr("e^x + 1");
|
|
128
|
+
|
|
129
|
+
const forms3 = [
|
|
130
|
+
"x+x+x+6=12",
|
|
131
|
+
"x+2x+6=12",
|
|
132
|
+
"3x+6=12",
|
|
133
|
+
"x+2x=6",
|
|
134
|
+
"2x=6-x",
|
|
135
|
+
"3x=6",
|
|
136
|
+
"x=2",
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
_.each(forms3, function (form) {
|
|
140
|
+
expect(forms3[0]).toEqualExpr(form);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect("100/55.6=t").toEqualExpr("t=100/55.6");
|
|
144
|
+
expect("100/1.6^2=t").toEqualExpr("t=100/1.6^2");
|
|
145
|
+
expect("7/3x+x=15").toEqualExpr("(2+1/3)x+x=15");
|
|
146
|
+
expect("7/3x+x=15").not.toEqualExpr("(1+1/3)x+x=15");
|
|
147
|
+
|
|
148
|
+
// Symmetric equations
|
|
149
|
+
expect("x^2+y^2=r^2").toEqualExpr("r^2=x^2+y^2");
|
|
150
|
+
expect("23^1.5=110.304").toEqualExpr("110.304=23^1.5");
|
|
151
|
+
|
|
152
|
+
// TODO(alex): make sure that I have both positive and negative
|
|
153
|
+
// test cases for all functionality
|
|
154
|
+
expect("6.12*10^-2").toEqualExpr("6.12*10^-2");
|
|
155
|
+
expect("6.12*10^-2").not.toEqualExpr("6.12*10^-6");
|
|
156
|
+
|
|
157
|
+
expect("3^-x").toEqualExpr("(1/3)^x");
|
|
158
|
+
expect("(1/3)^-x").toEqualExpr("3^x");
|
|
159
|
+
expect("(3)^-x").not.toEqualExpr("3^x");
|
|
160
|
+
|
|
161
|
+
expect("5.6=x+0.4+5.2").toEqualExpr("5.6=x+0.4+5.2");
|
|
162
|
+
|
|
163
|
+
// Reciprocal trig functions
|
|
164
|
+
expect("csc x").toEqualExpr("1/sin x");
|
|
165
|
+
expect("sec x").toEqualExpr("1/cos x");
|
|
166
|
+
expect("cot x").toEqualExpr("1/tan x");
|
|
167
|
+
expect("arccsc x").toEqualExpr("arcsin (1/x)");
|
|
168
|
+
expect("arcsec x").toEqualExpr("arccos (1/x)");
|
|
169
|
+
expect("arccot x").toEqualExpr("arctan (1/x)");
|
|
170
|
+
|
|
171
|
+
// Reciprocal hyperbolic trig functions
|
|
172
|
+
expect("csch x").toEqualExpr("1/sinh x");
|
|
173
|
+
expect("sech x").toEqualExpr("1/cosh x");
|
|
174
|
+
expect("coth x").toEqualExpr("1/tanh x");
|
|
175
|
+
|
|
176
|
+
// Make sure trig functions that are the same for all integer values
|
|
177
|
+
// are not the same
|
|
178
|
+
expect("-2sin(pi x) + 4").not.toEqualExpr("4");
|
|
179
|
+
expect("2sin(pi x) + 4").not.toEqualExpr("-2sin(pi x) + 4");
|
|
180
|
+
expect("sin(pi x)").not.toEqualExpr("0");
|
|
181
|
+
expect("0").not.toEqualExpr("sin(pi x)");
|
|
182
|
+
expect("cos(pi x)").not.toEqualExpr("cos(2 pi x)");
|
|
183
|
+
expect("sin(pi x)").not.toEqualExpr("sin(500pi x)");
|
|
184
|
+
expect("sin(500pi x)").not.toEqualExpr("sin(pi x)");
|
|
185
|
+
|
|
186
|
+
// Check that floating point error isn't killing us
|
|
187
|
+
// TODO(jack): These don't seem to test much; make better tests
|
|
188
|
+
expect("0").toEqualExpr("sin(7pi)");
|
|
189
|
+
expect("sin(7pi)").toEqualExpr("0");
|
|
190
|
+
expect("0").toEqualExpr("sin(500pi)");
|
|
191
|
+
expect("sin(500pi)").toEqualExpr("0");
|
|
192
|
+
|
|
193
|
+
// Handle denominators the same way regardless of a fraction's format
|
|
194
|
+
expect("x=1.2^2").toEqualExpr("x=1.44");
|
|
195
|
+
expect("x=1.2^2").toEqualExpr("x=36/25");
|
|
196
|
+
expect("x=1.44").toEqualExpr("x=36/25");
|
|
197
|
+
expect("x=1.44").not.toEqualExpr("x=35/25");
|
|
198
|
+
|
|
199
|
+
expect("x=1.2^(2y)").toEqualExpr("x=1.44^y");
|
|
200
|
+
expect("x=1.2^(2y)").toEqualExpr("x=(36/25)^y");
|
|
201
|
+
expect("x=1.44^y").toEqualExpr("x=(36/25)^y");
|
|
202
|
+
|
|
203
|
+
expect("x=1.3^2").toEqualExpr("x=1.69");
|
|
204
|
+
expect("x=1.4^2").toEqualExpr("x=1.96");
|
|
205
|
+
expect("x=1.5^2").toEqualExpr("x=2.25");
|
|
206
|
+
expect("x=1.5^2").toEqualExpr("x=2.25");
|
|
207
|
+
|
|
208
|
+
expect("x=1.2345^2").toEqualExpr("x=1.52399025");
|
|
209
|
+
expect("x=1.2345*1.2345").toEqualExpr("x=1.52399025");
|
|
210
|
+
expect("x=(1+.2345)^2").toEqualExpr("x=1.52399025");
|
|
211
|
+
expect("x=(1+.2345)^2").not.toEqualExpr("x=1.52399022");
|
|
212
|
+
|
|
213
|
+
// Varying small and large comparisons
|
|
214
|
+
expect("1.1234567891235 * 10^200").toEqualExpr(
|
|
215
|
+
"1.1234567891234 * 10^200",
|
|
216
|
+
);
|
|
217
|
+
expect("1.1234567891235 * 10^200").not.toEqualExpr("0.10");
|
|
218
|
+
expect("0.10").not.toEqualExpr("1.1234567891235 * 10^200");
|
|
219
|
+
expect("0.50").not.toEqualExpr("0.51");
|
|
220
|
+
expect("0.51").not.toEqualExpr("0.50");
|
|
221
|
+
|
|
222
|
+
expect("1.00").toEqualExpr("1.00");
|
|
223
|
+
expect("0.9").not.toEqualExpr("1.1");
|
|
224
|
+
expect("1.1").not.toEqualExpr("0.9");
|
|
225
|
+
|
|
226
|
+
// Real-world examples of equivalent equations that are now accepted
|
|
227
|
+
expect("12(1-r)^2+58(1-r)=10").toEqualExpr("(1-r)(70-12r)=10");
|
|
228
|
+
expect("720m+480(m-5)=42000").toEqualExpr("720m+480m-2400=42000");
|
|
229
|
+
expect("2w+50/w=25").toEqualExpr("w(12.5-w)=25");
|
|
230
|
+
expect("(n*(8+4n))/2>19206").toEqualExpr("6n+2n(n-1)>19206");
|
|
231
|
+
|
|
232
|
+
// Correctly handle exponents with negative bases and variables in exponent
|
|
233
|
+
expect("( 2)^n").not.toEqualExpr("( 2)^(n-1)");
|
|
234
|
+
expect("( 2)^n").toEqualExpr("( 2)^(n)");
|
|
235
|
+
expect("( 2)^n").not.toEqualExpr("( 2)^(n+1)");
|
|
236
|
+
|
|
237
|
+
expect("(-2)^n").not.toEqualExpr("(-2)^(n-1)");
|
|
238
|
+
expect("(-2)^n").toEqualExpr("(-2)^(n)");
|
|
239
|
+
expect("(-2)^n").not.toEqualExpr("(-2)^(n+1)");
|
|
240
|
+
|
|
241
|
+
expect("(-2)^(a)").not.toEqualExpr("(-2)^(ab)");
|
|
242
|
+
expect("(-2)^(a)").not.toEqualExpr("(-2)^(a^b)");
|
|
243
|
+
|
|
244
|
+
// This is incorrect, but accurately captures the current behavior
|
|
245
|
+
// See comment in `Expr.compare()` for more details
|
|
246
|
+
expect("(-2)^(n+0.1)").toEqualExpr("(-2)^(n+1.1)");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("simplify can't yet handle these", () => {
|
|
250
|
+
expect("sin(x + 2pi)").toEqualExpr("sin(x)");
|
|
251
|
+
expect("y = sin(x + 2pi)").toEqualExpr("y = sin(x)");
|
|
252
|
+
expect("sin^2(x)+cos^2(x)").toEqualExpr("x/x");
|
|
253
|
+
expect("y = sin^2(x)+cos^2(x)").toEqualExpr("y = x/x");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("partially evaluating functions", () => {
|
|
257
|
+
expect("f(x)").toEqualExpr("f(x)");
|
|
258
|
+
expect("f(x)").not.toEqualExpr("g(x)");
|
|
259
|
+
expect("f(g(x))").toEqualExpr("f(g(x))");
|
|
260
|
+
expect("sin(f(3x-x))/cos(f(x+x))").toEqualExpr("tan(f(2x))");
|
|
261
|
+
expect("f(x) = sin(x + 2pi)").toEqualExpr("f(x) = sin(x)");
|
|
262
|
+
// NOTE(kevinb): This test is flaky, because Expr.prototype.compare
|
|
263
|
+
// is non-deterministic. When comparing expressions this normally
|
|
264
|
+
// wouldn't be an issue because we could just evaluate the different
|
|
265
|
+
// variables at different point and then check that the difference
|
|
266
|
+
// between the expressions is always less than some very small number.
|
|
267
|
+
// In this case though, we convert equations to expressions, e.g.
|
|
268
|
+
// `f(x) = 1` is converted to `1-f(x)`, but because `f` is a function,
|
|
269
|
+
// we can't evaluate it. One possible solution to this would be
|
|
270
|
+
// to isolate `f(x)` in each expression and then check that the
|
|
271
|
+
// expressions that they're equal to are equal. In this case that
|
|
272
|
+
// would be `sin^2(x)+cos^2(x)` and `1`.
|
|
273
|
+
// TODO(TP-11651): Update compare to isolate functions on wide side
|
|
274
|
+
// before comparing expressions on the other side.
|
|
275
|
+
// expect("f(x) = sin^2(x)+cos^2(x)").toEqualExpr("f(x) = 1");
|
|
276
|
+
expect("f(x) = ln|x|+c").toEqualExpr("f(x)-ln|x|-c = 0");
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("evaluating and comparing form", () => {
|
|
280
|
+
expect("ab").toEqualExprAndForm("ba");
|
|
281
|
+
expect("(ab)c").toEqualExprAndForm("(cb)a");
|
|
282
|
+
|
|
283
|
+
const forms = [
|
|
284
|
+
"(6x+1)(x-1)",
|
|
285
|
+
"(1+6x)(x-1)",
|
|
286
|
+
"(6x+1)(-1+x)",
|
|
287
|
+
"(1+6x)(-1+x)",
|
|
288
|
+
"(-6x-1)(-x+1)",
|
|
289
|
+
"(-1-6x)(-x+1)",
|
|
290
|
+
"(-6x-1)(1-x)",
|
|
291
|
+
"(-1-6x)(1-x)",
|
|
292
|
+
"(x-1)(6x+1)",
|
|
293
|
+
"(x-1)(1+6x)",
|
|
294
|
+
"(-1+x)(6x+1)",
|
|
295
|
+
"(-1+x)(1+6x)",
|
|
296
|
+
"(-x+1)(-6x-1)",
|
|
297
|
+
"(-x+1)(-1-6x)",
|
|
298
|
+
"(1-x)(-6x-1)",
|
|
299
|
+
"(1-x)(-1-6x)",
|
|
300
|
+
"-(6x+1)(1-x)",
|
|
301
|
+
"-(-6x-1)(x-1)",
|
|
302
|
+
];
|
|
303
|
+
|
|
304
|
+
_.each(forms, function (form) {
|
|
305
|
+
expect(forms[0]).toEqualExprAndForm(form);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
expect("(6x+1)(x+1)").not.toEqualExprAndForm("(6x+1)(x-1)");
|
|
309
|
+
expect("a-b-c").not.toEqualExprAndForm("c+b+a");
|
|
310
|
+
|
|
311
|
+
expect("(6x+1)(x+1)").not.toEqualExprAndForm("(6x+1)(x-1)");
|
|
312
|
+
expect("a-b-c").not.toEqualExprAndForm("c+b+a");
|
|
313
|
+
expect("mx+b").toEqualExprAndForm("b+mx");
|
|
314
|
+
|
|
315
|
+
expect("y=mx+b").toEqualExprAndForm("-b-mx=-y");
|
|
316
|
+
expect("y=mx+b").not.toEqualExprAndForm("y-b=mx");
|
|
317
|
+
|
|
318
|
+
expect("y-3=2(x-4)").not.toEqualExprAndForm("y=2x-5");
|
|
319
|
+
expect("y-3=2(x-4)").not.toEqualExprAndForm("2x-y=5");
|
|
320
|
+
expect("y=2x-5").not.toEqualExprAndForm("2x-y=5");
|
|
321
|
+
});
|
|
322
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import _ from "underscore";
|
|
2
|
+
|
|
3
|
+
import * as KAS from "../index.js";
|
|
4
|
+
|
|
5
|
+
expect.extend({
|
|
6
|
+
toCompileAs(
|
|
7
|
+
input: string,
|
|
8
|
+
expected: number,
|
|
9
|
+
vars?: {[string]: string | number | (number) => number} = {},
|
|
10
|
+
) {
|
|
11
|
+
const functions = Object.keys(vars).filter(k => typeof vars[k] === 'function');
|
|
12
|
+
const func = KAS.parse(input, {functions}).expr.compile();
|
|
13
|
+
|
|
14
|
+
const actual = func(vars);
|
|
15
|
+
|
|
16
|
+
return Math.abs(actual - expected) < 1e-9
|
|
17
|
+
? {pass: true}
|
|
18
|
+
: {
|
|
19
|
+
pass: false,
|
|
20
|
+
message: () =>
|
|
21
|
+
`${input} should evaluate to ${expected}, was ${actual}; by func: ${func.toString()}`,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("compilation", () => {
|
|
27
|
+
test("empty", () => {
|
|
28
|
+
expect("").toCompileAs(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("simple expressions", () => {
|
|
32
|
+
expect("1+2+3+4").toCompileAs(10);
|
|
33
|
+
expect("1+2-3+4").toCompileAs(4);
|
|
34
|
+
expect("1*2*3*4").toCompileAs(24);
|
|
35
|
+
expect("1*2/3*4").toCompileAs(2 + 2 / 3);
|
|
36
|
+
expect("4^3^2^1").toCompileAs(262144);
|
|
37
|
+
expect("-1").toCompileAs(-1);
|
|
38
|
+
expect("--1").toCompileAs(1);
|
|
39
|
+
expect("---1").toCompileAs(-1);
|
|
40
|
+
expect("2^-2").toCompileAs(0.25);
|
|
41
|
+
expect("8^(1/3)").toCompileAs(2);
|
|
42
|
+
expect("0^0").toCompileAs(1);
|
|
43
|
+
expect(".25*4").toCompileAs(1);
|
|
44
|
+
expect("ln e").toCompileAs(1);
|
|
45
|
+
expect("log 10").toCompileAs(1);
|
|
46
|
+
expect("log_2 2").toCompileAs(1);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("variable expressions", () => {
|
|
50
|
+
expect("x").toCompileAs(3, {x: 3});
|
|
51
|
+
expect("x^2").toCompileAs(9, {x: 3});
|
|
52
|
+
expect("(x^2+y^2)^.5").toCompileAs(5, {x: 3, y: 4});
|
|
53
|
+
expect("log x_0").toCompileAs(1, {x_0: 10});
|
|
54
|
+
expect("log x_0 + log x_1").toCompileAs(3, {x_0: 10, x_1: 100});
|
|
55
|
+
expect("log x_42").toCompileAs(1, {x_42: 10});
|
|
56
|
+
expect("x_a + x_bc").toCompileAs(7, {x_a: 1, x_b: 2, c: 3});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("function expressions", () => {
|
|
60
|
+
expect("f(2)").toCompileAs(
|
|
61
|
+
4,
|
|
62
|
+
{
|
|
63
|
+
f: function (x) {
|
|
64
|
+
return 2 * x;
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
expect("f(4+8)").toCompileAs(
|
|
69
|
+
48,
|
|
70
|
+
{
|
|
71
|
+
f: function (x) {
|
|
72
|
+
return 4 * x;
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
expect("f(x-1)-f(x)").toCompileAs(
|
|
77
|
+
-7,
|
|
78
|
+
{
|
|
79
|
+
f: function (x) {
|
|
80
|
+
return Math.pow(x, 3);
|
|
81
|
+
},
|
|
82
|
+
x: 2,
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("trig expressions", () => {
|
|
88
|
+
expect("-2sin(pi x) + 4").toCompileAs(4, {x: 32});
|
|
89
|
+
expect("sin(pi x)").toCompileAs(0, {x: 6});
|
|
90
|
+
expect("sin(x)").toCompileAs(1, {x: Math.PI / 2});
|
|
91
|
+
expect("sin(pi x)").toCompileAs(-1, {x: 3 / 2});
|
|
92
|
+
expect("cos(x)").toCompileAs(1, {x: 0});
|
|
93
|
+
expect("cos x").toCompileAs(-1, {x: Math.PI});
|
|
94
|
+
expect("tan(x)").toCompileAs(0, {x: 0});
|
|
95
|
+
expect("tan x").toCompileAs(1, {x: Math.PI / 4});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import _ from "underscore";
|
|
2
|
+
|
|
3
|
+
import * as KAS from "../index.js";
|
|
4
|
+
|
|
5
|
+
expect.extend({
|
|
6
|
+
toEvaluateAs(
|
|
7
|
+
input: string,
|
|
8
|
+
expected: number,
|
|
9
|
+
vars?: {[string]: string | number} = {},
|
|
10
|
+
functions?: $ReadOnlyArray<string>,
|
|
11
|
+
) {
|
|
12
|
+
const actual = KAS.parse(input, {functions: functions}).expr.eval(
|
|
13
|
+
vars,
|
|
14
|
+
{functions: functions},
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
if (actual !== expected) {
|
|
18
|
+
return {
|
|
19
|
+
pass: actual !== expected,
|
|
20
|
+
message: () => `${input} evaluates as ${expected}`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return {pass: !this.isNot};
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("evaluating", () => {
|
|
29
|
+
test("empty", () => {
|
|
30
|
+
expect("").toEvaluateAs(0);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("simple expressions", () => {
|
|
34
|
+
expect("1+2+3+4").toEvaluateAs(10);
|
|
35
|
+
expect("1+2-3+4").toEvaluateAs(4);
|
|
36
|
+
expect("1*2*3*4").toEvaluateAs(24);
|
|
37
|
+
expect("1*2/3*4").toEvaluateAs(2 + 2 / 3);
|
|
38
|
+
expect("4^3^2^1").toEvaluateAs(262144);
|
|
39
|
+
expect("-1").toEvaluateAs(-1);
|
|
40
|
+
expect("--1").toEvaluateAs(1);
|
|
41
|
+
expect("---1").toEvaluateAs(-1);
|
|
42
|
+
expect("2^-2").toEvaluateAs(0.25);
|
|
43
|
+
expect("8^(1/3)").toEvaluateAs(2);
|
|
44
|
+
expect("0^0").toEvaluateAs(1);
|
|
45
|
+
expect(".25*4").toEvaluateAs(1);
|
|
46
|
+
expect("ln e").toEvaluateAs(1);
|
|
47
|
+
expect("log 10").toEvaluateAs(1);
|
|
48
|
+
expect("log_2 2").toEvaluateAs(1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("hyperbolic expressions", () => {
|
|
52
|
+
expect("cosh(0.2)").toEvaluateAs(1.020066755619076);
|
|
53
|
+
expect("coth(0.2)").toEvaluateAs(5.066489563439473);
|
|
54
|
+
expect("csch(3 * 2)").toEvaluateAs(0.00495753481347936);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("variable expressions", () => {
|
|
58
|
+
expect("x").toEvaluateAs(3, {x: 3});
|
|
59
|
+
expect("x^2").toEvaluateAs(9, {x: 3});
|
|
60
|
+
expect("(x^2+y^2)^.5").toEvaluateAs(5, {x: 3, y: 4});
|
|
61
|
+
expect("log x_0").toEvaluateAs(1, {x_0: 10});
|
|
62
|
+
expect("log x_0 + log x_1").toEvaluateAs(3, {x_0: 10, x_1: 100});
|
|
63
|
+
expect("log x_42").toEvaluateAs(1, {x_42: 10});
|
|
64
|
+
expect("x_a + x_bc").toEvaluateAs(7, {x_a: 1, x_b: 2, c: 3});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("function expressions", () => {
|
|
68
|
+
expect("f(2)").toEvaluateAs(4, {f: "2x"}, ["f"]);
|
|
69
|
+
expect("f(4+8)").toEvaluateAs(48, {f: "4x"}, ["f"]);
|
|
70
|
+
expect("f(x-1)-f(x)").toEvaluateAs(-7, {f: "x^3", x: 2}, ["f"]);
|
|
71
|
+
expect("g(1)").toEvaluateAs(-1, {f: "x", g: "-f(x)"}, ["f", "g"]);
|
|
72
|
+
});
|
|
73
|
+
});
|