json-source-map.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. 'use strict';
  2. var escapedChars = {
  3. 'b': '\b',
  4. 'f': '\f',
  5. 'n': '\n',
  6. 'r': '\r',
  7. 't': '\t',
  8. '"': '"',
  9. '/': '/',
  10. '\\': '\\'
  11. };
  12. var A_CODE = 'a'.charCodeAt();
  13. function parse(source) {
  14. var pointers = {};
  15. var line = 0;
  16. var column = 0;
  17. var pos = 0;
  18. return {
  19. data: _parse('', true),
  20. pointers: pointers
  21. };
  22. function _parse(ptr, topLevel) {
  23. whitespace();
  24. var data;
  25. map(ptr, 'value');
  26. var char = getChar();
  27. switch (char) {
  28. case 't':
  29. read('rue');
  30. data = true;
  31. break;
  32. case 'f':
  33. read('alse');
  34. data = false;
  35. break;
  36. case 'n':
  37. read('ull');
  38. data = null;
  39. break;
  40. case '"':
  41. data = parseString();
  42. break;
  43. case '[':
  44. data = parseArray(ptr);
  45. break;
  46. case '{':
  47. data = parseObject(ptr);
  48. break;
  49. default:
  50. backChar();
  51. if ('-0123456789'.indexOf(char) >= 0)
  52. data = parseNumber();
  53. else
  54. unexpectedToken();
  55. }
  56. map(ptr, 'valueEnd');
  57. whitespace();
  58. if (topLevel && pos < source.length) unexpectedToken();
  59. return data;
  60. }
  61. function whitespace() {
  62. loop: while (pos < source.length) {
  63. switch (source[pos]) {
  64. case ' ':
  65. column++;
  66. break;
  67. case '\t':
  68. column += 4;
  69. break;
  70. case '\r':
  71. column = 0;
  72. break;
  73. case '\n':
  74. column = 0;
  75. line++;
  76. break;
  77. default:
  78. break loop;
  79. }
  80. pos++;
  81. }
  82. }
  83. function parseString() {
  84. var str = '';
  85. var char;
  86. while (true) {
  87. char = getChar();
  88. if (char == '"') {
  89. break;
  90. } else if (char == '\\') {
  91. char = getChar();
  92. if (char in escapedChars)
  93. str += escapedChars[char];
  94. else if (char == 'u')
  95. str += getCharCode();
  96. else
  97. wasUnexpectedToken();
  98. } else {
  99. str += char;
  100. }
  101. }
  102. return str;
  103. }
  104. function parseNumber() {
  105. var numStr = '';
  106. if (source[pos] == '-') numStr += getChar();
  107. numStr += source[pos] == '0' ? getChar() : getDigits();
  108. if (source[pos] == '.')
  109. numStr += getChar() + getDigits();
  110. if (source[pos] == 'e' || source[pos] == 'E') {
  111. numStr += getChar();
  112. if (source[pos] == '+' || source[pos] == '-') numStr += getChar();
  113. numStr += getDigits();
  114. }
  115. return +numStr;
  116. }
  117. function parseArray(ptr) {
  118. whitespace();
  119. var arr = [];
  120. var i = 0;
  121. if (getChar() == ']') return arr;
  122. backChar();
  123. while (true) {
  124. var itemPtr = ptr + '/' + i;
  125. arr.push(_parse(itemPtr));
  126. whitespace();
  127. var char = getChar();
  128. if (char == ']') break;
  129. if (char != ',') wasUnexpectedToken();
  130. whitespace();
  131. i++;
  132. }
  133. return arr;
  134. }
  135. function parseObject(ptr) {
  136. whitespace();
  137. var obj = {};
  138. if (getChar() == '}') return obj;
  139. backChar();
  140. while (true) {
  141. var loc = getLoc();
  142. if (getChar() != '"') wasUnexpectedToken();
  143. var key = parseString();
  144. var propPtr = ptr + '/' + escapeJsonPointer(key);
  145. mapLoc(propPtr, 'key', loc);
  146. map(propPtr, 'keyEnd');
  147. whitespace();
  148. if (getChar() != ':') wasUnexpectedToken();
  149. whitespace();
  150. obj[key] = _parse(propPtr);
  151. whitespace();
  152. var char = getChar();
  153. if (char == '}') break;
  154. if (char != ',') wasUnexpectedToken();
  155. whitespace();
  156. }
  157. return obj;
  158. }
  159. function read(str) {
  160. for (var i = 0; i < str.length; i++)
  161. if (getChar() !== str[i]) wasUnexpectedToken();
  162. }
  163. function getChar() {
  164. checkUnexpectedEnd();
  165. var char = source[pos];
  166. pos++;
  167. column++; // new line?
  168. return char;
  169. }
  170. function backChar() {
  171. pos--;
  172. column--;
  173. }
  174. function getCharCode() {
  175. var count = 4;
  176. var code = 0;
  177. while (count--) {
  178. code <<= 4;
  179. var char = getChar().toLowerCase();
  180. if (char >= 'a' && char <= 'f')
  181. code += char.charCodeAt() - A_CODE + 10;
  182. else if (char >= '0' && char <= '9')
  183. code += +char;
  184. else
  185. wasUnexpectedToken();
  186. }
  187. return String.fromCharCode(code);
  188. }
  189. function getDigits() {
  190. var digits = '';
  191. while (source[pos] >= '0' && source[pos] <= '9')
  192. digits += getChar();
  193. if (digits.length) return digits;
  194. checkUnexpectedEnd();
  195. unexpectedToken();
  196. }
  197. function map(ptr, prop) {
  198. mapLoc(ptr, prop, getLoc());
  199. }
  200. function mapLoc(ptr, prop, loc) {
  201. pointers[ptr] = pointers[ptr] || {};
  202. pointers[ptr][prop] = loc;
  203. }
  204. function getLoc() {
  205. return {
  206. line: line,
  207. column: column,
  208. pos: pos
  209. };
  210. }
  211. function unexpectedToken() {
  212. throw new SyntaxError('Unexpected token ' + source[pos] + ' in JSON at position ' + pos);
  213. }
  214. function wasUnexpectedToken() {
  215. backChar();
  216. unexpectedToken();
  217. }
  218. function checkUnexpectedEnd() {
  219. if (pos >= source.length)
  220. throw new SyntaxError('Unexpected end of JSON input');
  221. }
  222. };
  223. function stringify(data, _, whitespace) {
  224. if (!validType(data)) return;
  225. var wsLine = 0;
  226. var wsPos, wsColumn;
  227. switch (typeof whitespace) {
  228. case 'number':
  229. var len = whitespace > 10 ? 10 : whitespace < 0 ? 0 : Math.floor(whitespace);
  230. whitespace = len && repeat(len, ' ');
  231. wsPos = len;
  232. wsColumn = len;
  233. break;
  234. case 'string':
  235. whitespace = whitespace.slice(0, 10);
  236. wsPos = 0;
  237. wsColumn = 0;
  238. for (var j = 0; j < whitespace.length; j++) {
  239. var char = whitespace[j];
  240. switch (char) {
  241. case ' ':
  242. wsColumn++;
  243. break;
  244. case '\t':
  245. wsColumn += 4;
  246. break;
  247. case '\r':
  248. wsColumn = 0;
  249. break;
  250. case '\n':
  251. wsColumn = 0;
  252. wsLine++;
  253. break;
  254. default:
  255. throw new Error('whitespace characters not allowed in JSON');
  256. }
  257. wsPos++;
  258. }
  259. break;
  260. default:
  261. whitespace = undefined;
  262. }
  263. var json = '';
  264. var pointers = {};
  265. var line = 0;
  266. var column = 0;
  267. var pos = 0;
  268. _stringify(data, 0, '');
  269. return {
  270. json: json,
  271. pointers: pointers
  272. };
  273. function _stringify(_data, lvl, ptr) {
  274. map(ptr, 'value');
  275. switch (typeof _data) {
  276. case 'number':
  277. case 'boolean':
  278. out('' + _data);
  279. break;
  280. case 'string':
  281. out(quoted(_data));
  282. break;
  283. case 'object':
  284. if (_data === null)
  285. out('null');
  286. else if (typeof _data.toJSON == 'function')
  287. out(quoted(_data.toJSON()));
  288. else if (Array.isArray(_data))
  289. stringifyArray();
  290. else
  291. stringifyObject();
  292. }
  293. map(ptr, 'valueEnd');
  294. function stringifyArray() {
  295. if (_data.length) {
  296. out('[');
  297. var itemLvl = lvl + 1;
  298. for (var i = 0; i < _data.length; i++) {
  299. if (i) out(',');
  300. indent(itemLvl);
  301. var item = validType(_data[i]) ? _data[i] : null;
  302. var itemPtr = ptr + '/' + i;
  303. _stringify(item, itemLvl, itemPtr);
  304. }
  305. indent(lvl);
  306. out(']');
  307. } else {
  308. out('[]');
  309. }
  310. }
  311. function stringifyObject() {
  312. var keys = Object.keys(_data);
  313. if (keys.length) {
  314. out('{');
  315. var propLvl = lvl + 1;
  316. for (var i = 0; i < keys.length; i++) {
  317. var key = keys[i];
  318. var value = _data[key];
  319. if (validType(value)) {
  320. if (i) out(',');
  321. var propPtr = ptr + '/' + escapeJsonPointer(key);
  322. indent(propLvl);
  323. map(propPtr, 'key');
  324. out(quoted(key));
  325. map(propPtr, 'keyEnd');
  326. out(':');
  327. if (whitespace) out(' ');
  328. _stringify(value, propLvl, propPtr);
  329. }
  330. }
  331. indent(lvl);
  332. out('}');
  333. } else {
  334. out('{}');
  335. }
  336. }
  337. }
  338. function out(str) {
  339. column += str.length;
  340. pos += str.length;
  341. json += str;
  342. }
  343. function indent(lvl) {
  344. if (whitespace) {
  345. json += '\n' + repeat(lvl, whitespace);
  346. line++;
  347. column = 0;
  348. while (lvl--) {
  349. if (wsLine) {
  350. line += wsLine;
  351. column = wsColumn;
  352. } else {
  353. column += wsColumn;
  354. }
  355. pos += wsPos;
  356. }
  357. pos += 1; // \n character
  358. }
  359. }
  360. function map(ptr, prop) {
  361. pointers[ptr] = pointers[ptr] || {};
  362. pointers[ptr][prop] = {
  363. line: line,
  364. column: column,
  365. pos: pos
  366. };
  367. }
  368. function repeat(n, str) {
  369. return Array(n + 1).join(str);
  370. }
  371. };
  372. var VALID_TYPES = ['number', 'boolean', 'string', 'object'];
  373. function validType(data) {
  374. return VALID_TYPES.indexOf(typeof data) >= 0;
  375. }
  376. var ESC_QUOTE = /"|\\/g;
  377. var ESC_B = /[\b]/g;
  378. var ESC_F = /\f/g;
  379. var ESC_N = /\n/g;
  380. var ESC_R = /\r/g;
  381. var ESC_T = /\t/g;
  382. function quoted(str) {
  383. str = str.replace(ESC_QUOTE, '\\$&')
  384. .replace(ESC_F, '\\f')
  385. .replace(ESC_B, '\\b')
  386. .replace(ESC_N, '\\n')
  387. .replace(ESC_R, '\\r')
  388. .replace(ESC_T, '\\t');
  389. return '"' + str + '"';
  390. }
  391. var ESC_0 = /~/g;
  392. var ESC_1 = /\//g;
  393. function escapeJsonPointer(str) {
  394. return str.replace(ESC_0, '~0')
  395. .replace(ESC_1, '~1');
  396. }