[Pkg-javascript-commits] [node-json5] 01/02: Import Upstream version 0.5.0

Thorsten Alteholz alteholz at moszumanska.debian.org
Wed Nov 16 19:17:03 UTC 2016


This is an automated email from the git hooks/post-receive script.

alteholz pushed a commit to branch master
in repository node-json5.

commit 5a9a625c32433176d102b8e18b17f5217ae28218
Author: Thorsten Alteholz <debian at alteholz.de>
Date:   Wed Nov 16 20:16:57 2016 +0100

    Import Upstream version 0.5.0
---
 .editorconfig                                      |   8 +
 .gitignore                                         |   2 +
 .jshintrc                                          |   4 +
 .travis.yml                                        |   7 +
 CHANGELOG.md                                       | 158 +++++
 README.md                                          | 285 ++++++++
 gulpfile.js                                        |   8 +
 lib/cli.js                                         |  41 ++
 lib/json5.js                                       | 767 +++++++++++++++++++++
 lib/require.js                                     |  18 +
 package.json                                       |  38 +
 package.json5                                      |  39 ++
 test/count-newlines.js                             |  37 +
 test/parse-cases/arrays/empty-array.json           |   1 +
 test/parse-cases/arrays/leading-comma-array.js     |   3 +
 .../arrays/lone-trailing-comma-array.js            |   3 +
 test/parse-cases/arrays/no-comma-array.errorSpec   |   6 +
 test/parse-cases/arrays/no-comma-array.txt         |   4 +
 test/parse-cases/arrays/regular-array.json         |   5 +
 test/parse-cases/arrays/trailing-comma-array.json5 |   3 +
 .../block-comment-following-array-element.json5    |   6 +
 .../block-comment-following-top-level-value.json5  |   5 +
 .../comments/block-comment-in-string.json          |   1 +
 .../block-comment-preceding-top-level-value.json5  |   5 +
 .../comments/block-comment-with-asterisks.json5    |   7 +
 .../inline-comment-following-array-element.json5   |   3 +
 .../inline-comment-following-top-level-value.json5 |   1 +
 .../comments/inline-comment-in-string.json         |   1 +
 .../inline-comment-preceding-top-level-value.json5 |   2 +
 .../comments/top-level-block-comment.errorSpec     |   6 +
 .../comments/top-level-block-comment.txt           |   4 +
 .../comments/top-level-inline-comment.errorSpec    |   6 +
 .../comments/top-level-inline-comment.txt          |   1 +
 .../comments/unterminated-block-comment.txt        |   5 +
 test/parse-cases/misc/empty.txt                    |   0
 test/parse-cases/misc/npm-package.json             | 106 +++
 test/parse-cases/misc/npm-package.json5            | 106 +++
 test/parse-cases/misc/readme-example.json5         |  25 +
 test/parse-cases/misc/valid-whitespace.json5       |   5 +
 test/parse-cases/new-lines/.editorconfig           |  13 +
 test/parse-cases/new-lines/.gitattributes          |   4 +
 test/parse-cases/new-lines/comment-cr.json5        |   1 +
 test/parse-cases/new-lines/comment-crlf.json5      |   3 +
 test/parse-cases/new-lines/comment-lf.json5        |   3 +
 test/parse-cases/new-lines/escaped-cr.json5        |   1 +
 test/parse-cases/new-lines/escaped-crlf.json5      |   5 +
 test/parse-cases/new-lines/escaped-lf.json5        |   5 +
 .../numbers/float-leading-decimal-point.json5      |   1 +
 test/parse-cases/numbers/float-leading-zero.json   |   1 +
 ...iling-decimal-point-with-integer-exponent.json5 |   1 +
 .../numbers/float-trailing-decimal-point.json5     |   1 +
 .../numbers/float-with-integer-exponent.json       |   1 +
 test/parse-cases/numbers/float.json                |   1 +
 test/parse-cases/numbers/hexadecimal-empty.txt     |   1 +
 .../numbers/hexadecimal-lowercase-letter.json5     |   1 +
 .../numbers/hexadecimal-uppercase-x.json5          |   1 +
 .../hexadecimal-with-integer-exponent.json5        |   1 +
 test/parse-cases/numbers/hexadecimal.json5         |   1 +
 test/parse-cases/numbers/infinity.json5            |   1 +
 .../numbers/integer-with-float-exponent.txt        |   1 +
 .../numbers/integer-with-hexadecimal-exponent.txt  |   1 +
 .../numbers/integer-with-integer-exponent.json     |   1 +
 .../integer-with-negative-float-exponent.txt       |   1 +
 .../integer-with-negative-hexadecimal-exponent.txt |   1 +
 .../integer-with-negative-integer-exponent.json    |   1 +
 ...nteger-with-negative-zero-integer-exponent.json |   1 +
 .../integer-with-positive-float-exponent.txt       |   1 +
 .../integer-with-positive-hexadecimal-exponent.txt |   1 +
 .../integer-with-positive-integer-exponent.json    |   1 +
 ...nteger-with-positive-zero-integer-exponent.json |   1 +
 .../integer-with-zero-integer-exponent.json        |   1 +
 test/parse-cases/numbers/integer.json              |   1 +
 test/parse-cases/numbers/lone-decimal-point.txt    |   1 +
 test/parse-cases/numbers/nan.json5                 |   1 +
 .../negative-float-leading-decimal-point.json5     |   1 +
 .../numbers/negative-float-leading-zero.json       |   1 +
 .../negative-float-trailing-decimal-point.json5    |   1 +
 test/parse-cases/numbers/negative-float.json       |   1 +
 .../parse-cases/numbers/negative-hexadecimal.json5 |   1 +
 test/parse-cases/numbers/negative-infinity.json5   |   1 +
 test/parse-cases/numbers/negative-integer.json     |   1 +
 test/parse-cases/numbers/negative-noctal.js        |   1 +
 test/parse-cases/numbers/negative-octal.txt        |   1 +
 ...negative-zero-float-leading-decimal-point.json5 |   1 +
 ...egative-zero-float-trailing-decimal-point.json5 |   1 +
 test/parse-cases/numbers/negative-zero-float.json  |   1 +
 .../numbers/negative-zero-hexadecimal.json5        |   1 +
 .../parse-cases/numbers/negative-zero-integer.json |   1 +
 test/parse-cases/numbers/negative-zero-octal.txt   |   1 +
 .../numbers/noctal-with-leading-octal-digit.js     |   1 +
 test/parse-cases/numbers/noctal.js                 |   1 +
 test/parse-cases/numbers/octal.txt                 |   1 +
 .../positive-float-leading-decimal-point.json5     |   1 +
 .../numbers/positive-float-leading-zero.json5      |   1 +
 .../positive-float-trailing-decimal-point.json5    |   1 +
 test/parse-cases/numbers/positive-float.json5      |   1 +
 .../parse-cases/numbers/positive-hexadecimal.json5 |   1 +
 test/parse-cases/numbers/positive-infinity.json5   |   1 +
 test/parse-cases/numbers/positive-integer.json5    |   1 +
 test/parse-cases/numbers/positive-noctal.js        |   1 +
 test/parse-cases/numbers/positive-octal.txt        |   1 +
 ...positive-zero-float-leading-decimal-point.json5 |   1 +
 ...ositive-zero-float-trailing-decimal-point.json5 |   1 +
 test/parse-cases/numbers/positive-zero-float.json5 |   1 +
 .../numbers/positive-zero-hexadecimal.json5        |   1 +
 .../numbers/positive-zero-integer.json5            |   1 +
 test/parse-cases/numbers/positive-zero-octal.txt   |   1 +
 .../numbers/zero-float-leading-decimal-point.json5 |   1 +
 .../zero-float-trailing-decimal-point.json5        |   1 +
 test/parse-cases/numbers/zero-float.json           |   1 +
 test/parse-cases/numbers/zero-hexadecimal.json5    |   1 +
 .../zero-integer-with-integer-exponent.json        |   1 +
 test/parse-cases/numbers/zero-integer.json         |   1 +
 test/parse-cases/numbers/zero-octal.txt            |   1 +
 test/parse-cases/objects/duplicate-keys.json       |   4 +
 test/parse-cases/objects/empty-object.json         |   1 +
 .../objects/illegal-unquoted-key-number.errorSpec  |   6 +
 .../objects/illegal-unquoted-key-number.txt        |   3 +
 .../objects/illegal-unquoted-key-symbol.errorSpec  |   6 +
 .../objects/illegal-unquoted-key-symbol.txt        |   3 +
 .../objects/leading-comma-object.errorSpec         |   6 +
 test/parse-cases/objects/leading-comma-object.txt  |   3 +
 .../objects/lone-trailing-comma-object.txt         |   3 +
 test/parse-cases/objects/no-comma-object.txt       |   4 +
 .../objects/reserved-unquoted-key.json5            |   3 +
 test/parse-cases/objects/single-quoted-key.json5   |   3 +
 .../objects/trailing-comma-object.json5            |   3 +
 test/parse-cases/objects/unquoted-keys.json5       |   8 +
 .../strings/escaped-single-quoted-string.json5     |   1 +
 test/parse-cases/strings/multi-line-string.json5   |   2 +
 test/parse-cases/strings/no-comma-array.errorSpec  |   6 +
 .../parse-cases/strings/single-quoted-string.json5 |   1 +
 .../strings/unescaped-multi-line-string.errorSpec  |   6 +
 .../strings/unescaped-multi-line-string.txt        |   2 +
 .../todo/unicode-escaped-unquoted-key.json5        |   3 +
 test/parse-cases/todo/unicode-unquoted-key.json5   |   3 +
 test/parse.js                                      | 133 ++++
 test/readme.md                                     |  23 +
 test/require.js                                    |  20 +
 test/stringify.js                                  | 496 +++++++++++++
 140 files changed, 2586 insertions(+)

diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..df7f120
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,8 @@
+# Don't allow rules from further up the tree.
+root = true
+
+[*]                             # Every file
+end_of_line = lf                # Unix sytle line endings
+insert_final_newline = true     # Ensure a new line at the end of each file
+indent_style = space            # Use spaces instead of tabs
+indent_size = 4                 # Indent 4 spaces
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..93f1361
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+npm-debug.log
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..8b3cd39
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,4 @@
+{
+  "browser": true,
+  "node": true
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..d825f62
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+sudo: false
+language: node_js
+node_js:
+  - "0.10"
+  - "0.12"
+  - "4"
+  - "5"
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..af2c608
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,158 @@
+### v0.5.0 [[code][c0.5.0], [diff][d0.5.0]]
+
+[c0.5.0]: https://github.com/aseemk/json5/tree/v0.5.0
+[d0.5.0]: https://github.com/aseemk/json5/compare/v0.4.0...v0.5.0
+
+This release includes major internal changes and public API enhancements.
+
+- **Major:** JSON5 officially supports Node.js v4 LTS and v5. Support for
+  Node.js v0.6 and v0.8 have been dropped, while support for v0.10 and v0.12
+  remain.
+
+- Fix: YUI Compressor no longer fails when compressing json5.js. ([#97])
+
+- New: `parse` and the CLI provide line and column numbers when displaying error
+  messages. ([#101]; awesome work by [@amb26].)
+
+
+### v0.4.0 [[code][c0.4.0], [diff][d0.4.0]]
+
+[c0.4.0]: https://github.com/aseemk/json5/tree/v0.4.0
+[d0.4.0]: https://github.com/aseemk/json5/compare/v0.2.0...v0.4.0
+
+Note that v0.3.0 was tagged, but never published to npm, so this v0.4.0
+changelog entry includes v0.3.0 features.
+
+This is a massive release that adds `stringify` support, among other things.
+
+- **Major:** `JSON5.stringify()` now exists!
+  This method is analogous to the native `JSON.stringify()`;
+  it just avoids quoting keys where possible.
+  See the [usage documentation](./README.md#usage) for more.
+  ([#32]; huge thanks and props [@aeisenberg]!)
+
+- New: `NaN` and `-NaN` are now allowed number literals.
+  ([#30]; thanks [@rowanhill].)
+
+- New: Duplicate object keys are now allowed; the last value is used.
+  This is the same behavior as JSON. ([#57]; thanks [@jordanbtucker].)
+
+- Fix: Properly handle various whitespace and newline cases now.
+  E.g. JSON5 now properly supports escaped CR and CRLF newlines in strings,
+  and JSON5 now accepts the same whitespace as JSON (stricter than ES5).
+  ([#58], [#60], and [#63]; thanks [@jordanbtucker].)
+
+- New: Negative hexadecimal numbers (e.g. `-0xC8`) are allowed again.
+  (They were disallowed in v0.2.0; see below.)
+  It turns out they *are* valid in ES5, so JSON5 supports them now too.
+  ([#36]; thanks [@jordanbtucker]!)
+
+
+### v0.2.0 [[code][c0.2.0], [diff][d0.2.0]]
+
+[c0.2.0]: https://github.com/aseemk/json5/tree/v0.2.0
+[d0.2.0]: https://github.com/aseemk/json5/compare/v0.1.0...v0.2.0
+
+This release fixes some bugs and adds some more utility features to help you
+express data more easily:
+
+- **Breaking:** Negative hexadecimal numbers (e.g. `-0xC8`) are rejected now.
+  While V8 (e.g. Chrome and Node) supported them, it turns out they're invalid
+  in ES5. This has been [fixed in V8][v8-hex-fix] (and by extension, Chrome
+  and Node), so JSON5 officially rejects them now, too. ([#36])
+
+- New: Trailing decimal points in decimal numbers are allowed again.
+  (They were disallowed in v0.1.0; see below.)
+  They're allowed by ES5, and differentiating between integers and floats may
+  make sense on some platforms. ([#16]; thanks [@Midar].)
+
+- New: `Infinity` and `-Infinity` are now allowed number literals.
+  ([#30]; thanks [@pepkin88].)
+
+- New: Plus signs (`+`) in front of numbers are now allowed, since it can
+  be helpful in some contexts to explicitly mark numbers as positive.
+  (E.g. when a property represents changes or deltas.)
+
+- Fix: unescaped newlines in strings are rejected now.
+  ([#24]; thanks [@Midar].)
+
+
+### v0.1.0 [[code][c0.1.0], [diff][d0.1.0]]
+
+[c0.1.0]: https://github.com/aseemk/json5/tree/v0.1.0
+[d0.1.0]: https://github.com/aseemk/json5/compare/v0.0.1...v0.1.0
+
+This release tightens JSON5 support and adds helpful utility features:
+
+- New: Support hexadecimal numbers. (Thanks [@MaxNanasy].)
+
+- Fix: Reject octal numbers properly now. Previously, they were accepted but
+  improperly parsed as base-10 numbers. (Thanks [@MaxNanasy].)
+
+- **Breaking:** Reject "noctal" numbers now (base-10 numbers that begin with a
+  leading zero). These are disallowed by both JSON5 and JSON, as well as by
+  ES5's strict mode. (Thanks [@MaxNanasy].)
+
+- New: Support leading decimal points in decimal numbers.
+  (Thanks [@MaxNanasy].)
+
+- **Breaking:** Reject trailing decimal points in decimal numbers now. These
+  are disallowed by both JSON5 and JSON. (Thanks [@MaxNanasy].)
+
+- **Breaking:** Reject omitted elements in arrays now. These are disallowed by
+  both JSON5 and JSON.
+
+- Fix: Throw proper `SyntaxError` instances on errors now.
+
+- New: Add Node.js `require()` hook. Register via `json5/lib/require`.
+
+- New: Add Node.js `json5` executable to compile JSON5 files to JSON.
+
+
+### v0.0.1 [[code][c0.0.1], [diff][d0.0.1]]
+
+[c0.0.1]: https://github.com/aseemk/json5/tree/v0.0.1
+[d0.0.1]: https://github.com/aseemk/json5/compare/v0.0.0...v0.0.1
+
+This was the first implementation of this JSON5 parser.
+
+- Support unquoted object keys, including reserved words. Unicode characters
+  and escape sequences sequences aren't yet supported.
+
+- Support single-quoted strings.
+
+- Support multi-line strings.
+
+- Support trailing commas in arrays and objects.
+
+- Support comments, both inline and block.
+
+
+### v0.0.0 [[code](https://github.com/aseemk/json5/tree/v0.0.0)]
+
+Let's consider this to be Douglas Crockford's original [json_parse.js] — a
+parser for the regular JSON format.
+
+
+[json_parse.js]: https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js
+[v8-hex-fix]: http://code.google.com/p/v8/issues/detail?id=2240
+
+[@MaxNanasy]: https://github.com/MaxNanasy
+[@Midar]: https://github.com/Midar
+[@pepkin88]: https://github.com/pepkin88
+[@rowanhill]: https://github.com/rowanhill
+[@aeisenberg]: https://github.com/aeisenberg
+[@jordanbtucker]: https://github.com/jordanbtucker
+[@amb26]: https://github.com/amb26
+
+[#16]: https://github.com/aseemk/json5/issues/16
+[#24]: https://github.com/aseemk/json5/issues/24
+[#30]: https://github.com/aseemk/json5/issues/30
+[#32]: https://github.com/aseemk/json5/issues/32
+[#36]: https://github.com/aseemk/json5/issues/36
+[#57]: https://github.com/aseemk/json5/issues/57
+[#58]: https://github.com/aseemk/json5/pull/58
+[#60]: https://github.com/aseemk/json5/pull/60
+[#63]: https://github.com/aseemk/json5/pull/63
+[#97]: https://github.com/aseemk/json5/pull/97
+[#101]: https://github.com/aseemk/json5/pull/101
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7194ed4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,285 @@
+# JSON5 – Modern JSON
+
+[![Build Status](https://travis-ci.org/aseemk/json5.png)](https://travis-ci.org/aseemk/json5)
+
+JSON is an excellent data format, but we think it can be better.
+
+**JSON5 is a proposed extension to JSON** that aims to make it easier for
+*humans to write and maintain* by hand. It does this by adding some minimal
+syntax features directly from ECMAScript 5.
+
+JSON5 remains a **strict subset of JavaScript**, adds **no new data types**,
+and **works with all existing JSON content**.
+
+JSON5 is *not* an official successor to JSON, and JSON5 content may *not*
+work with existing JSON parsers. For this reason, JSON5 files use a new .json5
+extension. *(TODO: new MIME type needed too.)*
+
+The code here is a **reference JavaScript implementation** for both Node.js
+and all browsers. It’s based directly off of Douglas Crockford’s own [JSON
+implementation][json_parse.js], and it’s both robust and secure.
+
+
+## Why
+
+JSON isn’t the friendliest to *write*. Keys need to be quoted, objects and
+arrays can’t have trailing commas, and comments aren’t allowed — even though
+none of these are the case with regular JavaScript today.
+
+That was fine when JSON’s goal was to be a great data format, but JSON’s usage
+has expanded beyond *machines*. JSON is now used for writing [configs][ex1],
+[manifests][ex2], even [tests][ex3] — all by *humans*.
+
+[ex1]: http://plovr.com/docs.html
+[ex2]: https://www.npmjs.org/doc/files/package.json.html
+[ex3]: http://code.google.com/p/fuzztester/wiki/JSONFileFormat
+
+There are other formats that are human-friendlier, like YAML, but changing
+from JSON to a completely different format is undesirable in many cases.
+JSON5’s aim is to remain close to JSON and JavaScript.
+
+
+## Features
+
+The following is the exact list of additions to JSON’s syntax introduced by
+JSON5. **All of these are optional**, and **all of these come from ES5**.
+
+### Objects
+
+- Object keys can be unquoted if they’re valid [identifiers][mdn_variables].
+  Yes, even reserved keywords (like `default`) are valid unquoted keys in ES5
+  [[§11.1.5](http://es5.github.com/#x11.1.5), [§7.6](http://es5.github.com/#x7.6)].
+  ([More info](https://mathiasbynens.be/notes/javascript-identifiers))
+
+  *(TODO: Unicode characters and escape sequences aren’t yet supported in this
+  implementation.)*
+
+- Objects can have trailing commas.
+
+[mdn_variables]: https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Variables
+
+### Arrays
+
+- Arrays can have trailing commas.
+
+### Strings
+
+- Strings can be single-quoted.
+
+- Strings can be split across multiple lines; just prefix each newline with a
+  backslash. [ES5 [§7.8.4](http://es5.github.com/#x7.8.4)]
+
+### Numbers
+
+- Numbers can be hexadecimal (base 16).
+
+- Numbers can begin or end with a (leading or trailing) decimal point.
+
+- Numbers can include `Infinity`, `-Infinity`,  `NaN`, and `-NaN`.
+
+- Numbers can begin with an explicit plus sign.
+
+### Comments
+
+- Both inline (single-line) and block (multi-line) comments are allowed.
+
+
+## Example
+
+The following is a contrived example, but it illustrates most of the features:
+
+```js
+{
+    foo: 'bar',
+    while: true,
+
+    this: 'is a \
+multi-line string',
+
+    // this is an inline comment
+    here: 'is another', // inline comment
+
+    /* this is a block comment
+       that continues on another line */
+
+    hex: 0xDEADbeef,
+    half: .5,
+    delta: +10,
+    to: Infinity,   // and beyond!
+
+    finally: 'a trailing comma',
+    oh: [
+        "we shouldn't forget",
+        'arrays can have',
+        'trailing commas too',
+    ],
+}
+```
+
+This implementation’s own [package.json5](package.json5) is more realistic:
+
+```js
+// This file is written in JSON5 syntax, naturally, but npm needs a regular
+// JSON file, so compile via `npm run build`. Be sure to keep both in sync!
+
+{
+    name: 'json5',
+    version: '0.5.0',
+    description: 'JSON for the ES5 era.',
+    keywords: ['json', 'es5'],
+    author: 'Aseem Kishore <aseem.kishore at gmail.com>',
+    contributors: [
+        // TODO: Should we remove this section in favor of GitHub's list?
+        // https://github.com/aseemk/json5/contributors
+        'Max Nanasy <max.nanasy at gmail.com>',
+        'Andrew Eisenberg <andrew at eisenberg.as>',
+        'Jordan Tucker <jordanbtucker at gmail.com>',
+    ],
+    main: 'lib/json5.js',
+    bin: 'lib/cli.js',
+    files: ["lib/"],
+    dependencies: {},
+    devDependencies: {
+        gulp: "^3.9.1",
+        'gulp-jshint': "^2.0.0",
+        jshint: "^2.9.1",
+        'jshint-stylish': "^2.1.0",
+        mocha: "^2.4.5"
+    },
+    scripts: {
+        build: 'node ./lib/cli.js -c package.json5',
+        test: 'mocha --ui exports --reporter spec',
+            // TODO: Would it be better to define these in a mocha.opts file?
+    },
+    homepage: 'http://json5.org/',
+    license: 'MIT',
+    repository: {
+        type: 'git',
+        url: 'https://github.com/aseemk/json5.git',
+    },
+}
+```
+
+
+## Community
+
+Join the [Google Group](http://groups.google.com/group/json5) if you’re
+interested in JSON5 news, updates, and general discussion.
+Don’t worry, it’s very low-traffic.
+
+The [GitHub wiki](https://github.com/aseemk/json5/wiki) is a good place to track
+JSON5 support and usage. Contribute freely there!
+
+[GitHub Issues](https://github.com/aseemk/json5/issues) is the place to
+formally propose feature requests and report bugs. Questions and general
+feedback are better directed at the Google Group.
+
+
+## Usage
+
+This JavaScript implementation of JSON5 simply provides a `JSON5` object just
+like the native ES5 `JSON` object.
+
+To use from Node:
+
+```sh
+npm install json5
+```
+
+```js
+var JSON5 = require('json5');
+```
+
+To use in the browser (adds the `JSON5` object to the global namespace):
+
+```html
+<script src="json5.js"></script>
+```
+
+Then in both cases, you can simply replace native `JSON` calls with `JSON5`:
+
+```js
+var obj = JSON5.parse('{unquoted:"key",trailing:"comma",}');
+var str = JSON5.stringify(obj);
+```
+
+`JSON5.parse` supports all of the JSON5 features listed above (*TODO: except
+Unicode*), as well as the native [`reviver` argument][json-parse].
+
+[json-parse]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
+
+`JSON5.stringify` mainly avoids quoting keys where possible, but we hope to
+keep expanding it in the future (e.g. to also output trailing commas).
+It supports the native [`replacer` and `space` arguments][json-stringify],
+as well. *(TODO: Any implemented `toJSON` methods aren’t used today.)*
+
+[json-stringify]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
+
+
+### Extras
+
+If you’re running this on Node, you can also register a JSON5 `require()` hook
+to let you `require()` `.json5` files just like you can `.json` files:
+
+```js
+require('json5/lib/require');
+require('./path/to/foo');   // tries foo.json5 after foo.js, foo.json, etc.
+require('./path/to/bar.json5');
+```
+
+This module also provides a `json5` executable (requires Node) for converting
+JSON5 files to JSON:
+
+```sh
+json5 -c path/to/foo.json5    # generates path/to/foo.json
+```
+
+
+## Development
+
+```sh
+git clone git://github.com/aseemk/json5.git
+cd json5
+npm install
+npm test
+```
+
+As the `package.json5` file states, be sure to run `npm run build` on changes
+to `package.json5`, since npm requires `package.json`.
+
+Feel free to [file issues](https://github.com/aseemk/json5/issues) and submit
+[pull requests](https://github.com/aseemk/json5/pulls) — contributions are
+welcome. If you do submit a pull request, please be sure to add or update the
+tests, and ensure that `npm test` continues to pass.
+
+
+## License
+
+MIT License © 2012-2016 Aseem Kishore, and [others](
+https://github.com/aseemk/json5/contributors).
+
+
+## Credits
+
+[Michael Bolin](http://bolinfest.com/) independently arrived at and published
+some of these same ideas with awesome explanations and detail.
+Recommended reading:
+[Suggested Improvements to JSON](http://bolinfest.com/essays/json.html)
+
+[Douglas Crockford](http://www.crockford.com/) of course designed and built
+JSON, but his state machine diagrams on the [JSON website](http://json.org/),
+as cheesy as it may sound, gave me motivation and confidence that building a
+new parser to implement these ideas this was within my reach!
+This code is also modeled directly off of Doug’s open-source [json_parse.js][]
+parser. I’m super grateful for that clean and well-documented code.
+
+[json_parse.js]: https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js
+
+[Max Nanasy](https://github.com/MaxNanasy) has been an early and prolific
+supporter, contributing multiple patches and ideas. Thanks Max!
+
+[Andrew Eisenberg](https://github.com/aeisenberg) has contributed the
+`stringify` method.
+
+[Jordan Tucker](https://github.com/jordanbtucker) has aligned JSON5 more closely
+with ES5 and is actively maintaining this project.
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000..64a7a2e
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,8 @@
+var gulp = require('gulp');
+var jshint = require('gulp-jshint');
+
+gulp.task('lint', function() {
+  return gulp.src('./lib/*.js')
+    .pipe(jshint())
+    .pipe(jshint.reporter('jshint-stylish'));
+});
diff --git a/lib/cli.js b/lib/cli.js
new file mode 100755
index 0000000..9b72f6d
--- /dev/null
+++ b/lib/cli.js
@@ -0,0 +1,41 @@
+#!/usr/bin/env node
+
+// cli.js
+// JSON5 command-line interface.
+//
+// This is pretty minimal for now; just supports compiling files via `-c`.
+// TODO More useful functionality, like output path, watch, etc.?
+
+var FS = require('fs');
+var JSON5 = require('./json5');
+var Path = require('path');
+
+var USAGE = [
+    'Usage: json5 -c path/to/file.json5 ...',
+    'Compiles JSON5 files into sibling JSON files with the same basenames.',
+].join('\n');
+
+// if valid, args look like [node, json5, -c, file1, file2, ...]
+var args = process.argv;
+
+if (args.length < 4 || args[2] !== '-c') {
+    console.error(USAGE);
+    process.exit(1);
+}
+
+var cwd = process.cwd();
+var files = args.slice(3);
+
+// iterate over each file and convert JSON5 files to JSON:
+files.forEach(function (file) {
+    var path = Path.resolve(cwd, file);
+    var basename = Path.basename(path, '.json5');
+    var dirname = Path.dirname(path);
+
+    var json5 = FS.readFileSync(path, 'utf8');
+    var obj = JSON5.parse(json5);
+    var json = JSON.stringify(obj, null, 4); // 4 spaces; TODO configurable?
+
+    path = Path.join(dirname, basename + '.json');
+    FS.writeFileSync(path, json, 'utf8');
+});
diff --git a/lib/json5.js b/lib/json5.js
new file mode 100644
index 0000000..ead6862
--- /dev/null
+++ b/lib/json5.js
@@ -0,0 +1,767 @@
+// json5.js
+// Modern JSON. See README.md for details.
+//
+// This file is based directly off of Douglas Crockford's json_parse.js:
+// https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js
+
+var JSON5 = (typeof exports === 'object' ? exports : {});
+
+JSON5.parse = (function () {
+    "use strict";
+
+// This is a function that can parse a JSON5 text, producing a JavaScript
+// data structure. It is a simple, recursive descent parser. It does not use
+// eval or regular expressions, so it can be used as a model for implementing
+// a JSON5 parser in other languages.
+
+// We are defining the function inside of another function to avoid creating
+// global variables.
+
+    var at,           // The index of the current character
+        lineNumber,   // The current line number
+        columnNumber, // The current column number
+        ch,           // The current character
+        escapee = {
+            "'":  "'",
+            '"':  '"',
+            '\\': '\\',
+            '/':  '/',
+            '\n': '',       // Replace escaped newlines in strings w/ empty string
+            b:    '\b',
+            f:    '\f',
+            n:    '\n',
+            r:    '\r',
+            t:    '\t'
+        },
+        ws = [
+            ' ',
+            '\t',
+            '\r',
+            '\n',
+            '\v',
+            '\f',
+            '\xA0',
+            '\uFEFF'
+        ],
+        text,
+
+        renderChar = function (chr) {
+            return chr === '' ? 'EOF' : "'" + chr + "'";
+        },
+
+        error = function (m) {
+
+// Call error when something is wrong.
+
+            var error = new SyntaxError();
+            // beginning of message suffix to agree with that provided by Gecko - see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
+            error.message = m + " at line " + lineNumber + " column " + columnNumber + " of the JSON5 data. Still to read: " + JSON.stringify(text.substring(at - 1, at + 19));
+            error.at = at;
+            // These two property names have been chosen to agree with the ones in Gecko, the only popular
+            // environment which seems to supply this info on JSON.parse
+            error.lineNumber = lineNumber;
+            error.columnNumber = columnNumber;
+            throw error;
+        },
+
+        next = function (c) {
+
+// If a c parameter is provided, verify that it matches the current character.
+
+            if (c && c !== ch) {
+                error("Expected " + renderChar(c) + " instead of " + renderChar(ch));
+            }
+
+// Get the next character. When there are no more characters,
+// return the empty string.
+
+            ch = text.charAt(at);
+            at++;
+            columnNumber++;
+            if (ch === '\n' || ch === '\r' && peek() !== '\n') {
+                lineNumber++;
+                columnNumber = 0;
+            }
+            return ch;
+        },
+
+        peek = function () {
+
+// Get the next character without consuming it or
+// assigning it to the ch varaible.
+
+            return text.charAt(at);
+        },
+
+        identifier = function () {
+
+// Parse an identifier. Normally, reserved words are disallowed here, but we
+// only use this for unquoted object keys, where reserved words are allowed,
+// so we don't check for those here. References:
+// - http://es5.github.com/#x7.6
+// - https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Variables
+// - http://docstore.mik.ua/orelly/webprog/jscript/ch02_07.htm
+// TODO Identifiers can have Unicode "letters" in them; add support for those.
+
+            var key = ch;
+
+            // Identifiers must start with a letter, _ or $.
+            if ((ch !== '_' && ch !== '$') &&
+                    (ch < 'a' || ch > 'z') &&
+                    (ch < 'A' || ch > 'Z')) {
+                error("Bad identifier as unquoted key");
+            }
+
+            // Subsequent characters can contain digits.
+            while (next() && (
+                    ch === '_' || ch === '$' ||
+                    (ch >= 'a' && ch <= 'z') ||
+                    (ch >= 'A' && ch <= 'Z') ||
+                    (ch >= '0' && ch <= '9'))) {
+                key += ch;
+            }
+
+            return key;
+        },
+
+        number = function () {
+
+// Parse a number value.
+
+            var number,
+                sign = '',
+                string = '',
+                base = 10;
+
+            if (ch === '-' || ch === '+') {
+                sign = ch;
+                next(ch);
+            }
+
+            // support for Infinity (could tweak to allow other words):
+            if (ch === 'I') {
+                number = word();
+                if (typeof number !== 'number' || isNaN(number)) {
+                    error('Unexpected word for number');
+                }
+                return (sign === '-') ? -number : number;
+            }
+
+            // support for NaN
+            if (ch === 'N' ) {
+              number = word();
+              if (!isNaN(number)) {
+                error('expected word to be NaN');
+              }
+              // ignore sign as -NaN also is NaN
+              return number;
+            }
+
+            if (ch === '0') {
+                string += ch;
+                next();
+                if (ch === 'x' || ch === 'X') {
+                    string += ch;
+                    next();
+                    base = 16;
+                } else if (ch >= '0' && ch <= '9') {
+                    error('Octal literal');
+                }
+            }
+
+            switch (base) {
+            case 10:
+                while (ch >= '0' && ch <= '9' ) {
+                    string += ch;
+                    next();
+                }
+                if (ch === '.') {
+                    string += '.';
+                    while (next() && ch >= '0' && ch <= '9') {
+                        string += ch;
+                    }
+                }
+                if (ch === 'e' || ch === 'E') {
+                    string += ch;
+                    next();
+                    if (ch === '-' || ch === '+') {
+                        string += ch;
+                        next();
+                    }
+                    while (ch >= '0' && ch <= '9') {
+                        string += ch;
+                        next();
+                    }
+                }
+                break;
+            case 16:
+                while (ch >= '0' && ch <= '9' || ch >= 'A' && ch <= 'F' || ch >= 'a' && ch <= 'f') {
+                    string += ch;
+                    next();
+                }
+                break;
+            }
+
+            if(sign === '-') {
+                number = -string;
+            } else {
+                number = +string;
+            }
+
+            if (!isFinite(number)) {
+                error("Bad number");
+            } else {
+                return number;
+            }
+        },
+
+        string = function () {
+
+// Parse a string value.
+
+            var hex,
+                i,
+                string = '',
+                delim,      // double quote or single quote
+                uffff;
+
+// When parsing for string values, we must look for ' or " and \ characters.
+
+            if (ch === '"' || ch === "'") {
+                delim = ch;
+                while (next()) {
+                    if (ch === delim) {
+                        next();
+                        return string;
+                    } else if (ch === '\\') {
+                        next();
+                        if (ch === 'u') {
+                            uffff = 0;
+                            for (i = 0; i < 4; i += 1) {
+                                hex = parseInt(next(), 16);
+                                if (!isFinite(hex)) {
+                                    break;
+                                }
+                                uffff = uffff * 16 + hex;
+                            }
+                            string += String.fromCharCode(uffff);
+                        } else if (ch === '\r') {
+                            if (peek() === '\n') {
+                                next();
+                            }
+                        } else if (typeof escapee[ch] === 'string') {
+                            string += escapee[ch];
+                        } else {
+                            break;
+                        }
+                    } else if (ch === '\n') {
+                        // unescaped newlines are invalid; see:
+                        // https://github.com/aseemk/json5/issues/24
+                        // TODO this feels special-cased; are there other
+                        // invalid unescaped chars?
+                        break;
+                    } else {
+                        string += ch;
+                    }
+                }
+            }
+            error("Bad string");
+        },
+
+        inlineComment = function () {
+
+// Skip an inline comment, assuming this is one. The current character should
+// be the second / character in the // pair that begins this inline comment.
+// To finish the inline comment, we look for a newline or the end of the text.
+
+            if (ch !== '/') {
+                error("Not an inline comment");
+            }
+
+            do {
+                next();
+                if (ch === '\n' || ch === '\r') {
+                    next();
+                    return;
+                }
+            } while (ch);
+        },
+
+        blockComment = function () {
+
+// Skip a block comment, assuming this is one. The current character should be
+// the * character in the /* pair that begins this block comment.
+// To finish the block comment, we look for an ending */ pair of characters,
+// but we also watch for the end of text before the comment is terminated.
+
+            if (ch !== '*') {
+                error("Not a block comment");
+            }
+
+            do {
+                next();
+                while (ch === '*') {
+                    next('*');
+                    if (ch === '/') {
+                        next('/');
+                        return;
+                    }
+                }
+            } while (ch);
+
+            error("Unterminated block comment");
+        },
+
+        comment = function () {
+
+// Skip a comment, whether inline or block-level, assuming this is one.
+// Comments always begin with a / character.
+
+            if (ch !== '/') {
+                error("Not a comment");
+            }
+
+            next('/');
+
+            if (ch === '/') {
+                inlineComment();
+            } else if (ch === '*') {
+                blockComment();
+            } else {
+                error("Unrecognized comment");
+            }
+        },
+
+        white = function () {
+
+// Skip whitespace and comments.
+// Note that we're detecting comments by only a single / character.
+// This works since regular expressions are not valid JSON(5), but this will
+// break if there are other valid values that begin with a / character!
+
+            while (ch) {
+                if (ch === '/') {
+                    comment();
+                } else if (ws.indexOf(ch) >= 0) {
+                    next();
+                } else {
+                    return;
+                }
+            }
+        },
+
+        word = function () {
+
+// true, false, or null.
+
+            switch (ch) {
+            case 't':
+                next('t');
+                next('r');
+                next('u');
+                next('e');
+                return true;
+            case 'f':
+                next('f');
+                next('a');
+                next('l');
+                next('s');
+                next('e');
+                return false;
+            case 'n':
+                next('n');
+                next('u');
+                next('l');
+                next('l');
+                return null;
+            case 'I':
+                next('I');
+                next('n');
+                next('f');
+                next('i');
+                next('n');
+                next('i');
+                next('t');
+                next('y');
+                return Infinity;
+            case 'N':
+              next( 'N' );
+              next( 'a' );
+              next( 'N' );
+              return NaN;
+            }
+            error("Unexpected " + renderChar(ch));
+        },
+
+        value,  // Place holder for the value function.
+
+        array = function () {
+
+// Parse an array value.
+
+            var array = [];
+
+            if (ch === '[') {
+                next('[');
+                white();
+                while (ch) {
+                    if (ch === ']') {
+                        next(']');
+                        return array;   // Potentially empty array
+                    }
+                    // ES5 allows omitting elements in arrays, e.g. [,] and
+                    // [,null]. We don't allow this in JSON5.
+                    if (ch === ',') {
+                        error("Missing array element");
+                    } else {
+                        array.push(value());
+                    }
+                    white();
+                    // If there's no comma after this value, this needs to
+                    // be the end of the array.
+                    if (ch !== ',') {
+                        next(']');
+                        return array;
+                    }
+                    next(',');
+                    white();
+                }
+            }
+            error("Bad array");
+        },
+
+        object = function () {
+
+// Parse an object value.
+
+            var key,
+                object = {};
+
+            if (ch === '{') {
+                next('{');
+                white();
+                while (ch) {
+                    if (ch === '}') {
+                        next('}');
+                        return object;   // Potentially empty object
+                    }
+
+                    // Keys can be unquoted. If they are, they need to be
+                    // valid JS identifiers.
+                    if (ch === '"' || ch === "'") {
+                        key = string();
+                    } else {
+                        key = identifier();
+                    }
+
+                    white();
+                    next(':');
+                    object[key] = value();
+                    white();
+                    // If there's no comma after this pair, this needs to be
+                    // the end of the object.
+                    if (ch !== ',') {
+                        next('}');
+                        return object;
+                    }
+                    next(',');
+                    white();
+                }
+            }
+            error("Bad object");
+        };
+
+    value = function () {
+
+// Parse a JSON value. It could be an object, an array, a string, a number,
+// or a word.
+
+        white();
+        switch (ch) {
+        case '{':
+            return object();
+        case '[':
+            return array();
+        case '"':
+        case "'":
+            return string();
+        case '-':
+        case '+':
+        case '.':
+            return number();
+        default:
+            return ch >= '0' && ch <= '9' ? number() : word();
+        }
+    };
+
+// Return the json_parse function. It will have access to all of the above
+// functions and variables.
+
+    return function (source, reviver) {
+        var result;
+
+        text = String(source);
+        at = 0;
+        lineNumber = 1;
+        columnNumber = 1;
+        ch = ' ';
+        result = value();
+        white();
+        if (ch) {
+            error("Syntax error");
+        }
+
+// If there is a reviver function, we recursively walk the new structure,
+// passing each name/value pair to the reviver function for possible
+// transformation, starting with a temporary root object that holds the result
+// in an empty key. If there is not a reviver function, we simply return the
+// result.
+
+        return typeof reviver === 'function' ? (function walk(holder, key) {
+            var k, v, value = holder[key];
+            if (value && typeof value === 'object') {
+                for (k in value) {
+                    if (Object.prototype.hasOwnProperty.call(value, k)) {
+                        v = walk(value, k);
+                        if (v !== undefined) {
+                            value[k] = v;
+                        } else {
+                            delete value[k];
+                        }
+                    }
+                }
+            }
+            return reviver.call(holder, key, value);
+        }({'': result}, '')) : result;
+    };
+}());
+
+// JSON5 stringify will not quote keys where appropriate
+JSON5.stringify = function (obj, replacer, space) {
+    if (replacer && (typeof(replacer) !== "function" && !isArray(replacer))) {
+        throw new Error('Replacer must be a function or an array');
+    }
+    var getReplacedValueOrUndefined = function(holder, key, isTopLevel) {
+        var value = holder[key];
+
+        // Replace the value with its toJSON value first, if possible
+        if (value && value.toJSON && typeof value.toJSON === "function") {
+            value = value.toJSON();
+        }
+
+        // If the user-supplied replacer if a function, call it. If it's an array, check objects' string keys for
+        // presence in the array (removing the key/value pair from the resulting JSON if the key is missing).
+        if (typeof(replacer) === "function") {
+            return replacer.call(holder, key, value);
+        } else if(replacer) {
+            if (isTopLevel || isArray(holder) || replacer.indexOf(key) >= 0) {
+                return value;
+            } else {
+                return undefined;
+            }
+        } else {
+            return value;
+        }
+    };
+
+    function isWordChar(c) {
+        return (c >= 'a' && c <= 'z') ||
+            (c >= 'A' && c <= 'Z') ||
+            (c >= '0' && c <= '9') ||
+            c === '_' || c === '$';
+    }
+
+    function isWordStart(c) {
+        return (c >= 'a' && c <= 'z') ||
+            (c >= 'A' && c <= 'Z') ||
+            c === '_' || c === '$';
+    }
+
+    function isWord(key) {
+        if (typeof key !== 'string') {
+            return false;
+        }
+        if (!isWordStart(key[0])) {
+            return false;
+        }
+        var i = 1, length = key.length;
+        while (i < length) {
+            if (!isWordChar(key[i])) {
+                return false;
+            }
+            i++;
+        }
+        return true;
+    }
+
+    // export for use in tests
+    JSON5.isWord = isWord;
+
+    // polyfills
+    function isArray(obj) {
+        if (Array.isArray) {
+            return Array.isArray(obj);
+        } else {
+            return Object.prototype.toString.call(obj) === '[object Array]';
+        }
+    }
+
+    function isDate(obj) {
+        return Object.prototype.toString.call(obj) === '[object Date]';
+    }
+
+    var objStack = [];
+    function checkForCircular(obj) {
+        for (var i = 0; i < objStack.length; i++) {
+            if (objStack[i] === obj) {
+                throw new TypeError("Converting circular structure to JSON");
+            }
+        }
+    }
+
+    function makeIndent(str, num, noNewLine) {
+        if (!str) {
+            return "";
+        }
+        // indentation no more than 10 chars
+        if (str.length > 10) {
+            str = str.substring(0, 10);
+        }
+
+        var indent = noNewLine ? "" : "\n";
+        for (var i = 0; i < num; i++) {
+            indent += str;
+        }
+
+        return indent;
+    }
+
+    var indentStr;
+    if (space) {
+        if (typeof space === "string") {
+            indentStr = space;
+        } else if (typeof space === "number" && space >= 0) {
+            indentStr = makeIndent(" ", space, true);
+        } else {
+            // ignore space parameter
+        }
+    }
+
+    // Copied from Crokford's implementation of JSON
+    // See https://github.com/douglascrockford/JSON-js/blob/e39db4b7e6249f04a195e7dd0840e610cc9e941e/json2.js#L195
+    // Begin
+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        meta = { // table of character substitutions
+        '\b': '\\b',
+        '\t': '\\t',
+        '\n': '\\n',
+        '\f': '\\f',
+        '\r': '\\r',
+        '"' : '\\"',
+        '\\': '\\\\'
+    };
+    function escapeString(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+        escapable.lastIndex = 0;
+        return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
+            var c = meta[a];
+            return typeof c === 'string' ?
+                c :
+                '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+        }) + '"' : '"' + string + '"';
+    }
+    // End
+
+    function internalStringify(holder, key, isTopLevel) {
+        var buffer, res;
+
+        // Replace the value, if necessary
+        var obj_part = getReplacedValueOrUndefined(holder, key, isTopLevel);
+
+        if (obj_part && !isDate(obj_part)) {
+            // unbox objects
+            // don't unbox dates, since will turn it into number
+            obj_part = obj_part.valueOf();
+        }
+        switch(typeof obj_part) {
+            case "boolean":
+                return obj_part.toString();
+
+            case "number":
+                if (isNaN(obj_part) || !isFinite(obj_part)) {
+                    return "null";
+                }
+                return obj_part.toString();
+
+            case "string":
+                return escapeString(obj_part.toString());
+
+            case "object":
+                if (obj_part === null) {
+                    return "null";
+                } else if (isArray(obj_part)) {
+                    checkForCircular(obj_part);
+                    buffer = "[";
+                    objStack.push(obj_part);
+
+                    for (var i = 0; i < obj_part.length; i++) {
+                        res = internalStringify(obj_part, i, false);
+                        buffer += makeIndent(indentStr, objStack.length);
+                        if (res === null || typeof res === "undefined") {
+                            buffer += "null";
+                        } else {
+                            buffer += res;
+                        }
+                        if (i < obj_part.length-1) {
+                            buffer += ",";
+                        } else if (indentStr) {
+                            buffer += "\n";
+                        }
+                    }
+                    objStack.pop();
+                    buffer += makeIndent(indentStr, objStack.length, true) + "]";
+                } else {
+                    checkForCircular(obj_part);
+                    buffer = "{";
+                    var nonEmpty = false;
+                    objStack.push(obj_part);
+                    for (var prop in obj_part) {
+                        if (obj_part.hasOwnProperty(prop)) {
+                            var value = internalStringify(obj_part, prop, false);
+                            isTopLevel = false;
+                            if (typeof value !== "undefined" && value !== null) {
+                                buffer += makeIndent(indentStr, objStack.length);
+                                nonEmpty = true;
+                                key = isWord(prop) ? prop : escapeString(prop);
+                                buffer += key + ":" + (indentStr ? ' ' : '') + value + ",";
+                            }
+                        }
+                    }
+                    objStack.pop();
+                    if (nonEmpty) {
+                        buffer = buffer.substring(0, buffer.length-1) + makeIndent(indentStr, objStack.length) + "}";
+                    } else {
+                        buffer = '{}';
+                    }
+                }
+                return buffer;
+            default:
+                // functions and undefined should be ignored
+                return undefined;
+        }
+    }
+
+    // special case...when undefined is used inside of
+    // a compound object/array, return null.
+    // but when top-level, return undefined
+    var topLevelHolder = {"":obj};
+    if (obj === undefined) {
+        return getReplacedValueOrUndefined(topLevelHolder, '', true);
+    }
+    return internalStringify(topLevelHolder, '', true);
+};
diff --git a/lib/require.js b/lib/require.js
new file mode 100644
index 0000000..73e56fe
--- /dev/null
+++ b/lib/require.js
@@ -0,0 +1,18 @@
+// require.js
+// Node.js only: adds a require() hook for .json5 files, just like the native
+// hook for .json files.
+//
+// Usage:
+// require('json5/require');
+// require('./foo');    // will check foo.json5 after foo.js, foo.json, etc.
+// require('./bar.json5');
+
+var FS = require('fs');
+var JSON5 = require('./json5');
+
+// Modeled off of (v0.6.18 link; check latest too):
+// https://github.com/joyent/node/blob/v0.6.18/lib/module.js#L468-L472
+require.extensions['.json5'] = function (module, filename) {
+    var content = FS.readFileSync(filename, 'utf8');
+    module.exports = JSON5.parse(content);
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..4e619cd
--- /dev/null
+++ b/package.json
@@ -0,0 +1,38 @@
+{
+    "name": "json5",
+    "version": "0.5.0",
+    "description": "JSON for the ES5 era.",
+    "keywords": [
+        "json",
+        "es5"
+    ],
+    "author": "Aseem Kishore <aseem.kishore at gmail.com>",
+    "contributors": [
+        "Max Nanasy <max.nanasy at gmail.com>",
+        "Andrew Eisenberg <andrew at eisenberg.as>",
+        "Jordan Tucker <jordanbtucker at gmail.com>"
+    ],
+    "main": "lib/json5.js",
+    "bin": "lib/cli.js",
+    "files": [
+        "lib/"
+    ],
+    "dependencies": {},
+    "devDependencies": {
+        "gulp": "^3.9.1",
+        "gulp-jshint": "^2.0.0",
+        "jshint": "^2.9.1",
+        "jshint-stylish": "^2.1.0",
+        "mocha": "^2.4.5"
+    },
+    "scripts": {
+        "build": "node ./lib/cli.js -c package.json5",
+        "test": "mocha --ui exports --reporter spec"
+    },
+    "homepage": "http://json5.org/",
+    "license": "MIT",
+    "repository": {
+        "type": "git",
+        "url": "https://github.com/aseemk/json5.git"
+    }
+}
\ No newline at end of file
diff --git a/package.json5 b/package.json5
new file mode 100644
index 0000000..4eca12a
--- /dev/null
+++ b/package.json5
@@ -0,0 +1,39 @@
+// This file is written in JSON5 syntax, naturally, but npm needs a regular
+// JSON file, so compile via `npm run build`. Be sure to keep both in sync!
+
+{
+    name: 'json5',
+    version: '0.5.0',
+    description: 'JSON for the ES5 era.',
+    keywords: ['json', 'es5'],
+    author: 'Aseem Kishore <aseem.kishore at gmail.com>',
+    contributors: [
+        // TODO: Should we remove this section in favor of GitHub's list?
+        // https://github.com/aseemk/json5/contributors
+        'Max Nanasy <max.nanasy at gmail.com>',
+        'Andrew Eisenberg <andrew at eisenberg.as>',
+        'Jordan Tucker <jordanbtucker at gmail.com>',
+    ],
+    main: 'lib/json5.js',
+    bin: 'lib/cli.js',
+    files: ["lib/"],
+    dependencies: {},
+    devDependencies: {
+        gulp: "^3.9.1",
+        'gulp-jshint': "^2.0.0",
+        jshint: "^2.9.1",
+        'jshint-stylish': "^2.1.0",
+        mocha: "^2.4.5"
+    },
+    scripts: {
+        build: 'node ./lib/cli.js -c package.json5',
+        test: 'mocha --ui exports --reporter spec',
+            // TODO: Would it be better to define these in a mocha.opts file?
+    },
+    homepage: 'http://json5.org/',
+    license: 'MIT',
+    repository: {
+        type: 'git',
+        url: 'https://github.com/aseemk/json5.git',
+    },
+}
diff --git a/test/count-newlines.js b/test/count-newlines.js
new file mode 100644
index 0000000..cb66ed7
--- /dev/null
+++ b/test/count-newlines.js
@@ -0,0 +1,37 @@
+// count-newlines.js
+// Tests JSON5's line counting algorithm's support for the basic varieties of newline that we support - 
+// LF, CR+LF and CR
+
+"use strict";
+
+var assert = require('assert');
+var JSON5 = require('..');
+
+// Each of these cases should give rise to a parse error with the same coordinates
+var cases = {
+    LF:   "{\u000a    10thing",
+    CRLF: "{\u000d\u000a    10thing",
+    CR:   "{\u000d    10thing"
+};
+
+var spec = {
+    lineNumber: 2,
+    columnNumber: 5
+};
+
+exports['count-newlines'] = {};
+
+Object.keys(cases).forEach(function (key) {
+    var str = cases[key];
+    exports['count-newlines'][key] = function () {
+        var err;
+        try {
+            JSON5.parse(str);
+        } catch (e) {
+            err = e;
+        }
+        assert(err, 'Expected JSON5 parsing to fail.');
+        assert.equal(err.lineNumber, spec.lineNumber);
+        assert.equal(err.columnNumber, spec.columnNumber);
+    };
+});
diff --git a/test/parse-cases/arrays/empty-array.json b/test/parse-cases/arrays/empty-array.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/test/parse-cases/arrays/empty-array.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/test/parse-cases/arrays/leading-comma-array.js b/test/parse-cases/arrays/leading-comma-array.js
new file mode 100644
index 0000000..23c097c
--- /dev/null
+++ b/test/parse-cases/arrays/leading-comma-array.js
@@ -0,0 +1,3 @@
+[
+    ,null
+]
\ No newline at end of file
diff --git a/test/parse-cases/arrays/lone-trailing-comma-array.js b/test/parse-cases/arrays/lone-trailing-comma-array.js
new file mode 100644
index 0000000..013b45c
--- /dev/null
+++ b/test/parse-cases/arrays/lone-trailing-comma-array.js
@@ -0,0 +1,3 @@
+[
+    ,
+]
\ No newline at end of file
diff --git a/test/parse-cases/arrays/no-comma-array.errorSpec b/test/parse-cases/arrays/no-comma-array.errorSpec
new file mode 100644
index 0000000..b476eca
--- /dev/null
+++ b/test/parse-cases/arrays/no-comma-array.errorSpec
@@ -0,0 +1,6 @@
+{
+    at: 16,
+    lineNumber: 3,
+    columnNumber: 5,
+    message: "Expected ']' instead of 'f'"
+}
\ No newline at end of file
diff --git a/test/parse-cases/arrays/no-comma-array.txt b/test/parse-cases/arrays/no-comma-array.txt
new file mode 100644
index 0000000..22b41c1
--- /dev/null
+++ b/test/parse-cases/arrays/no-comma-array.txt
@@ -0,0 +1,4 @@
+[
+    true
+    false
+]
\ No newline at end of file
diff --git a/test/parse-cases/arrays/regular-array.json b/test/parse-cases/arrays/regular-array.json
new file mode 100644
index 0000000..9072780
--- /dev/null
+++ b/test/parse-cases/arrays/regular-array.json
@@ -0,0 +1,5 @@
+[
+    true,
+    false,
+    null
+]
\ No newline at end of file
diff --git a/test/parse-cases/arrays/trailing-comma-array.json5 b/test/parse-cases/arrays/trailing-comma-array.json5
new file mode 100644
index 0000000..6e6b6ed
--- /dev/null
+++ b/test/parse-cases/arrays/trailing-comma-array.json5
@@ -0,0 +1,3 @@
+[
+    null,
+]
\ No newline at end of file
diff --git a/test/parse-cases/comments/block-comment-following-array-element.json5 b/test/parse-cases/comments/block-comment-following-array-element.json5
new file mode 100644
index 0000000..8677f63
--- /dev/null
+++ b/test/parse-cases/comments/block-comment-following-array-element.json5
@@ -0,0 +1,6 @@
+[
+    false
+    /*
+        true
+    */
+]
\ No newline at end of file
diff --git a/test/parse-cases/comments/block-comment-following-top-level-value.json5 b/test/parse-cases/comments/block-comment-following-top-level-value.json5
new file mode 100644
index 0000000..1e6ccfd
--- /dev/null
+++ b/test/parse-cases/comments/block-comment-following-top-level-value.json5
@@ -0,0 +1,5 @@
+null
+/*
+    Some non-comment top-level value is needed;
+    we use null above.
+*/
\ No newline at end of file
diff --git a/test/parse-cases/comments/block-comment-in-string.json b/test/parse-cases/comments/block-comment-in-string.json
new file mode 100644
index 0000000..7d2916c
--- /dev/null
+++ b/test/parse-cases/comments/block-comment-in-string.json
@@ -0,0 +1 @@
+"This /* block comment */ isn't really a block comment."
\ No newline at end of file
diff --git a/test/parse-cases/comments/block-comment-preceding-top-level-value.json5 b/test/parse-cases/comments/block-comment-preceding-top-level-value.json5
new file mode 100644
index 0000000..df1e520
--- /dev/null
+++ b/test/parse-cases/comments/block-comment-preceding-top-level-value.json5
@@ -0,0 +1,5 @@
+/*
+    Some non-comment top-level value is needed;
+    we use null below.
+*/
+null
\ No newline at end of file
diff --git a/test/parse-cases/comments/block-comment-with-asterisks.json5 b/test/parse-cases/comments/block-comment-with-asterisks.json5
new file mode 100644
index 0000000..94c44e7
--- /dev/null
+++ b/test/parse-cases/comments/block-comment-with-asterisks.json5
@@ -0,0 +1,7 @@
+/**
+ * This is a JavaDoc-like block comment.
+ * It contains asterisks inside of it.
+ * It might also be closed with multiple asterisks.
+ * Like this:
+ **/
+true
\ No newline at end of file
diff --git a/test/parse-cases/comments/inline-comment-following-array-element.json5 b/test/parse-cases/comments/inline-comment-following-array-element.json5
new file mode 100644
index 0000000..d6a3f8c
--- /dev/null
+++ b/test/parse-cases/comments/inline-comment-following-array-element.json5
@@ -0,0 +1,3 @@
+[
+    false   // true
+]
\ No newline at end of file
diff --git a/test/parse-cases/comments/inline-comment-following-top-level-value.json5 b/test/parse-cases/comments/inline-comment-following-top-level-value.json5
new file mode 100644
index 0000000..cf9ed01
--- /dev/null
+++ b/test/parse-cases/comments/inline-comment-following-top-level-value.json5
@@ -0,0 +1 @@
+null // Some non-comment top-level value is needed; we use null here.
\ No newline at end of file
diff --git a/test/parse-cases/comments/inline-comment-in-string.json b/test/parse-cases/comments/inline-comment-in-string.json
new file mode 100644
index 0000000..f0fb14f
--- /dev/null
+++ b/test/parse-cases/comments/inline-comment-in-string.json
@@ -0,0 +1 @@
+"This inline comment // isn't really an inline comment."
\ No newline at end of file
diff --git a/test/parse-cases/comments/inline-comment-preceding-top-level-value.json5 b/test/parse-cases/comments/inline-comment-preceding-top-level-value.json5
new file mode 100644
index 0000000..d4b9b4d
--- /dev/null
+++ b/test/parse-cases/comments/inline-comment-preceding-top-level-value.json5
@@ -0,0 +1,2 @@
+// Some non-comment top-level value is needed; we use null below.
+null
\ No newline at end of file
diff --git a/test/parse-cases/comments/top-level-block-comment.errorSpec b/test/parse-cases/comments/top-level-block-comment.errorSpec
new file mode 100644
index 0000000..9bf5cf5
--- /dev/null
+++ b/test/parse-cases/comments/top-level-block-comment.errorSpec
@@ -0,0 +1,6 @@
+{
+    at: 77,
+    lineNumber: 4,
+    columnNumber: 3,
+    message: "Unexpected EOF"
+}
\ No newline at end of file
diff --git a/test/parse-cases/comments/top-level-block-comment.txt b/test/parse-cases/comments/top-level-block-comment.txt
new file mode 100644
index 0000000..7466bd2
--- /dev/null
+++ b/test/parse-cases/comments/top-level-block-comment.txt
@@ -0,0 +1,4 @@
+/*
+    This should fail;
+    comments cannot be the only top-level value.
+*/
\ No newline at end of file
diff --git a/test/parse-cases/comments/top-level-inline-comment.errorSpec b/test/parse-cases/comments/top-level-inline-comment.errorSpec
new file mode 100644
index 0000000..3d915cd
--- /dev/null
+++ b/test/parse-cases/comments/top-level-inline-comment.errorSpec
@@ -0,0 +1,6 @@
+{
+    at: 66,
+    lineNumber: 1,
+    columnNumber: 67,
+    message: "Unexpected EOF"
+}
\ No newline at end of file
diff --git a/test/parse-cases/comments/top-level-inline-comment.txt b/test/parse-cases/comments/top-level-inline-comment.txt
new file mode 100644
index 0000000..c5577f1
--- /dev/null
+++ b/test/parse-cases/comments/top-level-inline-comment.txt
@@ -0,0 +1 @@
+// This should fail; comments cannot be the only top-level value.
\ No newline at end of file
diff --git a/test/parse-cases/comments/unterminated-block-comment.txt b/test/parse-cases/comments/unterminated-block-comment.txt
new file mode 100644
index 0000000..627b7bd
--- /dev/null
+++ b/test/parse-cases/comments/unterminated-block-comment.txt
@@ -0,0 +1,5 @@
+true
+/*
+    This block comment doesn't terminate.
+    There was a legitimate value before this,
+    but this is still invalid JS/JSON5.
diff --git a/test/parse-cases/misc/empty.txt b/test/parse-cases/misc/empty.txt
new file mode 100644
index 0000000..e69de29
diff --git a/test/parse-cases/misc/npm-package.json b/test/parse-cases/misc/npm-package.json
new file mode 100644
index 0000000..85568da
--- /dev/null
+++ b/test/parse-cases/misc/npm-package.json
@@ -0,0 +1,106 @@
+{
+  "name": "npm",
+  "publishConfig": {
+    "proprietary-attribs": false
+  },
+  "description": "A package manager for node",
+  "keywords": [
+    "package manager",
+    "modules",
+    "install",
+    "package.json"
+  ],
+  "version": "1.1.22",
+  "preferGlobal": true,
+  "config": {
+    "publishtest": false
+  },
+  "homepage": "http://npmjs.org/",
+  "author": "Isaac Z. Schlueter <i at izs.me> (http://blog.izs.me)",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/isaacs/npm"
+  },
+  "bugs": {
+    "email": "npm- at googlegroups.com",
+    "url": "http://github.com/isaacs/npm/issues"
+  },
+  "directories": {
+    "doc": "./doc",
+    "man": "./man",
+    "lib": "./lib",
+    "bin": "./bin"
+  },
+  "main": "./lib/npm.js",
+  "bin": "./bin/npm-cli.js",
+  "dependencies": {
+    "semver": "~1.0.14",
+    "ini": "1",
+    "slide": "1",
+    "abbrev": "1",
+    "graceful-fs": "~1.1.1",
+    "minimatch": "~0.2",
+    "nopt": "1",
+    "node-uuid": "~1.3",
+    "proto-list": "1",
+    "rimraf": "2",
+    "request": "~2.9",
+    "which": "1",
+    "tar": "~0.1.12",
+    "fstream": "~0.1.17",
+    "block-stream": "*",
+    "inherits": "1",
+    "mkdirp": "0.3",
+    "read": "0",
+    "lru-cache": "1",
+    "node-gyp": "~0.4.1",
+    "fstream-npm": "0 >=0.0.5",
+    "uid-number": "0",
+    "archy": "0",
+    "chownr": "0"
+  },
+  "bundleDependencies": [
+    "slide",
+    "ini",
+    "semver",
+    "abbrev",
+    "graceful-fs",
+    "minimatch",
+    "nopt",
+    "node-uuid",
+    "rimraf",
+    "request",
+    "proto-list",
+    "which",
+    "tar",
+    "fstream",
+    "block-stream",
+    "inherits",
+    "mkdirp",
+    "read",
+    "lru-cache",
+    "node-gyp",
+    "fstream-npm",
+    "uid-number",
+    "archy",
+    "chownr"
+  ],
+  "devDependencies": {
+    "ronn": "https://github.com/isaacs/ronnjs/tarball/master"
+  },
+  "engines": {
+    "node": "0.6 || 0.7 || 0.8",
+    "npm": "1"
+  },
+  "scripts": {
+    "test": "node ./test/run.js",
+    "prepublish": "npm prune; rm -rf node_modules/*/{test,example,bench}*; make -j4 doc",
+    "dumpconf": "env | grep npm | sort | uniq"
+  },
+  "licenses": [
+    {
+      "type": "MIT +no-false-attribs",
+      "url": "http://github.com/isaacs/npm/raw/master/LICENSE"
+    }
+  ]
+}
diff --git a/test/parse-cases/misc/npm-package.json5 b/test/parse-cases/misc/npm-package.json5
new file mode 100644
index 0000000..6994406
--- /dev/null
+++ b/test/parse-cases/misc/npm-package.json5
@@ -0,0 +1,106 @@
+{
+  name: 'npm',
+  publishConfig: {
+    'proprietary-attribs': false,
+  },
+  description: 'A package manager for node',
+  keywords: [
+    'package manager',
+    'modules',
+    'install',
+    'package.json',
+  ],
+  version: '1.1.22',
+  preferGlobal: true,
+  config: {
+    publishtest: false,
+  },
+  homepage: 'http://npmjs.org/',
+  author: 'Isaac Z. Schlueter <i at izs.me> (http://blog.izs.me)',
+  repository: {
+    type: 'git',
+    url: 'https://github.com/isaacs/npm',
+  },
+  bugs: {
+    email: 'npm- at googlegroups.com',
+    url: 'http://github.com/isaacs/npm/issues',
+  },
+  directories: {
+    doc: './doc',
+    man: './man',
+    lib: './lib',
+    bin: './bin',
+  },
+  main: './lib/npm.js',
+  bin: './bin/npm-cli.js',
+  dependencies: {
+    semver: '~1.0.14',
+    ini: '1',
+    slide: '1',
+    abbrev: '1',
+    'graceful-fs': '~1.1.1',
+    minimatch: '~0.2',
+    nopt: '1',
+    'node-uuid': '~1.3',
+    'proto-list': '1',
+    rimraf: '2',
+    request: '~2.9',
+    which: '1',
+    tar: '~0.1.12',
+    fstream: '~0.1.17',
+    'block-stream': '*',
+    inherits: '1',
+    mkdirp: '0.3',
+    read: '0',
+    'lru-cache': '1',
+    'node-gyp': '~0.4.1',
+    'fstream-npm': '0 >=0.0.5',
+    'uid-number': '0',
+    archy: '0',
+    chownr: '0',
+  },
+  bundleDependencies: [
+    'slide',
+    'ini',
+    'semver',
+    'abbrev',
+    'graceful-fs',
+    'minimatch',
+    'nopt',
+    'node-uuid',
+    'rimraf',
+    'request',
+    'proto-list',
+    'which',
+    'tar',
+    'fstream',
+    'block-stream',
+    'inherits',
+    'mkdirp',
+    'read',
+    'lru-cache',
+    'node-gyp',
+    'fstream-npm',
+    'uid-number',
+    'archy',
+    'chownr',
+  ],
+  devDependencies: {
+    ronn: 'https://github.com/isaacs/ronnjs/tarball/master',
+  },
+  engines: {
+    node: '0.6 || 0.7 || 0.8',
+    npm: '1',
+  },
+  scripts: {
+    test: 'node ./test/run.js',
+    prepublish: 'npm prune; rm -rf node_modules/*/{test,example,bench}*; make -j4 doc',
+    dumpconf: 'env | grep npm | sort | uniq',
+  },
+  licenses: [
+    {
+      type: 'MIT +no-false-attribs',
+      url: 'http://github.com/isaacs/npm/raw/master/LICENSE',
+    },
+  ],
+}
diff --git a/test/parse-cases/misc/readme-example.json5 b/test/parse-cases/misc/readme-example.json5
new file mode 100644
index 0000000..25c920a
--- /dev/null
+++ b/test/parse-cases/misc/readme-example.json5
@@ -0,0 +1,25 @@
+{
+    foo: 'bar',
+    while: true,
+
+    this: 'is a \
+multi-line string',
+
+    // this is an inline comment
+    here: 'is another', // inline comment
+
+    /* this is a block comment
+       that continues on another line */
+
+    hex: 0xDEADbeef,
+    half: .5,
+    delta: +10,
+    to: Infinity,   // and beyond!
+
+    finally: 'a trailing comma',
+    oh: [
+        "we shouldn't forget",
+        'arrays can have',
+        'trailing commas too',
+    ],
+}
diff --git a/test/parse-cases/misc/valid-whitespace.json5 b/test/parse-cases/misc/valid-whitespace.json5
new file mode 100644
index 0000000..5cb57d3
--- /dev/null
+++ b/test/parse-cases/misc/valid-whitespace.json5
@@ -0,0 +1,5 @@
+{
+ 
   // An invalid form feed character (\x0c) has been entered before this comment.
+    // Be careful not to delete it.
+  "a": true
+}
diff --git a/test/parse-cases/new-lines/.editorconfig b/test/parse-cases/new-lines/.editorconfig
new file mode 100644
index 0000000..1784f9e
--- /dev/null
+++ b/test/parse-cases/new-lines/.editorconfig
@@ -0,0 +1,13 @@
+# Since we're testing different representations of new lines,
+# make sure the editor doesn't mangle line endings.
+# Don't commit files in this directory unless you've checked
+# their escaped new lines.
+
+[*-lf.*]
+end_of_line = lf
+
+[*-cr.*]
+end_of_line = cr
+
+[*-crlf.*]
+end_of_line = crlf
diff --git a/test/parse-cases/new-lines/.gitattributes b/test/parse-cases/new-lines/.gitattributes
new file mode 100644
index 0000000..2b3eea6
--- /dev/null
+++ b/test/parse-cases/new-lines/.gitattributes
@@ -0,0 +1,4 @@
+# Since we're testing different representations of new lines,
+# treat all tests in this folder as binary files.
+
+* binary
diff --git a/test/parse-cases/new-lines/comment-cr.json5 b/test/parse-cases/new-lines/comment-cr.json5
new file mode 100644
index 0000000..e55aff8
--- /dev/null
+++ b/test/parse-cases/new-lines/comment-cr.json5
@@ -0,0 +1 @@
+{
    // This comment is terminated with `\r`.
}
\ No newline at end of file
diff --git a/test/parse-cases/new-lines/comment-crlf.json5 b/test/parse-cases/new-lines/comment-crlf.json5
new file mode 100644
index 0000000..3791ee6
--- /dev/null
+++ b/test/parse-cases/new-lines/comment-crlf.json5
@@ -0,0 +1,3 @@
+{
+    // This comment is terminated with `\r\n`.
+}
diff --git a/test/parse-cases/new-lines/comment-lf.json5 b/test/parse-cases/new-lines/comment-lf.json5
new file mode 100644
index 0000000..e17dd72
--- /dev/null
+++ b/test/parse-cases/new-lines/comment-lf.json5
@@ -0,0 +1,3 @@
+{
+    // This comment is terminated with `\n`.
+}
diff --git a/test/parse-cases/new-lines/escaped-cr.json5 b/test/parse-cases/new-lines/escaped-cr.json5
new file mode 100644
index 0000000..38e55b6
--- /dev/null
+++ b/test/parse-cases/new-lines/escaped-cr.json5
@@ -0,0 +1 @@
+{
    // the following string contains an escaped `\r`
    a: 'line 1 \
line 2'
}
\ No newline at end of file
diff --git a/test/parse-cases/new-lines/escaped-crlf.json5 b/test/parse-cases/new-lines/escaped-crlf.json5
new file mode 100644
index 0000000..7e3f1ce
--- /dev/null
+++ b/test/parse-cases/new-lines/escaped-crlf.json5
@@ -0,0 +1,5 @@
+{
+    // the following string contains an escaped `\r\n`
+    a: 'line 1 \
+line 2'
+}
diff --git a/test/parse-cases/new-lines/escaped-lf.json5 b/test/parse-cases/new-lines/escaped-lf.json5
new file mode 100644
index 0000000..2235e8c
--- /dev/null
+++ b/test/parse-cases/new-lines/escaped-lf.json5
@@ -0,0 +1,5 @@
+{
+    // the following string contains an escaped `\n`
+    a: 'line 1 \
+line 2'
+}
diff --git a/test/parse-cases/numbers/float-leading-decimal-point.json5 b/test/parse-cases/numbers/float-leading-decimal-point.json5
new file mode 100644
index 0000000..d6c9fff
--- /dev/null
+++ b/test/parse-cases/numbers/float-leading-decimal-point.json5
@@ -0,0 +1 @@
+.5
diff --git a/test/parse-cases/numbers/float-leading-zero.json b/test/parse-cases/numbers/float-leading-zero.json
new file mode 100644
index 0000000..2eb3c4f
--- /dev/null
+++ b/test/parse-cases/numbers/float-leading-zero.json
@@ -0,0 +1 @@
+0.5
diff --git a/test/parse-cases/numbers/float-trailing-decimal-point-with-integer-exponent.json5 b/test/parse-cases/numbers/float-trailing-decimal-point-with-integer-exponent.json5
new file mode 100644
index 0000000..70b8720
--- /dev/null
+++ b/test/parse-cases/numbers/float-trailing-decimal-point-with-integer-exponent.json5
@@ -0,0 +1 @@
+5.e4
diff --git a/test/parse-cases/numbers/float-trailing-decimal-point.json5 b/test/parse-cases/numbers/float-trailing-decimal-point.json5
new file mode 100644
index 0000000..e4c8c31
--- /dev/null
+++ b/test/parse-cases/numbers/float-trailing-decimal-point.json5
@@ -0,0 +1 @@
+5.
diff --git a/test/parse-cases/numbers/float-with-integer-exponent.json b/test/parse-cases/numbers/float-with-integer-exponent.json
new file mode 100644
index 0000000..0e957c6
--- /dev/null
+++ b/test/parse-cases/numbers/float-with-integer-exponent.json
@@ -0,0 +1 @@
+1.2e3
diff --git a/test/parse-cases/numbers/float.json b/test/parse-cases/numbers/float.json
new file mode 100644
index 0000000..5625e59
--- /dev/null
+++ b/test/parse-cases/numbers/float.json
@@ -0,0 +1 @@
+1.2
diff --git a/test/parse-cases/numbers/hexadecimal-empty.txt b/test/parse-cases/numbers/hexadecimal-empty.txt
new file mode 100644
index 0000000..ec68726
--- /dev/null
+++ b/test/parse-cases/numbers/hexadecimal-empty.txt
@@ -0,0 +1 @@
+0x
diff --git a/test/parse-cases/numbers/hexadecimal-lowercase-letter.json5 b/test/parse-cases/numbers/hexadecimal-lowercase-letter.json5
new file mode 100644
index 0000000..57e27ee
--- /dev/null
+++ b/test/parse-cases/numbers/hexadecimal-lowercase-letter.json5
@@ -0,0 +1 @@
+0xc8
diff --git a/test/parse-cases/numbers/hexadecimal-uppercase-x.json5 b/test/parse-cases/numbers/hexadecimal-uppercase-x.json5
new file mode 100644
index 0000000..1a35066
--- /dev/null
+++ b/test/parse-cases/numbers/hexadecimal-uppercase-x.json5
@@ -0,0 +1 @@
+0XC8
diff --git a/test/parse-cases/numbers/hexadecimal-with-integer-exponent.json5 b/test/parse-cases/numbers/hexadecimal-with-integer-exponent.json5
new file mode 100644
index 0000000..3c2204a
--- /dev/null
+++ b/test/parse-cases/numbers/hexadecimal-with-integer-exponent.json5
@@ -0,0 +1 @@
+0xc8e4
diff --git a/test/parse-cases/numbers/hexadecimal.json5 b/test/parse-cases/numbers/hexadecimal.json5
new file mode 100644
index 0000000..cf832ed
--- /dev/null
+++ b/test/parse-cases/numbers/hexadecimal.json5
@@ -0,0 +1 @@
+0xC8
diff --git a/test/parse-cases/numbers/infinity.json5 b/test/parse-cases/numbers/infinity.json5
new file mode 100644
index 0000000..3c62151
--- /dev/null
+++ b/test/parse-cases/numbers/infinity.json5
@@ -0,0 +1 @@
+Infinity
diff --git a/test/parse-cases/numbers/integer-with-float-exponent.txt b/test/parse-cases/numbers/integer-with-float-exponent.txt
new file mode 100644
index 0000000..fa0688c
--- /dev/null
+++ b/test/parse-cases/numbers/integer-with-float-exponent.txt
@@ -0,0 +1 @@
+1e2.3
diff --git a/test/parse-cases/numbers/integer-with-hexadecimal-exponent.txt b/test/parse-cases/numbers/integer-with-hexadecimal-exponent.txt
new file mode 100644
index 0000000..0f58237
--- /dev/null
+++ b/test/parse-cases/numbers/integer-with-hexadecimal-exponent.txt
@@ -0,0 +1 @@
+1e0x4
diff --git a/test/parse-cases/numbers/integer-with-integer-exponent.json b/test/parse-cases/numbers/integer-with-integer-exponent.json
new file mode 100644
index 0000000..0d5cde8
--- /dev/null
+++ b/test/parse-cases/numbers/integer-with-integer-exponent.json
@@ -0,0 +1 @@
+2e23
diff --git a/test/parse-cases/numbers/integer-with-negative-float-exponent.txt b/test/parse-cases/numbers/integer-with-negative-float-exponent.txt
new file mode 100644
index 0000000..5be0915
--- /dev/null
+++ b/test/parse-cases/numbers/integer-with-negative-float-exponent.txt
@@ -0,0 +1 @@
+1e-2.3
diff --git a/test/parse-cases/numbers/integer-with-negative-hexadecimal-exponent.txt b/test/parse-cases/numbers/integer-with-negative-hexadecimal-exponent.txt
new file mode 100644
index 0000000..adeb2b9
--- /dev/null
+++ b/test/parse-cases/numbers/integer-with-negative-hexadecimal-exponent.txt
@@ -0,0 +1 @@
+1e-0x4
diff --git a/test/parse-cases/numbers/integer-with-negative-integer-exponent.json b/test/parse-cases/numbers/integer-with-negative-integer-exponent.json
new file mode 100644
index 0000000..6118c3e
--- /dev/null
+++ b/test/parse-cases/numbers/integer-with-negative-integer-exponent.json
@@ -0,0 +1 @@
+2e-23
diff --git a/test/parse-cases/numbers/integer-with-negative-zero-integer-exponent.json b/test/parse-cases/numbers/integer-with-negative-zero-integer-exponent.json
new file mode 100644
index 0000000..eb67bf4
--- /dev/null
+++ b/test/parse-cases/numbers/integer-with-negative-zero-integer-exponent.json
@@ -0,0 +1 @@
+5e-0
diff --git a/test/parse-cases/numbers/integer-with-positive-float-exponent.txt b/test/parse-cases/numbers/integer-with-positive-float-exponent.txt
new file mode 100644
index 0000000..f89d55e
--- /dev/null
+++ b/test/parse-cases/numbers/integer-with-positive-float-exponent.txt
@@ -0,0 +1 @@
+1e+2.3
diff --git a/test/parse-cases/numbers/integer-with-positive-hexadecimal-exponent.txt b/test/parse-cases/numbers/integer-with-positive-hexadecimal-exponent.txt
new file mode 100644
index 0000000..a6c75d9
--- /dev/null
+++ b/test/parse-cases/numbers/integer-with-positive-hexadecimal-exponent.txt
@@ -0,0 +1 @@
+1e+0x4
diff --git a/test/parse-cases/numbers/integer-with-positive-integer-exponent.json b/test/parse-cases/numbers/integer-with-positive-integer-exponent.json
new file mode 100644
index 0000000..90c0616
--- /dev/null
+++ b/test/parse-cases/numbers/integer-with-positive-integer-exponent.json
@@ -0,0 +1 @@
+1e+2
diff --git a/test/parse-cases/numbers/integer-with-positive-zero-integer-exponent.json b/test/parse-cases/numbers/integer-with-positive-zero-integer-exponent.json
new file mode 100644
index 0000000..1d7002f
--- /dev/null
+++ b/test/parse-cases/numbers/integer-with-positive-zero-integer-exponent.json
@@ -0,0 +1 @@
+5e+0
diff --git a/test/parse-cases/numbers/integer-with-zero-integer-exponent.json b/test/parse-cases/numbers/integer-with-zero-integer-exponent.json
new file mode 100644
index 0000000..a5e3196
--- /dev/null
+++ b/test/parse-cases/numbers/integer-with-zero-integer-exponent.json
@@ -0,0 +1 @@
+5e0
diff --git a/test/parse-cases/numbers/integer.json b/test/parse-cases/numbers/integer.json
new file mode 100644
index 0000000..60d3b2f
--- /dev/null
+++ b/test/parse-cases/numbers/integer.json
@@ -0,0 +1 @@
+15
diff --git a/test/parse-cases/numbers/lone-decimal-point.txt b/test/parse-cases/numbers/lone-decimal-point.txt
new file mode 100644
index 0000000..9c558e3
--- /dev/null
+++ b/test/parse-cases/numbers/lone-decimal-point.txt
@@ -0,0 +1 @@
+.
diff --git a/test/parse-cases/numbers/nan.json5 b/test/parse-cases/numbers/nan.json5
new file mode 100644
index 0000000..736991a
--- /dev/null
+++ b/test/parse-cases/numbers/nan.json5
@@ -0,0 +1 @@
+NaN
diff --git a/test/parse-cases/numbers/negative-float-leading-decimal-point.json5 b/test/parse-cases/numbers/negative-float-leading-decimal-point.json5
new file mode 100644
index 0000000..c6eaee5
--- /dev/null
+++ b/test/parse-cases/numbers/negative-float-leading-decimal-point.json5
@@ -0,0 +1 @@
+-.5
diff --git a/test/parse-cases/numbers/negative-float-leading-zero.json b/test/parse-cases/numbers/negative-float-leading-zero.json
new file mode 100644
index 0000000..e118203
--- /dev/null
+++ b/test/parse-cases/numbers/negative-float-leading-zero.json
@@ -0,0 +1 @@
+-0.5
diff --git a/test/parse-cases/numbers/negative-float-trailing-decimal-point.json5 b/test/parse-cases/numbers/negative-float-trailing-decimal-point.json5
new file mode 100644
index 0000000..52e5245
--- /dev/null
+++ b/test/parse-cases/numbers/negative-float-trailing-decimal-point.json5
@@ -0,0 +1 @@
+-5.
diff --git a/test/parse-cases/numbers/negative-float.json b/test/parse-cases/numbers/negative-float.json
new file mode 100644
index 0000000..1d94c8a
--- /dev/null
+++ b/test/parse-cases/numbers/negative-float.json
@@ -0,0 +1 @@
+-1.2
diff --git a/test/parse-cases/numbers/negative-hexadecimal.json5 b/test/parse-cases/numbers/negative-hexadecimal.json5
new file mode 100644
index 0000000..8882fae
--- /dev/null
+++ b/test/parse-cases/numbers/negative-hexadecimal.json5
@@ -0,0 +1 @@
+-0xC8
diff --git a/test/parse-cases/numbers/negative-infinity.json5 b/test/parse-cases/numbers/negative-infinity.json5
new file mode 100644
index 0000000..879e80e
--- /dev/null
+++ b/test/parse-cases/numbers/negative-infinity.json5
@@ -0,0 +1 @@
+-Infinity
diff --git a/test/parse-cases/numbers/negative-integer.json b/test/parse-cases/numbers/negative-integer.json
new file mode 100644
index 0000000..2192236
--- /dev/null
+++ b/test/parse-cases/numbers/negative-integer.json
@@ -0,0 +1 @@
+-15
diff --git a/test/parse-cases/numbers/negative-noctal.js b/test/parse-cases/numbers/negative-noctal.js
new file mode 100644
index 0000000..8826f48
--- /dev/null
+++ b/test/parse-cases/numbers/negative-noctal.js
@@ -0,0 +1 @@
+-098
diff --git a/test/parse-cases/numbers/negative-octal.txt b/test/parse-cases/numbers/negative-octal.txt
new file mode 100644
index 0000000..2e7a4b4
--- /dev/null
+++ b/test/parse-cases/numbers/negative-octal.txt
@@ -0,0 +1 @@
+-0123
diff --git a/test/parse-cases/numbers/negative-zero-float-leading-decimal-point.json5 b/test/parse-cases/numbers/negative-zero-float-leading-decimal-point.json5
new file mode 100644
index 0000000..8dd8e03
--- /dev/null
+++ b/test/parse-cases/numbers/negative-zero-float-leading-decimal-point.json5
@@ -0,0 +1 @@
+-.0
diff --git a/test/parse-cases/numbers/negative-zero-float-trailing-decimal-point.json5 b/test/parse-cases/numbers/negative-zero-float-trailing-decimal-point.json5
new file mode 100644
index 0000000..90cc048
--- /dev/null
+++ b/test/parse-cases/numbers/negative-zero-float-trailing-decimal-point.json5
@@ -0,0 +1 @@
+-0.
diff --git a/test/parse-cases/numbers/negative-zero-float.json b/test/parse-cases/numbers/negative-zero-float.json
new file mode 100644
index 0000000..1344bfd
--- /dev/null
+++ b/test/parse-cases/numbers/negative-zero-float.json
@@ -0,0 +1 @@
+-0.0
diff --git a/test/parse-cases/numbers/negative-zero-hexadecimal.json5 b/test/parse-cases/numbers/negative-zero-hexadecimal.json5
new file mode 100644
index 0000000..8847d05
--- /dev/null
+++ b/test/parse-cases/numbers/negative-zero-hexadecimal.json5
@@ -0,0 +1 @@
+-0x0
diff --git a/test/parse-cases/numbers/negative-zero-integer.json b/test/parse-cases/numbers/negative-zero-integer.json
new file mode 100644
index 0000000..ec064f6
--- /dev/null
+++ b/test/parse-cases/numbers/negative-zero-integer.json
@@ -0,0 +1 @@
+-0
diff --git a/test/parse-cases/numbers/negative-zero-octal.txt b/test/parse-cases/numbers/negative-zero-octal.txt
new file mode 100644
index 0000000..200a801
--- /dev/null
+++ b/test/parse-cases/numbers/negative-zero-octal.txt
@@ -0,0 +1 @@
+-00
diff --git a/test/parse-cases/numbers/noctal-with-leading-octal-digit.js b/test/parse-cases/numbers/noctal-with-leading-octal-digit.js
new file mode 100644
index 0000000..1fd7c08
--- /dev/null
+++ b/test/parse-cases/numbers/noctal-with-leading-octal-digit.js
@@ -0,0 +1 @@
+0780
diff --git a/test/parse-cases/numbers/noctal.js b/test/parse-cases/numbers/noctal.js
new file mode 100644
index 0000000..fa5c783
--- /dev/null
+++ b/test/parse-cases/numbers/noctal.js
@@ -0,0 +1 @@
+080
diff --git a/test/parse-cases/numbers/octal.txt b/test/parse-cases/numbers/octal.txt
new file mode 100644
index 0000000..9e8493e
--- /dev/null
+++ b/test/parse-cases/numbers/octal.txt
@@ -0,0 +1 @@
+010
diff --git a/test/parse-cases/numbers/positive-float-leading-decimal-point.json5 b/test/parse-cases/numbers/positive-float-leading-decimal-point.json5
new file mode 100644
index 0000000..0434608
--- /dev/null
+++ b/test/parse-cases/numbers/positive-float-leading-decimal-point.json5
@@ -0,0 +1 @@
++.5
diff --git a/test/parse-cases/numbers/positive-float-leading-zero.json5 b/test/parse-cases/numbers/positive-float-leading-zero.json5
new file mode 100644
index 0000000..d89b45d
--- /dev/null
+++ b/test/parse-cases/numbers/positive-float-leading-zero.json5
@@ -0,0 +1 @@
++0.5
diff --git a/test/parse-cases/numbers/positive-float-trailing-decimal-point.json5 b/test/parse-cases/numbers/positive-float-trailing-decimal-point.json5
new file mode 100644
index 0000000..bee758a
--- /dev/null
+++ b/test/parse-cases/numbers/positive-float-trailing-decimal-point.json5
@@ -0,0 +1 @@
++5.
diff --git a/test/parse-cases/numbers/positive-float.json5 b/test/parse-cases/numbers/positive-float.json5
new file mode 100644
index 0000000..c5732cb
--- /dev/null
+++ b/test/parse-cases/numbers/positive-float.json5
@@ -0,0 +1 @@
++1.2
diff --git a/test/parse-cases/numbers/positive-hexadecimal.json5 b/test/parse-cases/numbers/positive-hexadecimal.json5
new file mode 100644
index 0000000..c91ede9
--- /dev/null
+++ b/test/parse-cases/numbers/positive-hexadecimal.json5
@@ -0,0 +1 @@
++0xC8
diff --git a/test/parse-cases/numbers/positive-infinity.json5 b/test/parse-cases/numbers/positive-infinity.json5
new file mode 100644
index 0000000..9bcb989
--- /dev/null
+++ b/test/parse-cases/numbers/positive-infinity.json5
@@ -0,0 +1 @@
++Infinity
diff --git a/test/parse-cases/numbers/positive-integer.json5 b/test/parse-cases/numbers/positive-integer.json5
new file mode 100644
index 0000000..8ed01e0
--- /dev/null
+++ b/test/parse-cases/numbers/positive-integer.json5
@@ -0,0 +1 @@
++15
diff --git a/test/parse-cases/numbers/positive-noctal.js b/test/parse-cases/numbers/positive-noctal.js
new file mode 100644
index 0000000..2f450fc
--- /dev/null
+++ b/test/parse-cases/numbers/positive-noctal.js
@@ -0,0 +1 @@
++098
diff --git a/test/parse-cases/numbers/positive-octal.txt b/test/parse-cases/numbers/positive-octal.txt
new file mode 100644
index 0000000..faa8600
--- /dev/null
+++ b/test/parse-cases/numbers/positive-octal.txt
@@ -0,0 +1 @@
++0123
diff --git a/test/parse-cases/numbers/positive-zero-float-leading-decimal-point.json5 b/test/parse-cases/numbers/positive-zero-float-leading-decimal-point.json5
new file mode 100644
index 0000000..557bcde
--- /dev/null
+++ b/test/parse-cases/numbers/positive-zero-float-leading-decimal-point.json5
@@ -0,0 +1 @@
++.0
diff --git a/test/parse-cases/numbers/positive-zero-float-trailing-decimal-point.json5 b/test/parse-cases/numbers/positive-zero-float-trailing-decimal-point.json5
new file mode 100644
index 0000000..d8912d1
--- /dev/null
+++ b/test/parse-cases/numbers/positive-zero-float-trailing-decimal-point.json5
@@ -0,0 +1 @@
++0.
diff --git a/test/parse-cases/numbers/positive-zero-float.json5 b/test/parse-cases/numbers/positive-zero-float.json5
new file mode 100644
index 0000000..11e8402
--- /dev/null
+++ b/test/parse-cases/numbers/positive-zero-float.json5
@@ -0,0 +1 @@
++0.0
diff --git a/test/parse-cases/numbers/positive-zero-hexadecimal.json5 b/test/parse-cases/numbers/positive-zero-hexadecimal.json5
new file mode 100644
index 0000000..40a9ce6
--- /dev/null
+++ b/test/parse-cases/numbers/positive-zero-hexadecimal.json5
@@ -0,0 +1 @@
++0x0
diff --git a/test/parse-cases/numbers/positive-zero-integer.json5 b/test/parse-cases/numbers/positive-zero-integer.json5
new file mode 100644
index 0000000..9317bcb
--- /dev/null
+++ b/test/parse-cases/numbers/positive-zero-integer.json5
@@ -0,0 +1 @@
++0
diff --git a/test/parse-cases/numbers/positive-zero-octal.txt b/test/parse-cases/numbers/positive-zero-octal.txt
new file mode 100644
index 0000000..80959e5
--- /dev/null
+++ b/test/parse-cases/numbers/positive-zero-octal.txt
@@ -0,0 +1 @@
++00
diff --git a/test/parse-cases/numbers/zero-float-leading-decimal-point.json5 b/test/parse-cases/numbers/zero-float-leading-decimal-point.json5
new file mode 100644
index 0000000..7d856fd
--- /dev/null
+++ b/test/parse-cases/numbers/zero-float-leading-decimal-point.json5
@@ -0,0 +1 @@
+.0
diff --git a/test/parse-cases/numbers/zero-float-trailing-decimal-point.json5 b/test/parse-cases/numbers/zero-float-trailing-decimal-point.json5
new file mode 100644
index 0000000..17a5757
--- /dev/null
+++ b/test/parse-cases/numbers/zero-float-trailing-decimal-point.json5
@@ -0,0 +1 @@
+0.
diff --git a/test/parse-cases/numbers/zero-float.json b/test/parse-cases/numbers/zero-float.json
new file mode 100644
index 0000000..ba66466
--- /dev/null
+++ b/test/parse-cases/numbers/zero-float.json
@@ -0,0 +1 @@
+0.0
diff --git a/test/parse-cases/numbers/zero-hexadecimal.json5 b/test/parse-cases/numbers/zero-hexadecimal.json5
new file mode 100644
index 0000000..9982566
--- /dev/null
+++ b/test/parse-cases/numbers/zero-hexadecimal.json5
@@ -0,0 +1 @@
+0x0
diff --git a/test/parse-cases/numbers/zero-integer-with-integer-exponent.json b/test/parse-cases/numbers/zero-integer-with-integer-exponent.json
new file mode 100644
index 0000000..da219e3
--- /dev/null
+++ b/test/parse-cases/numbers/zero-integer-with-integer-exponent.json
@@ -0,0 +1 @@
+0e23
diff --git a/test/parse-cases/numbers/zero-integer.json b/test/parse-cases/numbers/zero-integer.json
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/test/parse-cases/numbers/zero-integer.json
@@ -0,0 +1 @@
+0
diff --git a/test/parse-cases/numbers/zero-octal.txt b/test/parse-cases/numbers/zero-octal.txt
new file mode 100644
index 0000000..4daddb7
--- /dev/null
+++ b/test/parse-cases/numbers/zero-octal.txt
@@ -0,0 +1 @@
+00
diff --git a/test/parse-cases/objects/duplicate-keys.json b/test/parse-cases/objects/duplicate-keys.json
new file mode 100644
index 0000000..bb0e4cc
--- /dev/null
+++ b/test/parse-cases/objects/duplicate-keys.json
@@ -0,0 +1,4 @@
+{
+    "a": true,
+    "a": false
+}
diff --git a/test/parse-cases/objects/empty-object.json b/test/parse-cases/objects/empty-object.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/test/parse-cases/objects/empty-object.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/test/parse-cases/objects/illegal-unquoted-key-number.errorSpec b/test/parse-cases/objects/illegal-unquoted-key-number.errorSpec
new file mode 100644
index 0000000..e44dc85
--- /dev/null
+++ b/test/parse-cases/objects/illegal-unquoted-key-number.errorSpec
@@ -0,0 +1,6 @@
+{
+    at: 7,
+    lineNumber: 2,
+    columnNumber: 5,
+    message: "Bad identifier as unquoted key"
+}
\ No newline at end of file
diff --git a/test/parse-cases/objects/illegal-unquoted-key-number.txt b/test/parse-cases/objects/illegal-unquoted-key-number.txt
new file mode 100644
index 0000000..aebcac2
--- /dev/null
+++ b/test/parse-cases/objects/illegal-unquoted-key-number.txt
@@ -0,0 +1,3 @@
+{
+    10twenty: "ten twenty"
+}
\ No newline at end of file
diff --git a/test/parse-cases/objects/illegal-unquoted-key-symbol.errorSpec b/test/parse-cases/objects/illegal-unquoted-key-symbol.errorSpec
new file mode 100644
index 0000000..95ba468
--- /dev/null
+++ b/test/parse-cases/objects/illegal-unquoted-key-symbol.errorSpec
@@ -0,0 +1,6 @@
+{
+    at: 12,
+    lineNumber: 2,
+    columnNumber: 10,
+    message: "Expected ':' instead of '-'"
+}
\ No newline at end of file
diff --git a/test/parse-cases/objects/illegal-unquoted-key-symbol.txt b/test/parse-cases/objects/illegal-unquoted-key-symbol.txt
new file mode 100644
index 0000000..4cb2bd5
--- /dev/null
+++ b/test/parse-cases/objects/illegal-unquoted-key-symbol.txt
@@ -0,0 +1,3 @@
+{
+    multi-word: "multi-word"
+}
\ No newline at end of file
diff --git a/test/parse-cases/objects/leading-comma-object.errorSpec b/test/parse-cases/objects/leading-comma-object.errorSpec
new file mode 100644
index 0000000..e44dc85
--- /dev/null
+++ b/test/parse-cases/objects/leading-comma-object.errorSpec
@@ -0,0 +1,6 @@
+{
+    at: 7,
+    lineNumber: 2,
+    columnNumber: 5,
+    message: "Bad identifier as unquoted key"
+}
\ No newline at end of file
diff --git a/test/parse-cases/objects/leading-comma-object.txt b/test/parse-cases/objects/leading-comma-object.txt
new file mode 100644
index 0000000..bfb3c51
--- /dev/null
+++ b/test/parse-cases/objects/leading-comma-object.txt
@@ -0,0 +1,3 @@
+{
+    ,"foo": "bar"
+}
\ No newline at end of file
diff --git a/test/parse-cases/objects/lone-trailing-comma-object.txt b/test/parse-cases/objects/lone-trailing-comma-object.txt
new file mode 100644
index 0000000..3f3f9f7
--- /dev/null
+++ b/test/parse-cases/objects/lone-trailing-comma-object.txt
@@ -0,0 +1,3 @@
+{
+    ,
+}
\ No newline at end of file
diff --git a/test/parse-cases/objects/no-comma-object.txt b/test/parse-cases/objects/no-comma-object.txt
new file mode 100644
index 0000000..c073875
--- /dev/null
+++ b/test/parse-cases/objects/no-comma-object.txt
@@ -0,0 +1,4 @@
+{
+    "foo": "bar"
+    "hello": "world"
+}
\ No newline at end of file
diff --git a/test/parse-cases/objects/reserved-unquoted-key.json5 b/test/parse-cases/objects/reserved-unquoted-key.json5
new file mode 100644
index 0000000..4b80a63
--- /dev/null
+++ b/test/parse-cases/objects/reserved-unquoted-key.json5
@@ -0,0 +1,3 @@
+{
+    while: true
+}
\ No newline at end of file
diff --git a/test/parse-cases/objects/single-quoted-key.json5 b/test/parse-cases/objects/single-quoted-key.json5
new file mode 100644
index 0000000..842ca19
--- /dev/null
+++ b/test/parse-cases/objects/single-quoted-key.json5
@@ -0,0 +1,3 @@
+{
+    'hello': "world"
+}
\ No newline at end of file
diff --git a/test/parse-cases/objects/trailing-comma-object.json5 b/test/parse-cases/objects/trailing-comma-object.json5
new file mode 100644
index 0000000..ab61ba7
--- /dev/null
+++ b/test/parse-cases/objects/trailing-comma-object.json5
@@ -0,0 +1,3 @@
+{
+    "foo": "bar",
+}
\ No newline at end of file
diff --git a/test/parse-cases/objects/unquoted-keys.json5 b/test/parse-cases/objects/unquoted-keys.json5
new file mode 100644
index 0000000..0c06f3f
--- /dev/null
+++ b/test/parse-cases/objects/unquoted-keys.json5
@@ -0,0 +1,8 @@
+{
+    hello: "world",
+    _: "underscore",
+    $: "dollar sign",
+    one1: "numerals",
+    _$_: "multiple symbols",
+    $_$hello123world_$_: "mixed"
+}
\ No newline at end of file
diff --git a/test/parse-cases/strings/escaped-single-quoted-string.json5 b/test/parse-cases/strings/escaped-single-quoted-string.json5
new file mode 100644
index 0000000..1c79910
--- /dev/null
+++ b/test/parse-cases/strings/escaped-single-quoted-string.json5
@@ -0,0 +1 @@
+'I can\'t wait'
\ No newline at end of file
diff --git a/test/parse-cases/strings/multi-line-string.json5 b/test/parse-cases/strings/multi-line-string.json5
new file mode 100644
index 0000000..964dc2d
--- /dev/null
+++ b/test/parse-cases/strings/multi-line-string.json5
@@ -0,0 +1,2 @@
+'hello\
+ world'
\ No newline at end of file
diff --git a/test/parse-cases/strings/no-comma-array.errorSpec b/test/parse-cases/strings/no-comma-array.errorSpec
new file mode 100644
index 0000000..9835561
--- /dev/null
+++ b/test/parse-cases/strings/no-comma-array.errorSpec
@@ -0,0 +1,6 @@
+{
+    at: 16,
+    lineNumber: 3,
+    columNumber: 5,
+    message: "Expected ']' instead of 'f'"
+}
\ No newline at end of file
diff --git a/test/parse-cases/strings/single-quoted-string.json5 b/test/parse-cases/strings/single-quoted-string.json5
new file mode 100644
index 0000000..5dadd33
--- /dev/null
+++ b/test/parse-cases/strings/single-quoted-string.json5
@@ -0,0 +1 @@
+'hello world'
\ No newline at end of file
diff --git a/test/parse-cases/strings/unescaped-multi-line-string.errorSpec b/test/parse-cases/strings/unescaped-multi-line-string.errorSpec
new file mode 100644
index 0000000..a85f1ad
--- /dev/null
+++ b/test/parse-cases/strings/unescaped-multi-line-string.errorSpec
@@ -0,0 +1,6 @@
+{
+    at: 5,
+    lineNumber: 2,
+    columnNumber: 0,
+    message: "Bad string"
+}
\ No newline at end of file
diff --git a/test/parse-cases/strings/unescaped-multi-line-string.txt b/test/parse-cases/strings/unescaped-multi-line-string.txt
new file mode 100644
index 0000000..7325139
--- /dev/null
+++ b/test/parse-cases/strings/unescaped-multi-line-string.txt
@@ -0,0 +1,2 @@
+"foo
+bar"
diff --git a/test/parse-cases/todo/unicode-escaped-unquoted-key.json5 b/test/parse-cases/todo/unicode-escaped-unquoted-key.json5
new file mode 100644
index 0000000..56c3457
--- /dev/null
+++ b/test/parse-cases/todo/unicode-escaped-unquoted-key.json5
@@ -0,0 +1,3 @@
+{
+    sig\u03A3ma: "the sum of all things"
+}
\ No newline at end of file
diff --git a/test/parse-cases/todo/unicode-unquoted-key.json5 b/test/parse-cases/todo/unicode-unquoted-key.json5
new file mode 100644
index 0000000..98382e6
--- /dev/null
+++ b/test/parse-cases/todo/unicode-unquoted-key.json5
@@ -0,0 +1,3 @@
+{
+    ümlåût: "that's not really an ümlaüt, but this is"
+}
\ No newline at end of file
diff --git a/test/parse.js b/test/parse.js
new file mode 100644
index 0000000..d96e1a1
--- /dev/null
+++ b/test/parse.js
@@ -0,0 +1,133 @@
+// parse.js
+// Tests parse(). See readme.txt for details.
+
+"use strict";
+
+var assert = require('assert');
+var FS = require('fs');
+var JSON5 = require('..');
+var Path = require('path');
+
+// Test JSON5.parse() by comparing its output for each case with either the
+// native JSON.parse() or ES5 strict-mode eval(). See readme.txt for details.
+// For eval(), remember to wrap the input in parentheses before eval()'ing,
+// since {...} is ambiguous in JavaScript. Also ensure the parentheses are on
+// lines of their own, to support inline comments.
+
+// TODO More test cases, and ones that test specific features and edge cases.
+// Mozilla's test cases are a great inspiration and reference here:
+// http://mxr.mozilla.org/mozilla-central/source/js/src/tests/ecma_5/JSON/
+
+var dirsPath = Path.resolve(__dirname, 'parse-cases');
+var dirs = FS.readdirSync(dirsPath);
+
+var readErrorSpec = function (filePath) {
+    var specName = Path.basename(filePath, '.txt') + '.errorSpec';
+    var specPath = Path.join(Path.dirname(filePath), specName);
+    var specTxt;
+    try {
+        specTxt = FS.readFileSync(specPath); // note that existsSync has been deprecated
+    } catch (e) {}
+    if (specTxt) {
+        try {
+            return JSON5.parse(specTxt);
+        } catch (err) {
+            err.message = 'Error reading error specification file ' + specName + ': ' + err.message;
+            throw err;
+        }
+    }
+};
+
+var testParseJSON5 = function (filePath, str) {
+    var errorSpec = readErrorSpec(filePath);
+    var err;
+    try {
+        JSON5.parse(str);
+    } catch (e) {
+        err = e;
+    }
+    assert(err, 'Expected JSON5 parsing to fail.');
+    if (errorSpec) {
+        describe("Error fixture " + filePath, function () {
+        Object.keys(errorSpec).forEach(function (key) {
+            if (key === 'message') {
+                it('Expected error message\n' + err.message + '\nto start with ' + errorSpec.message, function () {
+                    assert(err.message.indexOf(errorSpec.message) === 0);
+                });
+            } else {
+                it('Expected parse error field ' + key + ' to hold value ' + errorSpec[key], function () {
+                    assert.equal(err[key], errorSpec[key]);
+                });
+            }
+        })
+        });
+    }
+};
+
+function createTest(fileName, dir) {
+    var ext = Path.extname(fileName);
+    var filePath = Path.join(dirsPath, dir, fileName);
+    var str = FS.readFileSync(filePath, 'utf8');
+
+    function parseJSON5() {
+        return JSON5.parse(str);
+    }
+
+    function parseJSON() {
+        return JSON.parse(str);
+    }
+
+    function parseES5() {
+        return eval('"use strict"; (\n' + str + '\n)');
+    }
+
+    exports[dir][fileName] = function test() {
+        switch (ext) {
+            case '.json':
+                assert.deepEqual(parseJSON5(), parseJSON(),
+                    'Expected parsed JSON5 to equal parsed JSON.');
+                break;
+            case '.json5':
+                assert.throws(parseJSON,        // test validation
+                    'Test case bug: expected JSON parsing to fail.');
+                // Need special case for NaN as NaN != NaN
+                if ( fileName === 'nan.json5' ) {
+                  assert.equal( isNaN( parseJSON5() ), isNaN( parseES5() ),
+                    'Expected parsed JSON5 to equal parsed ES5.');
+                }
+                else {
+                  assert.deepEqual( parseJSON5(), parseES5(),
+                    'Expected parsed JSON5 to equal parsed ES5.');
+                }
+                break;
+            case '.js':
+                assert.throws(parseJSON,        // test validation
+                    'Test case bug: expected JSON parsing to fail.');
+                assert.doesNotThrow(parseES5,   // test validation
+                    'Test case bug: expected ES5 parsing not to fail.');
+                assert.throws(parseJSON5,
+                    'Expected JSON5 parsing to fail.');
+                break;
+            case '.txt':
+                assert.throws(parseES5,         // test validation
+                    'Test case bug: expected ES5 parsing to fail.');
+                testParseJSON5(filePath, str);
+                break;
+        }
+    };
+}
+
+dirs.forEach(function (dir) {
+    // create a test suite for this group of tests:
+    exports[dir] = {};
+
+    // skip the TODO directory -- these tests are expected to fail:
+    if (dir === 'todo') {
+        return;
+    }
+
+    // otherwise create a test for each file in this group:
+    FS.readdirSync(Path.join(dirsPath, dir)).forEach(function (file) {
+        createTest(file, dir);
+    });
+});
diff --git a/test/readme.md b/test/readme.md
new file mode 100644
index 0000000..c8af4e8
--- /dev/null
+++ b/test/readme.md
@@ -0,0 +1,23 @@
+These tests are written for [Mocha][] using the [exports][] interface.
+
+[Mocha]: http://visionmedia.github.com/mocha/
+[exports]: http://visionmedia.github.com/mocha/#exports-interface
+
+The `parse()` tests are run by comparing the output of `JSON5.parse()` with
+that of the native `JSON.parse()` and ES5's `eval()` in strict mode. The test
+cases' file extension signals the expected behavior:
+
+- Valid JSON should remain valid JSON5. These cases have a `.json` extension
+  and are tested via `JSON.parse()`.
+
+- JSON5's new features should remain valid ES5. These cases have a `.json5`
+  extension are tested via `eval()`.
+
+- Valid ES5 that's explicitly disallowed by JSON5 is also invalid JSON. These
+  cases have a `.js` extension and are expected to fail.
+
+- Invalid ES5 should remain invalid JSON5. These cases have a `.txt` extension
+  and are expected to fail.
+
+This should cover all our bases. Most of the cases are unit tests for each
+supported data type, but aggregate test cases are welcome, too.
diff --git a/test/require.js b/test/require.js
new file mode 100644
index 0000000..35ff590
--- /dev/null
+++ b/test/require.js
@@ -0,0 +1,20 @@
+// require.js
+// Tests JSON5's require() hook.
+//
+// Important: expects the following test cases to be present:
+// - /parse-cases/misc/npm-package.json
+// - /parse-cases/misc/npm-package.json5
+
+"use strict";
+
+var assert = require('assert');
+
+exports['misc'] = {};
+exports['misc']['require hook'] = function () {
+    require('../lib/require');
+
+    var json = require('./parse-cases/misc/npm-package.json');
+    var json5 = require('./parse-cases/misc/npm-package.json5');
+
+    assert.deepEqual(json5, json);
+};
diff --git a/test/stringify.js b/test/stringify.js
new file mode 100644
index 0000000..7d86a69
--- /dev/null
+++ b/test/stringify.js
@@ -0,0 +1,496 @@
+// tests stringify()
+
+// set to true to show performance stats
+var DEBUG = false;
+
+var assert = require('assert');
+var JSON5 = require('../lib/json5');
+
+// Test JSON5.stringify() by comparing its output for each case with 
+// native JSON.stringify().  The only differences will be in how object keys are 
+// handled.
+
+var simpleCases = [
+    null,
+    9, -9, +9, +9.878,
+    '', "''", '999', '9aa', 'aaa', 'aa a', 'aa\na', 'aa\\a', '\'', '\\\'', '\\"',
+    undefined,
+    true, false,
+    {}, [], function(){},
+    Date.now(), new Date(Date.now())
+];
+
+exports.stringify = {};
+exports.stringify.simple = function test() {
+    for (var i=0; i<simpleCases.length; i++) {
+        assertStringify(simpleCases[i]);
+    }
+};
+
+exports.stringify.oddities = function test() {
+    assertStringify(Function);
+    assertStringify(Date);
+    assertStringify(Object);
+    assertStringify(NaN);
+    assertStringify(Infinity);
+    assertStringify(10e6);
+    assertStringify(19.3223e6);
+    assertStringify(077);
+    assertStringify(0x99);
+    assertStringify(/aa/);
+    assertStringify(new RegExp('aa'));
+    
+    assertStringify(new Number(7));
+    assertStringify(new String(7));
+    assertStringify(new String(""));
+    assertStringify(new String("abcde"));
+    assertStringify(new String(new String("abcde")));
+    assertStringify(new Boolean(true));
+    assertStringify(new Boolean());
+};
+
+exports.stringify.arrays = function test() {
+    assertStringify([""]);
+    assertStringify([1, 2]);
+    assertStringify([undefined]);
+    assertStringify([1, 'fasds']);
+    assertStringify([1, '\n\b\t\f\r\'']);
+    assertStringify([1, 'fasds', ['fdsafsd'], null]);
+    assertStringify([1, 'fasds', ['fdsafsd'], null, function(aaa) { return 1; }, false ]);
+    assertStringify([1, 'fasds', ['fdsafsd'], undefined, function(aaa) { return 1; }, false ]);
+};
+
+exports.stringify.objects = function test() {
+    assertStringify({a:1, b:2});
+    assertStringify({"":1, b:2});
+    assertStringify({9:1, b:2});
+    assertStringify({"9aaa":1, b:2});
+    assertStringify({aaaa:1, bbbb:2});
+    assertStringify({a$a_aa:1, bbbb:2});
+    assertStringify({"a$a_aa":1, 'bbbb':2});
+    assertStringify({"a$a_aa":[1], 'bbbb':{a:2}});
+    assertStringify({"a$22222_aa":[1], 'bbbb':{aaaa:2, name:function(a,n,fh,h) { return 'nuthin'; }, foo: undefined}});
+    assertStringify({"a$222222_aa":[1], 'bbbb':{aaaa:2, name:'other', foo: undefined}});
+    assertStringify({"a$222222_aa":[1, {}, undefined, function() { }, { jjj: function() { } }], 'bbbb':{aaaa:2, name:'other', foo: undefined}});
+    
+    // using same obj multiple times
+    var innerObj = {a: 9, b:6};
+    assertStringify({a : innerObj, b: innerObj, c: [innerObj, innerObj, innerObj]});
+};
+
+exports.stringify.oddKeys = function test() {
+    assertStringify({"this is a crazy long key":1, 'bbbb':2});
+    assertStringify({"":1, 'bbbb':2});
+    assertStringify({"s\ns":1, 'bbbb':2});
+    assertStringify({'\n\b\t\f\r\'\\':1, 'bbbb':2});
+    assertStringify({undefined:1, 'bbbb':2});
+    assertStringify({'\x00':'\x00'});
+};
+
+// we expect errors from all of these tests.  The errors should match
+exports.stringify.circular = function test() {
+    var obj = { };
+    obj.obj = obj;
+    assertStringify(obj, null, true);
+
+    var obj2 = {inner1: {inner2: {}}};
+    obj2.inner1.inner2.obj = obj2;
+    assertStringify(obj2, null, true);
+
+    var obj3 = {inner1: {inner2: []}};
+    obj3.inner1.inner2[0] = obj3;
+    assertStringify(obj3, null, true);
+};
+
+exports.stringify.replacerType = function test() {
+    var assertStringifyJSON5ThrowsExceptionForReplacer = function(replacer) {
+        assert.throws(
+            function() { JSON5.stringify(null, replacer); },
+            /Replacer must be a function or an array/
+        );
+    };
+    assertStringifyJSON5ThrowsExceptionForReplacer('string');
+    assertStringifyJSON5ThrowsExceptionForReplacer(123);
+    assertStringifyJSON5ThrowsExceptionForReplacer({});
+};
+
+exports.stringify.replacer = {};
+exports.stringify.replacer.function = {};
+
+exports.stringify.replacer.function.simple = function test() {
+    function replacerTestFactory(expectedValue) {
+        return function() {
+            var lastKey = null,
+                lastValue = null,
+                numCalls = 0,
+                replacerThis;
+            return {
+                replacer: function(key, value) {
+                    lastKey = key;
+                    lastValue = value;
+                    numCalls++;
+                    replacerThis = this;
+                    return value;
+                },
+                assert: function() {
+                    assert.equal(numCalls, 1, "Replacer should be called exactly once for " + expectedValue);
+                    assert.equal(lastKey, "");
+                    assert.deepEqual(replacerThis, {"":expectedValue});
+                    var expectedValueToJson = expectedValue;
+                    if (expectedValue && expectedValue['toJSON']) {
+                        expectedValueToJson = expectedValue.toJSON();
+                    }
+                    assert.equal(lastValue, expectedValueToJson);
+                }
+            }
+        }
+    }
+    for (var i=0; i<simpleCases.length; i++) {
+        assertStringify(simpleCases[i], replacerTestFactory(simpleCases[i]));
+    }
+};
+
+exports.stringify.replacer.function.complexObject = function test() {
+    var obj = {
+        "": "emptyPropertyName",
+        one: 'string',
+        two: 123,
+        three: ['array1', 'array2'],
+        four: {nested_one:'anotherString'},
+        five: new Date(),
+        six: Date.now(),
+        seven: null,
+        eight: true,
+        nine: false,
+        ten: [NaN, Infinity, -Infinity],
+        eleven: function() {}
+    };
+    var expectedKeys = [
+        '', // top level object
+        '', // First key
+        'one',
+        'two',
+        'three', 0, 1, // array keys
+        'four', 'nested_one', // nested object keys
+        'five',
+        'six',
+        'seven',
+        'eight',
+        'nine',
+        'ten', 0, 1, 2, // array keys
+        'eleven'
+    ];
+    var expectedHolders = [
+        {"": obj},
+        obj,
+        obj,
+        obj,
+        obj, obj.three, obj.three,
+        obj, obj.four,
+        obj,
+        obj,
+        obj,
+        obj,
+        obj,
+        obj, obj.ten, obj.ten, obj.ten,
+        obj
+    ];
+    var ReplacerTest = function() {
+        var seenKeys = [];
+        var seenHolders = [];
+        return {
+            replacer: function(key, value) {
+                seenKeys.push(key);
+                seenHolders.push(this);
+                if (typeof(value) == "object") {
+                    return value;
+                }
+                return 'replaced ' + (value ? value.toString() : '');
+            },
+            assert: function() {
+                assert.deepEqual(seenKeys, expectedKeys);
+                assert.deepEqual(seenHolders, expectedHolders);
+            }
+        }
+    };
+    assertStringify(obj, ReplacerTest);
+};
+
+exports.stringify.replacer.function.replacingWithUndefined = function test() {
+    var obj = { shouldSurvive: 'one', shouldBeRemoved: 'two' };
+    var ReplacerTest = function() {
+        return {
+            replacer: function(key, value) {
+                if (key === 'shouldBeRemoved') {
+                    return undefined;
+                } else {
+                    return value;
+                }
+            },
+            assert: function() { /* no-op */ }
+        }
+    };
+    assertStringify(obj, ReplacerTest);
+};
+
+exports.stringify.replacer.function.replacingArrayValueWithUndefined = function test() {
+    var obj = ['should survive', 'should be removed'];
+    var ReplacerTest = function() {
+        return {
+            replacer: function(key, value) {
+                if (value === 'should be removed') {
+                    return undefined;
+                } else {
+                    return value;
+                }
+            },
+            assert: function() { /* no-op */ }
+        }
+    };
+    assertStringify(obj, ReplacerTest);
+};
+
+exports.stringify.replacer.array = {};
+
+exports.stringify.replacer.array.simple = function test() {
+    var ReplacerTest = function() {
+        return {
+            replacer: [],
+            assert: function() { /* no-op */ }
+        }
+    };
+    for (var i=0; i<simpleCases.length; i++) {
+        assertStringify(simpleCases[i], ReplacerTest);
+    }
+};
+
+exports.stringify.replacer.array.emptyStringProperty = function test() {
+    var obj = {'': 'keep', 'one': 'remove'};
+    var ReplacerTest = function() {
+        return {
+            replacer: [''],
+            assert: function() {/* no-op */}
+        }
+    };
+    assertStringify(obj, ReplacerTest);
+};
+
+exports.stringify.replacer.array.complexObject = function test() {
+    var obj = {
+        "": "emptyPropertyName",
+        one: 'string',
+        one_remove: 'string',
+        two: 123,
+        two_remove: 123,
+        three: ['array1', 'array2'],
+        three_remove: ['array1', 'array2'],
+        four: {nested_one:'anotherString', nested_one_remove: 'anotherString'},
+        four_remove: {nested_one:'anotherString', nested_one_remove: 'anotherString'},
+        five: new Date(),
+        five_remove: new Date(),
+        six: Date.now(),
+        six_remove: Date.now(),
+        seven: null,
+        seven_remove: null,
+        eight: true,
+        eight_remove: true,
+        nine: false,
+        nine_remove: false,
+        ten: [NaN, Infinity, -Infinity],
+        ten_remove: [NaN, Infinity, -Infinity],
+        eleven: function() {},
+        eleven_remove: function() {}
+    };
+    var ReplacerTest = function() {
+        return {
+            replacer: [
+                'one', 'two', 'three', 'four', 'nested_one', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 0
+            ],
+            assert: function() {/* no-op */}
+        }
+    };
+    assertStringify(obj, ReplacerTest);
+};
+
+exports.stringify.toJSON = function test() {
+    var customToJSONObject = {
+        name: 'customToJSONObject',
+        toJSON: function() {
+            return 'custom-to-json-object-serialization';
+        }
+    };
+    assertStringify(customToJSONObject);
+
+    var customToJSONPrimitive = "Some string";
+    customToJSONPrimitive.toJSON = function() {
+        return 'custom-to-json-string-serialization';
+    };
+    assertStringify(customToJSONPrimitive);
+
+    var object = {
+        customToJSONObject: customToJSONObject
+    };
+    assertStringify(object);
+
+    // Returning an object with a toJSON function does *NOT* have that toJSON function called: it is omitted
+    var nested = {
+        name: 'nested',
+        toJSON: function() {
+            return customToJSONObject;
+        }
+    };
+    assertStringify(nested);
+
+    var count = 0;
+    function createObjectSerialisingTo(value) {
+        count++;
+        return {
+            name: 'obj-' + count,
+            toJSON: function() {
+                return value;
+            }
+        };
+    }
+    assertStringify(createObjectSerialisingTo(null));
+    assertStringify(createObjectSerialisingTo(undefined));
+    assertStringify(createObjectSerialisingTo([]));
+    assertStringify(createObjectSerialisingTo({}));
+    assertStringify(createObjectSerialisingTo(12345));
+    assertStringify(createObjectSerialisingTo(true));
+    assertStringify(createObjectSerialisingTo(new Date()));
+    assertStringify(createObjectSerialisingTo(function(){}));
+};
+
+function stringifyJSON5(obj, replacer, space) {
+    var start, res, end;
+    try {
+        start = new Date();
+        res = JSON5.stringify(obj, replacer, space);
+        end = new Date();
+    } catch (e) {
+        res = e.message;
+        end = new Date();
+    }
+    if (DEBUG) {
+        console.log('JSON5.stringify time: ' + (end-start));
+        console.log(res);
+    }
+    return res;
+}
+
+function stringifyJSON(obj, replacer, space) {
+    var start, res, end;
+    
+    try {
+        start = new Date();
+        res = JSON.stringify(obj, replacer, space);
+        end = new Date();
+    
+        // now remove all quotes from keys where appropriate
+        // first recursively find all key names
+        var keys = [];
+        function findKeys(key, innerObj) {
+            if (innerObj && innerObj.toJSON && typeof innerObj.toJSON === "function") {
+                innerObj = innerObj.toJSON();
+            }
+            if (replacer) {
+                if (typeof replacer === 'function') {
+                    innerObj = replacer(key, innerObj);
+                } else if (key !== '' && replacer.indexOf(key) < 0) {
+                    return;
+                }
+            }
+            if (JSON5.isWord(key) &&
+                typeof innerObj !== 'function' &&
+                typeof innerObj !== 'undefined') {
+                keys.push(key);
+            }
+            if (typeof innerObj === 'object') {
+                if (Array.isArray(innerObj)) {
+                    for (var i = 0; i < innerObj.length; i++) {
+                        findKeys(i, innerObj[i]);
+                    }
+                } else if (innerObj !== null) {
+                    for (var prop in innerObj) {
+                        if (innerObj.hasOwnProperty(prop)) {
+                            findKeys(prop, innerObj[prop]);
+                        }
+                    }
+                }
+            }
+        }
+        findKeys('', obj);
+
+        // now replace each key in the result
+        var last = 0;
+        for (var i = 0; i < keys.length; i++) {
+        
+            // not perfect since we can match on parts of the previous value that 
+            // matches the key, but we can design our test around that.
+            last = res.indexOf('"' + keys[i] + '"', last);
+            if (last === -1) {
+                // problem with test framework
+                console.log("Couldn't find: " + keys[i]);
+                throw new Error("Couldn't find: " + keys[i]);
+            }
+            res = res.substring(0, last) + 
+                res.substring(last+1, last + keys[i].length+1) + 
+                res.substring(last + keys[i].length + 2, res.length);
+            last += keys[i].length;
+        }
+    } catch (e) {
+        res = e.message;
+        end = new Date();
+    }
+    if (DEBUG) {
+        console.log('JSON.stringify time: ' + (end-start));
+    }
+    return res;
+}
+
+function assertStringify(obj, replacerTestConstructor, expectError) {
+    if (!replacerTestConstructor) {
+        replacerTestConstructor = function(){
+            return {replacer: null, assert: function(){}};
+        };
+    }
+    var testStringsEqual = function(obj, indent) {
+        var j5ReplacerTest = replacerTestConstructor();
+        var jReplacerTest = replacerTestConstructor();
+        var j5, j;
+        j5 = stringifyJSON5(obj, j5ReplacerTest.replacer, indent);
+        j = stringifyJSON(obj, jReplacerTest.replacer, indent);
+        assert.equal(j5, j);
+        j5ReplacerTest.assert();
+    };
+
+    var indents = [
+        undefined,
+        " ",
+        "          ",
+        "                    ",
+        "\t",
+        "this is an odd indent",
+        5,
+        20,
+        '\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'
+    ];
+    for (var i=0; i<indents.length; i++) {
+        testStringsEqual(obj, indents[i]);
+    }
+
+    if (!expectError) {
+        // no point in round tripping if there is an error
+        var origStr = JSON5.stringify(obj), roundTripStr;
+        if (origStr !== "undefined" && typeof origStr !== "undefined") {
+            try {
+                roundTripStr = JSON5.stringify(JSON5.parse(origStr));
+            } catch (e) {
+                console.log(e);
+                console.log(origStr);    
+                throw e;
+            }
+            assert.equal(origStr, roundTripStr);
+        }
+    }
+}
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-json5.git



More information about the Pkg-javascript-commits mailing list