UNPKG

12.1 kBJavaScriptView Raw
1#!/usr/bin/env node
2/**
3 * math.js
4 * https://github.com/josdejong/mathjs
5 *
6 * Math.js is an extensive math library for JavaScript and Node.js,
7 * It features real and complex numbers, units, matrices, a large set of
8 * mathematical functions, and a flexible expression parser.
9 *
10 * Usage:
11 *
12 * mathjs [scriptfile(s)] {OPTIONS}
13 *
14 * Options:
15 *
16 * --version, -v Show application version
17 * --help, -h Show this message
18 * --tex Generate LaTeX instead of evaluating
19 * --string Generate string instead of evaluating
20 * --parenthesis= Set the parenthesis option to
21 * either of "keep", "auto" and "all"
22 *
23 * Example usage:
24 * mathjs Open a command prompt
25 * mathjs 1+2 Evaluate expression
26 * mathjs script.txt Run a script file
27 * mathjs script1.txt script2.txt Run two script files
28 * mathjs script.txt > results.txt Run a script file, output to file
29 * cat script.txt | mathjs Run input stream
30 * cat script.txt | mathjs > results.txt Run input stream, output to file
31 *
32 * @license
33 * Copyright (C) 2013-2018 Jos de Jong <wjosdejong@gmail.com>
34 *
35 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
36 * use this file except in compliance with the License. You may obtain a copy
37 * of the License at
38 *
39 * http://www.apache.org/licenses/LICENSE-2.0
40 *
41 * Unless required by applicable law or agreed to in writing, software
42 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
43 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
44 * License for the specific language governing permissions and limitations under
45 * the License.
46 */
47
48var scope = {};
49var fs = require('fs');
50
51var PRECISION = 14; // decimals
52
53/**
54 * "Lazy" load math.js: only require when we actually start using it.
55 * This ensures the cli application looks like it loads instantly.
56 * When requesting help or version number, math.js isn't even loaded.
57 * @return {*}
58 */
59function getMath () {
60 return require('../index');
61}
62
63/**
64 * Helper function to format a value. Regular numbers will be rounded
65 * to 14 digits to prevent round-off errors from showing up.
66 * @param {*} value
67 */
68function format(value) {
69 var math = getMath();
70
71 return math.format(value, {
72 fn: function (value) {
73 if (typeof value === 'number') {
74 // round numbers
75 return math.format(value, PRECISION);
76 }
77 else {
78 return math.format(value);
79 }
80 }
81 });
82}
83
84/**
85 * auto complete a text
86 * @param {String} text
87 * @return {[Array, String]} completions
88 */
89function completer (text) {
90 var math = getMath();
91 var name;
92 var matches = [];
93 var m = /[a-zA-Z_0-9]+$/.exec(text);
94 if (m) {
95 var keyword = m[0];
96
97 // scope variables
98 for (var def in scope) {
99 if (scope.hasOwnProperty(def)) {
100 if (def.indexOf(keyword) == 0) {
101 matches.push(def);
102 }
103 }
104 }
105
106 // commandline keywords
107 ['exit', 'quit', 'clear'].forEach(function (cmd) {
108 if (cmd.indexOf(keyword) == 0) {
109 matches.push(cmd);
110 }
111 });
112
113 // math functions and constants
114 var ignore = ['expr', 'type'];
115 for (var func in math) {
116 if (math.hasOwnProperty(func)) {
117 if (func.indexOf(keyword) == 0 && ignore.indexOf(func) == -1) {
118 matches.push(func);
119 }
120 }
121 }
122
123 // units
124 var Unit = math.type.Unit;
125 for (name in Unit.UNITS) {
126 if (Unit.UNITS.hasOwnProperty(name)) {
127 if (name.indexOf(keyword) == 0) {
128 matches.push(name);
129 }
130 }
131 }
132 for (name in Unit.PREFIXES) {
133 if (Unit.PREFIXES.hasOwnProperty(name)) {
134 var prefixes = Unit.PREFIXES[name];
135 for (var prefix in prefixes) {
136 if (prefixes.hasOwnProperty(prefix)) {
137 if (prefix.indexOf(keyword) == 0) {
138 matches.push(prefix);
139 }
140 else if (keyword.indexOf(prefix) == 0) {
141 var unitKeyword = keyword.substring(prefix.length);
142 for (var n in Unit.UNITS) {
143 if (Unit.UNITS.hasOwnProperty(n)) {
144 if (n.indexOf(unitKeyword) == 0 &&
145 Unit.isValuelessUnit(prefix + n)) {
146 matches.push(prefix + n);
147 }
148 }
149 }
150 }
151 }
152 }
153 }
154 }
155
156 // remove duplicates
157 matches = matches.filter(function(elem, pos, arr) {
158 return arr.indexOf(elem) == pos;
159 });
160 }
161
162 return [matches, keyword];
163}
164
165/**
166 * Run stream, read and evaluate input and stream that to output.
167 * Text lines read from the input are evaluated, and the results are send to
168 * the output.
169 * @param input Input stream
170 * @param output Output stream
171 * @param mode Output mode
172 * @param parenthesis Parenthesis option
173 */
174function runStream (input, output, mode, parenthesis) {
175 var readline = require('readline'),
176 rl = readline.createInterface({
177 input: input || process.stdin,
178 output: output || process.stdout,
179 completer: completer
180 });
181
182 if (rl.output.isTTY) {
183 rl.setPrompt('> ');
184 rl.prompt();
185 }
186
187 // load math.js now, right *after* loading the prompt.
188 var math = getMath();
189
190 // TODO: automatic insertion of 'ans' before operators like +, -, *, /
191
192 rl.on('line', function(line) {
193 var expr = line.trim();
194
195 switch (expr.toLowerCase()) {
196 case 'quit':
197 case 'exit':
198 // exit application
199 rl.close();
200 break;
201 case 'clear':
202 // clear memory
203 scope = {};
204 console.log('memory cleared');
205
206 // get next input
207 if (rl.output.isTTY) {
208 rl.prompt();
209 }
210 break;
211 default:
212 if (!expr) {
213 break;
214 }
215 switch (mode) {
216 case 'eval':
217 // evaluate expression
218 try {
219 var node = math.parse(expr);
220 var res = node.eval(scope);
221
222 if (math.type.isResultSet(res)) {
223 // we can have 0 or 1 results in the ResultSet, as the CLI
224 // does not allow multiple expressions separated by a return
225 res = res.entries[0];
226 node = node.blocks
227 .filter(function (entry) { return entry.visible; })
228 .map(function (entry) { return entry.node })[0];
229 }
230
231 if (node) {
232 if (math.type.isAssignmentNode(node)) {
233 var name = findSymbolName(node);
234 if (name != null) {
235 scope.ans = scope[name];
236 console.log(name + ' = ' + format(scope[name]));
237 }
238 else {
239 scope.ans = res;
240 console.log(format(res));
241 }
242 }
243 else if (math.type.isHelp(res)) {
244 console.log(res.toString());
245 }
246 else {
247 scope.ans = res;
248 console.log(format(res));
249 }
250 }
251 }
252 catch (err) {
253 console.log(err.toString());
254 }
255 break;
256
257 case 'string':
258 try {
259 var string = math.parse(expr).toString({parenthesis: parenthesis});
260 console.log(string);
261 }
262 catch (err) {
263 console.log(err.toString());
264 }
265 break;
266
267 case 'tex':
268 try {
269 var tex = math.parse(expr).toTex({parenthesis: parenthesis});
270 console.log(tex);
271 }
272 catch (err) {
273 console.log(err.toString());
274 }
275 break;
276 }
277 }
278
279 // get next input
280 if (rl.output.isTTY) {
281 rl.prompt();
282 }
283 });
284
285 rl.on('close', function() {
286 console.log();
287 process.exit(0);
288 });
289}
290
291/**
292 * Find the symbol name of an AssignmentNode. Recurses into the chain of
293 * objects to the root object.
294 * @param {AssignmentNode} node
295 * @return {string | null} Returns the name when found, else returns null.
296 */
297function findSymbolName (node) {
298 var math = getMath();
299 var n = node;
300
301 while (n) {
302 if (math.type.isSymbolNode(n)) {
303 return n.name;
304 }
305 n = n.object;
306 }
307
308 return null;
309}
310
311/**
312 * Output application version number.
313 * Version number is read version from package.json.
314 */
315function outputVersion () {
316 fs.readFile(__dirname + '/../package.json', function (err, data) {
317 if (err) {
318 console.log(err.toString());
319 }
320 else {
321 var pkg = JSON.parse(data);
322 var version = pkg && pkg.version ? pkg.version : 'unknown';
323 console.log(version);
324 }
325 process.exit(0);
326 });
327}
328
329/**
330 * Output a help message
331 */
332function outputHelp() {
333 console.log('math.js');
334 console.log('http://mathjs.org');
335 console.log();
336 console.log('Math.js is an extensive math library for JavaScript and Node.js. It features ');
337 console.log('real and complex numbers, units, matrices, a large set of mathematical');
338 console.log('functions, and a flexible expression parser.');
339 console.log();
340 console.log('Usage:');
341 console.log(' mathjs [scriptfile(s)|expression] {OPTIONS}');
342 console.log();
343 console.log('Options:');
344 console.log(' --version, -v Show application version');
345 console.log(' --help, -h Show this message');
346 console.log(' --tex Generate LaTeX instead of evaluating');
347 console.log(' --string Generate string instead of evaluating');
348 console.log(' --parenthesis= Set the parenthesis option to');
349 console.log(' either of "keep", "auto" and "all"');
350 console.log();
351 console.log('Example usage:');
352 console.log(' mathjs Open a command prompt');
353 console.log(' mathjs 1+2 Evaluate expression');
354 console.log(' mathjs script.txt Run a script file');
355 console.log(' mathjs script.txt script2.txt Run two script files');
356 console.log(' mathjs script.txt > results.txt Run a script file, output to file');
357 console.log(' cat script.txt | mathjs Run input stream');
358 console.log(' cat script.txt | mathjs > results.txt Run input stream, output to file');
359 console.log();
360 process.exit(0);
361}
362
363/**
364 * Process input and output, based on the command line arguments
365 */
366var scripts = []; //queue of scripts that need to be processed
367var mode = 'eval'; //one of 'eval', 'tex' or 'string'
368var parenthesis = 'keep';
369var version = false;
370var help = false;
371
372process.argv.forEach(function (arg, index) {
373 if (index < 2) {
374 return;
375 }
376
377 switch (arg) {
378 case '-v':
379 case '--version':
380 version = true;
381 break;
382
383 case '-h':
384 case '--help':
385 help = true;
386 break;
387
388 case '--tex':
389 mode = 'tex';
390 break;
391
392 case '--string':
393 mode = 'string';
394 break;
395
396 case '--parenthesis=keep':
397 parenthesis = 'keep';
398 break;
399
400 case '--parenthesis=auto':
401 parenthesis = 'auto';
402 break;
403
404 case '--parenthesis=all':
405 parenthesis = 'all';
406 break;
407
408 // TODO: implement configuration via command line arguments
409
410 default:
411 scripts.push(arg);
412 }
413});
414
415if (version) {
416 outputVersion();
417}
418else if (help) {
419 outputHelp();
420}
421else if (scripts.length === 0) {
422 // run a stream, can be user input or pipe input
423 runStream(process.stdin, process.stdout, mode, parenthesis);
424}
425else {
426 fs.stat(scripts[0], function(e, f) {
427 if (e) {
428 console.log(getMath().eval(scripts.join(' ')).toString())
429 } else {
430 //work through the queue of scripts
431 scripts.forEach(function (arg) {
432 // run a script file
433 runStream(fs.createReadStream(arg), process.stdout, mode, parenthesis);
434 });
435 }
436 })
437}