Skip to content

Commit

Permalink
Merge a8b6a3a into 2c49b2b
Browse files Browse the repository at this point in the history
  • Loading branch information
Evgeny Poberezkin committed Jan 11, 2017
2 parents 2c49b2b + a8b6a3a commit add33ab
Show file tree
Hide file tree
Showing 5 changed files with 510 additions and 92 deletions.
28 changes: 28 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
extends: eslint:recommended
env:
node: true
browser: true
rules:
no-constant-condition: 0
no-shadow: 1
block-scoped-var: 2
callback-return: 2
complexity: [2, 13]
curly: [2, multi-or-nest, consistent]
dot-location: [2, property]
dot-notation: 2
indent: [2, 2, SwitchCase: 1]
linebreak-style: [2, unix]
new-cap: 2
no-console: [2, allow: [warn, error]]
no-else-return: 2
no-eq-null: 2
no-fallthrough: 2
no-invalid-this: 2
no-return-assign: 2
no-trailing-spaces: 2
no-use-before-define: [2, nofunc]
quotes: [2, single, avoid-escape]
semi: [2, always]
strict: [2, global]
valid-jsdoc: [2, requireReturn: false]
332 changes: 278 additions & 54 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,223 @@
'use strict';

exports.parse = function () {
throw new Error('Not implemented');
var escapedChars = {
'b': '\b',
'f': '\f',
'n': '\n',
'r': '\r',
't': '\t',
'"': '"',
'/': '/',
'\\': '\\'
};

var A_CODE = 'a'.charCodeAt();


exports.parse = function (source) {
var pointers = {};
var line = 0;
var column = 0;
var pos = 0;
return {
data: _parse('', true),
pointers: pointers
};

function _parse(ptr, topLevel) {
whitespace();
var data;
map(ptr, 'value');
var char = getChar();
switch (char) {
case 't': read('rue'); data = true; break;
case 'f': read('alse'); data = false; break;
case 'n': read('ull'); data = null; break;
case '"': data = parseString(); break;
case '[': data = parseArray(ptr); break;
case '{': data = parseObject(ptr); break;
default:
backChar();
if ('-0123456789'.indexOf(char) >= 0)
data = parseNumber();
else
unexpectedToken();
}
map(ptr, 'valueEnd');
whitespace();
if (topLevel && pos < source.length) unexpectedToken();
return data;
}

function whitespace() {
// TODO scan whitespace
}

function parseString() {
var str = '';
var char;
while (true) {
char = getChar();
if (char == '"') {
break;
} else if (char == '\\') {
char = getChar();
if (char in escapedChars)
str += escapedChars[char];
else if (char == 'u')
str += getCharCode();
else
wasUnexpectedToken();
} else {
str += char;
}
}
return str;
}

function parseNumber() {
var numStr = '';
if (source[pos] == '-') numStr += getChar();

numStr += source[pos] == '0'
? getChar()
: getDigits();

if (source[pos] == '.')
numStr += getChar() + getDigits();

if (source[pos] == 'e' || source[pos] == 'E') {
numStr += getChar();
if (source[pos] == '+' || source[pos] == '-') numStr += getChar();
numStr += getDigits();
}

return +numStr;
}

function parseArray(ptr) {
whitespace();
var arr = [];
var i = 0;
if (getChar() == ']') return arr;
backChar();

while (true) {
var itemPtr = ptr + '/' + i;
arr.push(_parse(itemPtr));
whitespace();
var char = getChar();
if (char == ']') break;
if (char != ',') wasUnexpectedToken();
whitespace();
i++;
}
return arr;
}

function parseObject(ptr) {
whitespace();
var obj = {};
if (getChar() == '}') return obj;
backChar();

while (true) {
var loc = getLoc();
if (getChar() != '"') wasUnexpectedToken();
var key = parseString();
var propPtr = ptr + '/' + escapeJsonPointer(key);
mapLoc(propPtr, 'key', loc);
map(propPtr, 'keyEnd');
whitespace();
if (getChar() != ':') wasUnexpectedToken();
whitespace();
obj[key] = _parse(propPtr);
whitespace();
var char = getChar();
if (char == '}') break;
if (char != ',') wasUnexpectedToken();
whitespace();
}
return obj;
}

function read(str) {
for (var i=0; i<str.length; i++)
if (getChar() !== str[i]) wasUnexpectedToken();
}

function getChar() {
checkUnexpectedEnd();
var char = source[pos];
pos++;
column++; // new line?
return char;
}

function backChar() {
pos--;
column--;
}

function getCharCode() {
var count = 4;
var code = 0;
while (count--) {
code <<= 4;
var char = getChar().toLowerCase();
if (char >= 'a' && char <= 'f')
code += char.charCodeAt() - A_CODE + 10;
else if (char >= '0' && char <= '9')
code += +char;
else
wasUnexpectedToken();
}
return String.fromCharCode(code);
}

function getDigits() {
var digits = '';
while (source[pos] >= '0' && source[pos] <= '9')
digits += getChar();

if (digits.length) return digits;
checkUnexpectedEnd();
unexpectedToken();
}

function map(ptr, prop) {
mapLoc(ptr, prop, getLoc());
}

function mapLoc(ptr, prop, loc) {
pointers[ptr] = pointers[ptr] || {};
pointers[ptr][prop] = loc;
}

function getLoc() {
return {
line: line,
column: column,
pos: pos
};
}

function unexpectedToken() {
throw new SyntaxError('Unexpected token ' + source[pos] + ' in JSON at position ' + pos);
}

function wasUnexpectedToken() {
backChar();
unexpectedToken();
}

function checkUnexpectedEnd() {
if (pos >= source.length)
throw new SyntaxError('Unexpected end of JSON input');
}
};


exports.stringify = function (data, _, whitespace) {
if (!validType(data)) return;
switch (typeof whitespace) {
Expand All @@ -28,65 +242,75 @@ exports.stringify = function (data, _, whitespace) {
var column = 0;
var pos = 0;
_stringify(data, 0, '');
return { json: json, pointers: pointers };
return {
json: json,
pointers: pointers
};

function _stringify(data, lvl, ptr) {
function _stringify(_data, lvl, ptr) {
map(ptr, 'value');
switch (typeof data) {
switch (typeof _data) {
case 'number':
case 'boolean':
out('' + data); break;
out('' + _data); break;
case 'string':
out(quoted(data)); break;
out(quoted(_data)); break;
case 'object':
if (data === null) {
if (_data === null)
out('null');
} else if (typeof data.toJSON == 'function') {
out(quoted(data.toJSON()));
} else if (Array.isArray(data)) {
if (data.length) {
out('[');
var itemLvl = lvl + 1;
for (var i=0; i<data.length; i++) {
if (i) out(',');
indent(itemLvl);
var item = validType(data[i]) ? data[i] : null;
var itemPtr = ptr + '/' + i;
_stringify(item, itemLvl, itemPtr);
}
indent(lvl);
out(']');
} else {
out('[]');
}
} else {
var keys = Object.keys(data);
if (keys.length) {
out('{');
var propLvl = lvl + 1;
for (var i=0; i<keys.length; i++) {
var key = keys[i];
var value = data[key];
if (validType(value)) {
if (i) out(',');
var propPtr = ptr + '/' + escapeJsonPointer(key);
indent(propLvl);
map(propPtr, 'key');
out(quoted(key));
map(propPtr, 'keyEnd');
out(':')
if (whitespace) out(' ');
_stringify(value, propLvl, propPtr);
}
}
indent(lvl);
out('}');
} else {
out('{}');
else if (typeof _data.toJSON == 'function')
out(quoted(_data.toJSON()));
else if (Array.isArray(_data))
stringifyArray();
else
stringifyObject();
}
map(ptr, 'valueEnd');

function stringifyArray() {
if (_data.length) {
out('[');
var itemLvl = lvl + 1;
for (var i=0; i<_data.length; i++) {
if (i) out(',');
indent(itemLvl);
var item = validType(_data[i]) ? _data[i] : null;
var itemPtr = ptr + '/' + i;
_stringify(item, itemLvl, itemPtr);
}
indent(lvl);
out(']');
} else {
out('[]');
}
}

function stringifyObject() {
var keys = Object.keys(_data);
if (keys.length) {
out('{');
var propLvl = lvl + 1;
for (var i=0; i<keys.length; i++) {
var key = keys[i];
var value = _data[key];
if (validType(value)) {
if (i) out(',');
var propPtr = ptr + '/' + escapeJsonPointer(key);
indent(propLvl);
map(propPtr, 'key');
out(quoted(key));
map(propPtr, 'keyEnd');
out(':');
if (whitespace) out(' ');
_stringify(value, propLvl, propPtr);
}
}
indent(lvl);
out('}');
} else {
out('{}');
}
}
map(ptr, 'valueEnd');
}

function out(str) {
Expand All @@ -98,9 +322,9 @@ exports.stringify = function (data, _, whitespace) {
function indent(lvl) {
if (whitespace) {
line++;
var wsLen = lvl * whitespace.length;
column = wsLen;
pos += wsLen + 1;
var ws = lvl * whitespace.length;
column = ws;
pos += ws + 1;
json += '\n' + repeat(lvl, whitespace);
}
}
Expand Down
Loading

0 comments on commit add33ab

Please sign in to comment.