Skip to content

Instantly share code, notes, and snippets.

@mortie
Created May 28, 2017 01:18
Show Gist options
  • Save mortie/e168babdaa235a2d08b05a7d3b8a1c61 to your computer and use it in GitHub Desktop.
Save mortie/e168babdaa235a2d08b05a7d3b8a1c61 to your computer and use it in GitHub Desktop.
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