Created
May 28, 2017 01:18
-
-
Save mortie/e168babdaa235a2d08b05a7d3b8a1c61 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
exports.parseStr = parseStr; | |
class Enum { | |
constructor(...vals) { | |
var i = 1; | |
vals.forEach(j => this[j] = i++); | |
} | |
match(val, obj) { | |
var res = obj[this.str(val)]; | |
return res; | |
} | |
str(val) { | |
for (var i in this) { | |
if (this[i] === val) | |
return i; | |
} | |
return null; | |
} | |
} | |
var TokenTypes = new Enum( | |
"NAME", | |
"STRING", | |
"NUMBER", | |
"EOF", | |
"OPENBRACKET", | |
"CLOSEBRACKET", | |
"OPENBRACE", | |
"CLOSEBRACE", | |
"UNKNOWN"); | |
function makeToken(type, linenr, content) { | |
if (content == null) { | |
content = TokenTypes.match(type, { | |
NAME: "[name]", | |
STRING: "[string]", | |
NUMBER: "[number]", | |
EOF: "[eof]", | |
OPENBRACKET: "[", | |
CLOSEBRACKET: "]", | |
OPENBRACE: "{", | |
CLOSEBRACE: "}", | |
UNKNOWN: "[unknown]", | |
}); | |
} | |
return { type, linenr, content }; | |
} | |
function isspace(ch) { | |
return ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n' || ch == null; | |
} | |
class TokenStream { | |
constructor(str) { | |
this.str = str; | |
this.idx = 0; | |
this.linenr = 1; | |
this.prevChar = null; | |
this.currChar = null; | |
this.nextChar = null; | |
this.currToken = null; | |
this.readChar(); | |
this.readToken(); | |
} | |
_nextChar() { | |
var c; | |
if (this.str.length >= this.idx) | |
c = this.str[this.idx++]; | |
else | |
c = null; | |
return c; | |
} | |
readChar() { | |
var c = this._nextChar(); | |
this.prevChar = this.currChar; | |
this.currChar = this.nextChar; | |
this.nextChar = c; | |
if (this.prevChar == "\n") | |
this.linenr += 1; | |
} | |
_nextToken() { | |
this.readChar(); | |
var curr = this.currChar; | |
var next = this.nextChar; | |
var content = ""; | |
// EOF | |
if (curr == null) | |
return makeToken(TokenTypes.EOF, this.linenr); | |
// [ | |
else if (curr == '[') | |
return makeToken(TokenTypes.OPENBRACKET, this.linenr); | |
// ] | |
else if (curr == ']') | |
return makeToken(TokenTypes.CLOSEBRACKET, this.linenr); | |
// { | |
else if (curr == '{') | |
return makeToken(TokenTypes.OPENBRACE, this.linenr); | |
// } | |
else if (curr == '}') | |
return makeToken(TokenTypes.CLOSEBRACE, this.linenr); | |
// whitespace | |
if (isspace(curr)) { | |
while (isspace(this.nextChar) && this.nextChar != null) | |
this.readChar(); | |
return this._nextToken(); | |
} | |
// number | |
else if ( | |
(next >= '0' && next <= '9' && curr == '-') || | |
(curr >= '0' && curr <= '9')) { | |
var containsDot = false; | |
var cont = () => { | |
if (this.nextChar >= '0' && this.nextChar <= '9') | |
return true; | |
if (!containsDot && this.nextChar == '.') | |
return true; | |
return false; | |
}; | |
content += curr; | |
while (cont()) { | |
content += this.nextChar; | |
this.readChar() | |
} | |
return makeToken(TokenTypes.NUMBER, this.linenr, content); | |
} | |
// string | |
else if (curr == '"') { | |
this.readChar(); | |
while (this.currChar != '"') { | |
content += this.currChar; | |
this.readChar(); | |
} | |
return makeToken(TokenTypes.STRING, this.linenr, content); | |
} | |
// name | |
else { | |
var cont = () => { | |
return ( | |
!isspace(this.nextChar) && ( | |
this.nextChar != ':' && | |
this.nextChar != '[' && this.nextChar != ']' && | |
this.nextChar != '{' && this.nextChar != '}')); | |
} | |
content += this.currChar; | |
while (cont()) { | |
content += this.nextChar; | |
this.readChar(); | |
} | |
return makeToken(TokenTypes.NAME, this.linenr, content); | |
} | |
} | |
err(token, msg) { | |
var err = new Error("Line "+token.linenr+": "+msg); | |
err.rcParseError = true; | |
throw err; | |
} | |
readToken() { | |
this.currToken = this._nextToken(); | |
} | |
expect(type) { | |
var t = this.currToken; | |
if (t.type !== type) | |
this.err( | |
t, | |
"Expected "+TokenTypes.str(type)+", got "+ | |
TokenTypes.str(t.type)); | |
this.readToken(); | |
return t; | |
} | |
} | |
class RCParser { | |
constructor(stream, sections) { | |
this.stream = stream; | |
this.sections = sections; | |
this.data = {}; | |
} | |
parse() { | |
while (this.stream.currToken.type !== TokenTypes.EOF) { | |
this.parseSection(); | |
} | |
} | |
parseSection() { | |
var stream = this.stream; | |
var obj = {}; | |
var section = stream.expect(TokenTypes.NAME); | |
if ( | |
(this.sections != null) && | |
(this.sections.indexOf(section.content) === -1)) { | |
stream.err(section, "Unknown section name: "+section.content); | |
} | |
var sub = null; | |
if (stream.currToken.type === TokenTypes.NAME) | |
sub = stream.expect(TokenTypes.NAME).content; | |
else if (stream.currToken.type === TokenTypes.STRING) | |
sub = stream.expect(TokenTypes.STRING).content; | |
obj.name = sub; | |
stream.expect(TokenTypes.OPENBRACE); | |
while (stream.currToken.type !== TokenTypes.CLOSEBRACE) { | |
var name = stream.expect(TokenTypes.NAME); | |
var val = this.parseValue(); | |
obj[name.content] = val; | |
} | |
stream.expect(TokenTypes.CLOSEBRACE); | |
if (this.data[section.content] == null) | |
this.data[section.content] = []; | |
this.data[section.content].push(obj); | |
} | |
parseArray() { | |
var stream = this.stream; | |
stream.expect(TokenTypes.OPENBRACKET); | |
var arr = []; | |
while (stream.currToken.type !== TokenTypes.CLOSEBRACKET) { | |
arr.push(this.parseValue()); | |
} | |
stream.expect(TokenTypes.CLOSEBRACKET); | |
return arr; | |
} | |
parseObject() { | |
var stream = this.stream; | |
stream.expect(TokenTypes.OPENBRACE); | |
var obj = {}; | |
while (stream.currToken.type !== TokenTypes.CLOSEBRACE) { | |
var name = stream.expect(TokenTypes.NAME); | |
var val = this.parseValue(); | |
obj[name.content] = val; | |
} | |
stream.expect(TokenTypes.CLOSEBRACE); | |
return obj; | |
} | |
parseValue() { | |
var stream = this.stream; | |
if ( | |
(stream.currToken.type === TokenTypes.NAME) && | |
(stream.currToken.content === "true")) | |
return true; | |
if ( | |
(stream.currToken.type === TokenTypes.NAME) && | |
(stream.currToken.content === "false")) | |
return false; | |
if (stream.currToken.type === TokenTypes.NAME) | |
return stream.expect(TokenTypes.NAME).content; | |
if (stream.currToken.type === TokenTypes.STRING) | |
return stream.expect(TokenTypes.STRING).content; | |
if (stream.currToken.type === TokenTypes.NUMBER) | |
return parseFloat(stream.expect(TokenTypes.NUMBER).content); | |
if (stream.currToken.type === TokenTypes.OPENBRACKET) | |
return this.parseArray(); | |
if (stream.currToken.type === TokenTypes.OPENBRACE) | |
return this.parseObject(); | |
stream.err( | |
stream.currToken, | |
"Unexpected token "+TokenTypes.str(stream.currToken.type)); | |
} | |
} | |
function parseStr(str, sections) { | |
var stream = new TokenStream(str); | |
var parser = new RCParser(stream); | |
parser.parse(); | |
return parser.data; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment