Skip to content

Instantly share code, notes, and snippets.

@nnabeyang
Last active May 8, 2017 23:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nnabeyang/4d7c744771e34540e059c48afd5789fc to your computer and use it in GitHub Desktop.
Save nnabeyang/4d7c744771e34540e059c48afd5789fc to your computer and use it in GitHub Desktop.
普通の電卓
//usage: node main.js "14 + 34 * 22 + (3 + 4) * 5"
var util = require('util'); var calc = calc || {};
calc.Parser = function() {
this.tokens = []; this.parens = []; this.pos = 0;
};
calc.Parser.prototype.next = function() {
var offset = this.parens[this.parens.length - 1];
if(offset + this.pos < this.tokens.length) return this.tokens[offset + this.pos++];
else return null;
};
calc.Parser.prototype.parse = function(line) {
var scanner = new calc.Scanner("("+line+")"); var token;
while(token = scanner.next()) token.appendToTokens(this);
return this.tokens.pop();
};
calc.Parser.prototype.finish = function() {
this.tokens.splice(this.parens.pop(), this.pos+1); this.pos = 0;
};
calc.Scanner = function(line) {
this.buf = line.split(''); this.pos = 0; this.end = 0;
};
calc.Scanner.prototype.peek = function() {
if(this.buf.length <= this.pos) return null;
while(this.isSpace(this.pos)) this.pos++;
this.end = this.pos;
if(this.buf.length <= this.pos) return null;
if(/[\+\-\*]/.test(this.buf[this.end])) {
return new calc.Op(this.buf[this.end++]);
} else if(this.buf[this.end] == '(') {
this.end++;
return new calc.LParen();
} else if(this.buf[this.end] ==')') {
this.end++;
return new calc.RParen();
}else if(this.isNumber(this.end)) {
while(this.isNumber(this.end)) this.end++;
return new calc.Num(this.buf.slice(this.pos, this.end).join(''));
} else {
throw "invalid expression:" + this.buf.join('');
}
};
calc.Scanner.prototype.next = function() {
var token = this.peek(); this.pos = this.end; return token;
};
calc.Scanner.prototype.isSpace = function(pos) {
return /\s/.test(this.buf[pos])
};
calc.Scanner.prototype.isNumber = function(pos) {
return /\d/.test(this.buf[pos])
};
calc.AST = function() {};
calc.AST.prototype.appendToTokens = function(parser) { parser.tokens.push(this);};
calc.AST.prototype.appendToStack = function(parser) { parser.exps.push(this);};
calc.Exp = function(l, op, r) {
calc.AST.call(this); this.l = l; this.op = op; this.r = r;
};
util.inherits(calc.Exp, calc.AST);
calc.Exp.prototype.exec = function() {
switch (this.op.ch) {
case '+': return this.l.exec() + this.r.exec();
case '-': return this.l.exec() - this.r.exec();
case '*': return this.l.exec() * this.r.exec();
}
};
calc.Num = function(v) {
calc.AST.call(this); this.v = parseInt(v, 10);
};
util.inherits(calc.Num, calc.AST);
calc.Num.prototype.exec = function() { return this.v;};
calc.Op = function(ch) {
calc.AST.call(this); this.ch = ch;
this.precedence = "+-*".indexOf(ch);
};
util.inherits(calc.Op, calc.AST);
calc.Op.prototype.appendToStack = function(parser) {
var op = parser.operatorPeek();
if(op && op.precedence >= this.precedence) {
var r = parser.exps.pop(); parser.ops.pop();
var l = parser.exps.pop();
parser.exps.push(new calc.Exp(l, op, r));
}
parser.ops.push(this);
};
calc.LParen = function() {};
calc.LParen.prototype.appendToTokens = function(parser) {parser.parens.push(parser.tokens.length);};
calc.RParen = function() { this.ops = []; this.exps = [];};
calc.RParen.prototype.operatorPeek = function() {
return (this.ops.length > 0)? this.ops[this.ops.length - 1]: null;
};
calc.RParen.prototype.appendToTokens = function(scanner) {
while(token = scanner.next()) token.appendToStack(this);
while(this.ops.length > 0) {
var r = this.exps.pop(); var op = this.ops.pop();
var l = this.exps.pop();
this.exps.push(new calc.Exp(l, op, r));
}
scanner.finish();
scanner.tokens.push(this.exps.pop());
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment