Skip to content

Instantly share code, notes, and snippets.

@bmeck
Created February 10, 2014 05:29
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bmeck/8910816 to your computer and use it in GitHub Desktop.
Save bmeck/8910816 to your computer and use it in GitHub Desktop.
Example of how to use yield* to combine generators
// Our incremental JSON parser (simplified only to use strings and arrays)
// while iterator result != done keep using .next(moreSrc) or .next(null) if you are done providing data
// while the iterator is not done it will return the state of the iterator
// the iterator is seeking more data to get a valid value
// call .next(str) to provide it more data
var NEED_MORE_DATA = 0;
// the iterator has a valid result and will return a result if you provide .next(null)
// this is not automatic, due to numbers and potentially invalid trailing characters after an expected end
var HAVE_RESULT = 1;
function* parseJSON(src) {
return (yield* parseJSONValue(src, false)).result;
}
function removeStartingSpaces(src) {
return src.replace(/^\s+/, '');
}
function checkForUnexpectedEnd(src) {
if (src == null) {
throw new Error('unexpected end of source');
}
return src;
}
function needToCheckTrailingSource(src) {
if (src == null) {
return false;
}
var invalidCharacter = /\S/.exec(src);
if (invalidCharacter) {
throw new Error('Found an invalid character after string ' + invalidCharacter[0]);
}
}
// our "internal" method, used to grab all of the types of data
function* parseJSONValue(src, stop_on_result) {
while (src != null) {
src = removeStartingSpaces(src);
if (src.length) switch (src[0]) {
case '"':
var value = yield* parseJSONString(src, stop_on_result);
return value;
case '[':
var value = yield* parseJSONArray(src, stop_on_result);
return value;
default:
throw new Error('Unexpected character while expecting value');
}
src = yield checkForUnexpectedEnd(src);
}
checkForUnexpectedEnd(src);
}
function* parseJSONString(src, stop_on_result) {
// skip starting spaces
while (!src.length) {
src = removeStartingSpaces(checkForUnexpectedEnd(yield NEED_MORE_DATA));
}
if (src[0] !== '"') {
throw new Error('Unexpected character while expecting string ' + src[i]);
}
var result = '';
var escaped = false;
src = src.slice(1);
parse_loop:
while (true) {
for (var i = 0; i < src.length; i++) {
if (escaped) {
// ideally we would add some checks here, unicode, but this is just an example
result += src[i];
}
else if (src[i] == '\\') {
escaped = true;
}
else if (src[i] == '"') {
src = src.slice(i+1);
break parse_loop;
}
else {
result += src[i];
}
}
src = checkForUnexpectedEnd(yield NEED_MORE_DATA);
}
if (!stop_on_result) {
while (needToCheckTrailingSource(yield HAVE_RESULT));
}
return { result: result, unused: src };
}
function* parseJSONArray(src, stop_on_result) {
// skip starting spaces
while (!src.length) {
src = removeStartingSpaces(checkForUnexpectedEnd(yield NEED_MORE_DATA));
}
if (src[0] !== '[') {
throw new Error('Unexpected character while expecting array ' + src[i]);
}
var result = [];
// remove opening bracket and spaces
src = removeStartingSpaces(src.slice(1));
// skip spaces
while (!src.length) {
src = removeStartingSpaces(checkForUnexpectedEnd(yield NEED_MORE_DATA));
}
if (src[0] != ']') while (true) {
// we want to grab a value from our iteration, but halt on getting a value
var values = yield* parseJSONValue(src, true);
result.push(values.result);
// keep processing data if we have stuff left over
src = values.unused;
// request more data, just skip starting spaces
while (!src.length) {
src = removeStartingSpaces(checkForUnexpectedEnd(yield NEED_MORE_DATA).replace(/^\s+/, ''));
}
if (src[0] == ']') {
break;
}
if (src[0] != ',') throw new Error('Found an invalid character while expecting end of array or comma');
// remove comma
src = src.slice(1);
}
// remove trailing backet
src = src.slice(1);
if (!stop_on_result) {
while (needToCheckTrailingSource(yield HAVE_RESULT));
}
return { result: result, unused: src };
}
g=parseJSON('[[""],"test"]');
g.next();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment