Monday, November 08, 2010 at 12:04 AM.

system.verbs.builtins.json.compile

on compile (s, adrtable, flParseAtts=false) {
	<<Changes
		<<10/24/10; 10:10:40 PM by DW
			<<Handle backslashes in string constants to escape double-quote characters and itself.
		<<10/24/10; 7:50:53 PM by DW
			<<If we're passed a scalar, then instead of adrtable pointing to a table when we return, it points to a scalar. Handle empty lists.
		<<10/21/10; 7:14:33 AM by DW
			<<The equivalent of xml.compile for JSON. Turns some JSON text into the exact same structure that xml.compile creates, so all the techniques we use for dealing with XML should apply to JSON.
			<<Examples used to test are in workspace.userlandSamples.jsonTesting
				<<http://www.ietf.org/rfc/rfc4627.txt
				<<http://labs.adobe.com/technologies/spry/samples/data_region/JSONDataSetSample.html
				<<http://www.json.org/example.html
				<<http://api.hunch.com/api/v1/get-recommendations/?topic_ids=all_544&limit=10&auth_basic=1
			<<Optional param, flParseAtts. If true, we look for special syntax for XML attributes that are put there by (for example) xml.tableToJson. If a struct has an element named #value, then assume that the other elements are actually attributes.
				<<For example:
					<<"guid": {
						<<"isPermaLink": "false",
						<<"#value": "http://www.hello.com/article22.html"
						<<}
				<<Becomes
					<<<guid isPermaLink="false">http://www.hello.com/article22.html</guid>
	local (tyPunctuation, tyStringConst, tyBooleanValue, tyNullValue, tyNumberValue);
	local (flLookahead = false, lookaheadToken, lookaheadType);
	bundle { //set type constants
		tyPunctuation = "punctuation";
		tyStringConst = "stringConst";
		tyBooleanValue = "boolean";
		tyNullValue = "null";
		tyNumberValue = "number"};
	on isWhitespaceChar (ch) {
		if (ch != ' ') and (ch != '\t') and (ch != '\r') and (ch != '\n') {
			return (false)};
		return (true)};
	on isPunctuationChar (ch) {
		return ((ch == '[') or (ch == ']') or (ch == '{') or (ch == '}') or (ch == ',') or (ch == ':'))};
	on skipWhitespace () {
		local (ch);
		while sizeof (s) > 0 {
			ch = s [1];
			if (ch != ' ') and (ch != '\t') and (ch != '\r') and (ch != '\n') {
				return};
			s = string.delete (s, 1, 1)}};
	on popChar () {
		local (ch = s [1]);
		s = string.delete (s, 1, 1);
		return (ch)};
	on scan (adrtype) {
		if flLookahead {
			adrtype^ = lookaheadType;
			flLookahead = false;
			return (lookaheadToken)};
		local (ch);
		skipWhitespace ();
		ch = popchar ();
		if isPunctuationChar (ch) {
			adrtype^ = tyPunctuation;
			return (ch)};
		case ch {
			'"' {
				local (stringconst = "", chprev = ' ', fladd);
				loop {
					ch = popchar ();
					if (ch == '"') and (chprev != '\\') {
						adrtype^ = tyStringConst;
						return (stringconst)};
					fladd = true;
					if ch == '\\' {
						if chprev != '\\' {
							fladd = false}};
					if fladd {
						stringconst = stringconst + ch};
					chprev = ch}}}
		else {
			local (token = string (ch)); //it's got to be something like a number, true, false or null
			on coerceValue () {
				case string.lower (token) {
					"true" {
						adrtype^ = tyBooleanValue;
						token = true};
					"false" {
						adrtype^ = tyBooleanValue;
						token = false};
					"null" {
						adrtype^ = tyNullValue;
						token = nil}}
				else {
					if token contains '.' {
						token = double (token)}
					else {
						token = number (token)};
					adrtype^ = tyNumberValue}};
			loop {
				if isPunctuationChar (s [1]) { //lookahead
					coerceValue ();
					return (token)};
				ch = popchar ();
				if isWhitespaceChar (ch) {
					coerceValue ();
					return (token)};
				token = token + ch;
				if sizeof (s) == 0 {
					coerceValue ();
					return (token)}}}};
	on dovalue (name, adrtable) {
		local (type, token = scan (@type));
		case type {
			tyNumberValue;
			tyStringConst;
			tyBooleanValue {
				xml.addvalue (adrtable, name, token)};
			tyNullValue {
				xml.addvalue (adrtable, name, nil)}}
		else {
			if token == '[' {
				dolist (name, adrtable)}
			else {
				if token == '{' {
					dostruct (xml.addtable (adrtable, name))}
				else {
					if token == ']' { //empty list -- put back the closing token
						lookaheadType = type;
						lookaheadToken =  token;
						flLookahead = true;
						adrtable^.[name] = {}}}}}}; //an empty list
	on dolist (name, adrtable) {
		local (token, type);
		loop {
			dovalue (name, adrtable);
			token = scan (@type);
			if token == ']' {
				return};
			if token == ',' {
				continue}}};
	on dostruct (adrtable) {
		on fixStruct (adrstruct) {
			local (adr);
			for adr in adrstruct {
				if nameof (adr^) endswith "#value" {
					local (value = adr^, adrinstruct, tablecopy);
					new (tabletype, @tablecopy);
					for adrinstruct in adrstruct {
						tablecopy.[string.nthfield (nameof (adrinstruct^), "\t", 2)] = adrinstruct^};
					delete (@tablecopy.["#value"]);
					table.emptytable (adrstruct);
					adrstruct^.["/atts"] = tablecopy;
					adrstruct^.["/pcdata"] = value}}};
		local (name, type, token);
		loop {
			name = scan (@type);
			if name == '}' { //empty struct
				return};
			if type == tyStringConst {
				if scan (@type) == ':' {
					dovalue (name, adrtable)}};
			token = scan (@type);
			if token == '}' {
				if flParseAtts {
					fixstruct (adrtable)};
				return};
			if token == ',' {
				continue}}};
	new (tabletype, adrtable);
	while sizeof (s) > 0 {
		local (type, token);
		token = scan (@type);
		if token == '{' {
			dostruct (adrtable)}
		else {
			if token == '[' {
				dolist ("", adrtable)}
			else {
				flLookahead = true;
				lookaheadToken = token;
				lookaheadType = type;
				dovalue ("", adrtable);
				local (val = adrtable^ [1]);
				delete (adrtable);
				adrtable^ = val}};
		skipWhitespace ()}};
	<<bundle //test code
		<<local (type)
		<<new (outlinetype, @scratchpad.tokens); target.set (@scratchpad.tokens); edit (@scratchpad.tokens)
		<<while sizeof (s) > 0
			<<op.insert (scan (@type) + " (" + type + ")", down)
			<<skipWhitespace ()
<<bundle //test code
	<<local (tc = clock.ticks (), i)
	<<for i = 1 to 100
		<<compile (string (xml.examples.json.source.feedhose), @xml.examples.json.tables.feedhose)
	<<dialog.alert (clock.ticks () - tc)
bundle { //test code
	compile (string (scratchpad.foolishjack), @scratchpad.foolishshit)}
	<<compile (string (workspace.userlandSamples.jsonTesting.source.andrewTunnellJonesExample), @scratchpad.foolishshit)



This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.