New plugin: Json Parser

Discuss questions about plugin development with the Hollywood SDK here
midwan
Posts: 37
Joined: Sun Jun 19, 2016 1:15 pm

Re: New plugin: Json Parser

Post by midwan » Mon Jan 07, 2019 9:00 am

Hi Allanon!

I'm currently in the middle of a few other projects, so it will take me a while to come back to this one - but if you do have something that works, I'd love it if you could share. It doesn't matter if the code is "dirty" as you say, if it works it's always helpful as a starting point or a different perspective also. :)

midwan
Posts: 37
Joined: Sun Jun 19, 2016 1:15 pm

Re: New plugin: Json Parser

Post by midwan » Thu Feb 14, 2019 12:34 pm

Ping!

I'd like to revisit this soon, now that some of my other bigger projects are going out of the way.
@Allanon, if you have something you'd be willing to share, it would be great!

Otherwise, I will get started with the samples I found so far and post back any updates here.

BeChris
Posts: 3
Joined: Wed Apr 17, 2019 6:39 pm

Re: New plugin: Json Parser

Post by BeChris » Mon Apr 29, 2019 10:49 pm

Hello, I adapted an Lua json decoder from https://github.com/rxi/json.lua

Here it is:

Code: Select all

/*
 * Converted from json.lua code at https://github.com/rxi/json.lua
 * Conversion done by Christophe Gouiran (bechris13250@gmail.com)
 *
 * Original copyright:
 *
 * json.lua
 *
 * Copyright (c) 2019 rxi
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */


Function escape_char_map_inv(c)
	Local escape_char_map = {
		[ "\\/" ] = "/",
		[ "\\" ] = "\\\\",
		[ "\"" ] = "\\\"",
		[ "\b" ] = "\\b",
		[ "\f" ] = "\\f",
		[ "\n" ] = "\\n",
		[ "\r" ] = "\\r",
		[ "\t" ] = "\\t",
	}
	
	Return (RawGet(escape_char_map, c))
EndFunction
	

Function create_set(...)
	Local res = {}

	For Local i = 0 To arg.n - 1
		res[ arg[i] ] = True
	Next
	Return (res)
EndFunction

space_chars  = create_set(" ", "\t", "\r", "\n")
delim_chars  = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
escape_chars = create_set("\\", "/", "\"", "b", "f", "n", "r", "t", "u")
literals     = create_set("true", "false", "null")

literal_map = {
	[ "true"  ] = True,
	[ "false" ] = False,
	[ "null"  ] = "null",
}

Function next_char(str, idx, set, negate)
	
	For Local i = idx To StrLen(str) - 1
		
		Local t = MidStr(str, i, 1)
		
		If RawGet(set, t) <> negate
			Return (i)
		EndIf
	Next
	Return (StrLen(str) + 1)
EndFunction


Function decode_error(str, idx, msg)

	Local len = StrLen(str)

	Local line_count = 1
	Local col_count = 1
	For i = 0 To idx - 1
		If i >= len
			Break
		EndIf
		col_count = col_count + 1
		If ByteAsc(str, i) = 10
			line_count = line_count + 1
			col_count = 1
		EndIf
	Next
	Error( FormatStr("%s at line %d col %d", msg, line_count, col_count) )
EndFunction


Function codepoint_to_utf8(n)
	; http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
	Local f = Floor
	If n <= 0x7f
		Return Chr(n, #ENCODING_RAW)
	ElseIf n <= 0x7ff
		Return (Chr(f(n / 64) + 192, #ENCODING_RAW) .. Chr(n % 64 + 128, #ENCODING_RAW))
	ElseIf n <= 0xffff
		Return (Chr(f(n / 4096) + 224, #ENCODING_RAW) .. Chr(f(n % 4096 / 64) + 128, #ENCODING_RAW) .. Chr(n % 64 + 128, #ENCODING_RAW))
	ElseIf n <= 0x10ffff
		Return (Chr(f(n / 262144) + 240, #ENCODING_RAW) .. Chr(f(n % 262144 / 4096) + 128, #ENCODING_RAW) .. Chr(f(n % 4096 / 64) + 128, #ENCODING_RAW) .. Chr(n % 64 + 128, #ENCODING_RAW))
	EndIf
	Error( FormatStr("invalid unicode codepoint '%x'", n) )
EndFunction


Function parse_unicode_escape(s)
	
	Local n1 = ToNumber( MidStr(s, 2, 4), 16)
	Local n2 = ToNumber( MidStr(s, 8, 4), 16)

	; Surrogate pair?
	If n2
		Return (codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000))
	Else
		Return (codepoint_to_utf8(n1))
	EndIf

EndFunction


Function parse_string(str, i)
	
	Local has_unicode_escape = False
	Local has_surrogate_escape = False
	Local has_escape = False
	Local last
	For j = i + 1 To StrLen(str) - 1
		Local x = ByteAsc(str, j)

		If x < 32
			decode_error(str, j, "control character in string")
		EndIf

		If last = 92 ; "\\" (escape char)
			If x = 117 ; "u" (unicode escape sequence)
				Local hex = MidStr(str, j + 1, 4)
				If PatternFindStrDirect(hex, "%x%x%x%x") = -1
					decode_error(str, j, "invalid unicode escape in string")
				EndIf
				If PatternFindStrDirect(hex, "^[dD][89aAbB]") = 0
					has_surrogate_escape = True
				Else
					has_unicode_escape = True
				EndIf
			Else
				Local c = Chr(x)
				If Not escape_chars[c]
					decode_error(str, j, "invalid escape char '" .. c .. "' in string")
				EndIf
				has_escape = True
			EndIf
			last = Nil

		ElseIf x = 34 ; '"' (end of string)
			Local s = MidStr(str, i + 1, j - i - 1)
			If has_surrogate_escape
				s = PatternReplaceStr(s, "\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
			EndIf
			If has_unicode_escape
				s = PatternReplaceStr(s, "\\u....", parse_unicode_escape)
			EndIf
			If has_escape
				s = PatternReplaceStr(s, "\\.", escape_char_map_inv)
			EndIf
			Return (s, j + 1)

		Else
			last = x
		EndIf
	Next
	decode_error(str, i, "expected closing quote for string")
EndFunction


Function parse_number(str, i)

	Local x = next_char(str, i, delim_chars)
	Local s = MidStr(str, i, x - i)

	Local n = ToNumber(s)
	If IsNil(n)
		decode_error(str, i, "invalid number '" .. s .. "'")
	EndIf
	Return (n, x)
EndFunction


Function parse_literal(str, i)

	Local x = next_char(str, i, delim_chars)
	Local word = MidStr(str, i, x - i)
	If Not HaveItem(literals, word)
		decode_error(str, i, "invalid literal '" .. word .. "'")
	EndIf
	Return (literal_map[word], x)
EndFunction


Function parse_array(str, i)

	Local res = {}
	Local n = 1
	i = i + 1
	Repeat
		Local x
		i = next_char(str, i, space_chars, True)
		; Empty / End of array?
		If MidStr(str, i, 1) = "]"
			i = i + 1
			Break
		EndIf
		; Read token
		x, i = parse(str, i)
		res[n] = x
		n = n + 1
		; Next token
		i = next_char(str, i, space_chars, True)
		Local c = MidStr(str, i, 1)
		i = i + 1
		If c = "]" Then Break
		If c <> "," Then decode_error(str, i, "expected ']' or ','")
	Forever
	Return (res, i)
EndFunction


Function parse_object(str, i)
	
	Local res = {}
	i = i + 1

	Repeat
		Local key, value
		i = next_char(str, i, space_chars, True)

		; Empty / End of object?
		If MidStr(str, i, 1) = "}"
			i = i + 1
			Break
		EndIf
		; Read key
		If MidStr(str, i, 1) <> "\""
			decode_error(str, i, "expected string for key")
		EndIf
		key, i = parse(str, i)
		; Read ':' delimiter
		i = next_char(str, i, space_chars, True)
		If MidStr(str, i, 1) <> ":"
			decode_error(str, i, "expected ':' after key")
		EndIf
		i = next_char(str, i + 1, space_chars, True)
		; Read value
		value, i = parse(str, i)
		; Set
		res[key] = value
		; next token
		i = next_char(str, i, space_chars, True)
		Local c = MidStr(str, i, 1)
		i = i + 1
		If c = "}" Then Break
		If c <> "," Then decode_error(str, i, "expected '}' or ','")
	Forever
	Return (res, i)
EndFunction


Function parse(str, idx)
	
	Local char_func_map = {
		[ "\"" ] = parse_string,
		[ "0" ] = parse_number,
		[ "1" ] = parse_number,
		[ "2" ] = parse_number,
		[ "3" ] = parse_number,
		[ "4" ] = parse_number,
		[ "5" ] = parse_number,
		[ "6" ] = parse_number,
		[ "7" ] = parse_number,
		[ "8" ] = parse_number,
		[ "9" ] = parse_number,
		[ "-" ] = parse_number,
		[ "t" ] = parse_literal,
		[ "f" ] = parse_literal,
		[ "n" ] = parse_literal,
		[ "[" ] = parse_array,
		[ "{" ] = parse_object,
	}
	
	Local c = MidStr(str, idx, 1)
	
	If HaveItem(char_func_map, c)
		Local f = char_func_map[c]
	
		If f
			Return (f(str, idx))
		EndIf
	EndIf
	decode_error(str, idx, "unexpected character '" .. c .. "'")
	
EndFunction


Function decode(str)
	
	/* Check str parameter is a string */
	Local str_type = GetType(str)
	If str_type <> #STRING
		Local type_as_string = "unknown type"
		Switch str_type
			Case #NUMBER
				type_as_string = "number"
			Case #TABLE
				type_as_string = "table"
			Case #FUNCTION
				type_as_string = "function"
			Case #USERDATA
				type_as_string = "userdata"
			Case #LIGHTUSERDATA
				type_as_string = "lightuserdata"
			Case #THREAD
				type_as_string = "thread"
			Case #NIL
				type_as_string = "nil"
		EndSwitch
			
		Error("expected argument of type string, got " .. type_as_string)
	EndIf
	
	Local res, idx = parse(str, next_char(str, 0, space_chars, True))
	idx = next_char(str, idx, space_chars, True)
	If idx <= StrLen(str)
		decode_error(str, idx, "trailing garbage")
	EndIf
	Return (res)	
EndFunction

Function MyDebug(...)

	Local Function PrettyPrint(item, name, indent)

		DebugPrintNR(RepeatStr(" ", indent))

		If GetType(item) = #TABLE
			If StrLen(name) <> 0
				DebugPrintNR(name, " : ")
			EndIf
		Else
			If StrLen(name) = 0
				DebugPrintNR(item, " ")
			Else
				DebugPrintNR(name, " : ", item)
			EndIf	
		EndIf
	
		Switch GetType(item)
			Case #NUMBER
				DebugPrint("")
			Case #STRING
				DebugPrint("")
			Case #TABLE
				DebugPrint("{")

				For k,v In Pairs(item)
					PrettyPrint(v, k, indent + 2)
				Next
		 
				DebugPrintNR(RepeatStr(" ", indent) .. "}")
				
				If StrLen(name) <> 0
					DebugPrint(" // ", name)
				Else
					DebugPrint("")
				EndIf
				
			Case #FUNCTION
				DebugPrint(" (function)")
			Case #USERDATA
				DebugPrint(" (userdata)")
			Case #LIGHTUSERDATA
				DebugPrint(" (lightuserdata)")
			Case #THREAD
				DebugPrint(" (thread)")
			Case #NIL
				DebugPrint(" (nil)")
		EndSwitch
	
	
	EndFunction

	For k, v In IPairs(arg)
		PrettyPrint(v, "param" .. k+1, 0)
		DebugPrint("")
	Next
	
EndFunction

;MyDebug(decode(FileToString("test2.json")))

the decode fonction can be given a json structure as string and returns the corresponding Hollywood table.

MyDebug function was only necessary during debugging of this code. It can be safely removed.

User avatar
airsoftsoftwair
Posts: 3065
Joined: Fri Feb 12, 2010 2:33 pm
Location: Germany
Contact:

Re: New plugin: Json Parser

Post by airsoftsoftwair » Tue Apr 30, 2019 9:11 pm

Thanks for posting this!

p-OS
Posts: 104
Joined: Mon Nov 01, 2010 11:56 pm

Re: New plugin: Json Parser

Post by p-OS » Wed May 01, 2019 1:16 am

Thanx for the port, might be quite useful for reading data from public web services..

User avatar
Clyde
Posts: 164
Joined: Sun Feb 14, 2010 12:38 pm
Location: Dresden / Germany

Re: New plugin: Json Parser

Post by Clyde » Mon Jun 03, 2019 9:21 am

Thanks for the port as well, BeChris!

Not sure about other opinions, but I think you should update you code at one line. In the function "parse_array(str, i)" I would set Local n to 0 (zero) not 1. I know that in the original Lua code this is set to 1, but IMHO Hollywood uses zero-based indices (at least when watching some examples in the documentation) and IMHO it is kind of a standard in programming to use the zero-based approach.

So, I tried the json decode function and it works nicely. But I have one problem when accessing the properties of the object after I read the json. Let's assume I have this json definition in a json file:

Code: Select all

{
    "myProp": 1
}
After I read the file and decoded it I want to access the property like this in Hollywood:

Code: Select all

myTable.myProp
But this does not work (error message in Hollywood which I can't remember currently, sth like "myProp is not defined"). I just can access it like so:

Code: Select all

myTable["myProp"]
I don't know whether this is "a problem" with the json decode code that can be fixed or something I have to live with (I prefer the dot notation).

Thanks in advance!
Currently using: Hollywood 7.1 with Windows IDE

BeChris
Posts: 3
Joined: Wed Apr 17, 2019 6:39 pm

Re: New plugin: Json Parser

Post by BeChris » Fri Aug 02, 2019 11:43 pm

Clyde wrote:
Mon Jun 03, 2019 9:21 am
Thanks for the port as well, BeChris!

Not sure about other opinions, but I think you should update you code at one line. In the function "parse_array(str, i)" I would set Local n to 0 (zero) not 1. I know that in the original Lua code this is set to 1, but IMHO Hollywood uses zero-based indices (at least when watching some examples in the documentation) and IMHO it is kind of a standard in programming to use the zero-based approach.

So, I tried the json decode function and it works nicely. But I have one problem when accessing the properties of the object after I read the json. Let's assume I have this json definition in a json file:

Code: Select all

{
    "myProp": 1
}
After I read the file and decoded it I want to access the property like this in Hollywood:

Code: Select all

myTable.myProp
But this does not work (error message in Hollywood which I can't remember currently, sth like "myProp is not defined"). I just can access it like so:

Code: Select all

myTable["myProp"]
I don't know whether this is "a problem" with the json decode code that can be fixed or something I have to live with (I prefer the dot notation).

Thanks in advance!
Hello Clyde, you are right and I might have written Local n = 0
I'm going to update my original post.

Good catch !

For the second problem (access via dot notation) I will take a look at it later.

BeChris
Posts: 3
Joined: Wed Apr 17, 2019 6:39 pm

Re: New plugin: Json Parser

Post by BeChris » Fri Aug 02, 2019 11:47 pm

BeChris wrote:
Mon Apr 29, 2019 10:49 pm
Hello, I adapted an Lua json decoder from https://github.com/rxi/json.lua

Here it is:

Code: Select all

/*
 * Converted from json.lua code at https://github.com/rxi/json.lua
 * Conversion done by Christophe Gouiran (bechris13250@gmail.com)
 *
 * Original copyright:
 *
 * json.lua
 *
 * Copyright (c) 2019 rxi
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */


Function escape_char_map_inv(c)
	Local escape_char_map = {
		[ "\\/" ] = "/",
		[ "\\" ] = "\\\\",
		[ "\"" ] = "\\\"",
		[ "\b" ] = "\\b",
		[ "\f" ] = "\\f",
		[ "\n" ] = "\\n",
		[ "\r" ] = "\\r",
		[ "\t" ] = "\\t",
	}
	
	Return (RawGet(escape_char_map, c))
EndFunction
	

Function create_set(...)
	Local res = {}

	For Local i = 0 To arg.n - 1
		res[ arg[i] ] = True
	Next
	Return (res)
EndFunction

space_chars  = create_set(" ", "\t", "\r", "\n")
delim_chars  = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
escape_chars = create_set("\\", "/", "\"", "b", "f", "n", "r", "t", "u")
literals     = create_set("true", "false", "null")

literal_map = {
	[ "true"  ] = True,
	[ "false" ] = False,
	[ "null"  ] = "null",
}

Function next_char(str, idx, set, negate)
	
	For Local i = idx To StrLen(str) - 1
		
		Local t = MidStr(str, i, 1)
		
		If RawGet(set, t) <> negate
			Return (i)
		EndIf
	Next
	Return (StrLen(str) + 1)
EndFunction


Function decode_error(str, idx, msg)

	Local len = StrLen(str)

	Local line_count = 1
	Local col_count = 1
	For i = 0 To idx - 1
		If i >= len
			Break
		EndIf
		col_count = col_count + 1
		If ByteAsc(str, i) = 10
			line_count = line_count + 1
			col_count = 1
		EndIf
	Next
	Error( FormatStr("%s at line %d col %d", msg, line_count, col_count) )
EndFunction


Function codepoint_to_utf8(n)
	; http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
	Local f = Floor
	If n <= 0x7f
		Return Chr(n, #ENCODING_RAW)
	ElseIf n <= 0x7ff
		Return (Chr(f(n / 64) + 192, #ENCODING_RAW) .. Chr(n % 64 + 128, #ENCODING_RAW))
	ElseIf n <= 0xffff
		Return (Chr(f(n / 4096) + 224, #ENCODING_RAW) .. Chr(f(n % 4096 / 64) + 128, #ENCODING_RAW) .. Chr(n % 64 + 128, #ENCODING_RAW))
	ElseIf n <= 0x10ffff
		Return (Chr(f(n / 262144) + 240, #ENCODING_RAW) .. Chr(f(n % 262144 / 4096) + 128, #ENCODING_RAW) .. Chr(f(n % 4096 / 64) + 128, #ENCODING_RAW) .. Chr(n % 64 + 128, #ENCODING_RAW))
	EndIf
	Error( FormatStr("invalid unicode codepoint '%x'", n) )
EndFunction


Function parse_unicode_escape(s)
	
	Local n1 = ToNumber( MidStr(s, 2, 4), 16)
	Local n2 = ToNumber( MidStr(s, 8, 4), 16)

	; Surrogate pair?
	If n2
		Return (codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000))
	Else
		Return (codepoint_to_utf8(n1))
	EndIf

EndFunction


Function parse_string(str, i)
	
	Local has_unicode_escape = False
	Local has_surrogate_escape = False
	Local has_escape = False
	Local last
	For j = i + 1 To StrLen(str) - 1
		Local x = ByteAsc(str, j)

		If x < 32
			decode_error(str, j, "control character in string")
		EndIf

		If last = 92 ; "\\" (escape char)
			If x = 117 ; "u" (unicode escape sequence)
				Local hex = MidStr(str, j + 1, 4)
				If PatternFindStrDirect(hex, "%x%x%x%x") = -1
					decode_error(str, j, "invalid unicode escape in string")
				EndIf
				If PatternFindStrDirect(hex, "^[dD][89aAbB]") = 0
					has_surrogate_escape = True
				Else
					has_unicode_escape = True
				EndIf
			Else
				Local c = Chr(x)
				If Not escape_chars[c]
					decode_error(str, j, "invalid escape char '" .. c .. "' in string")
				EndIf
				has_escape = True
			EndIf
			last = Nil

		ElseIf x = 34 ; '"' (end of string)
			Local s = MidStr(str, i + 1, j - i - 1)
			If has_surrogate_escape
				s = PatternReplaceStr(s, "\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
			EndIf
			If has_unicode_escape
				s = PatternReplaceStr(s, "\\u....", parse_unicode_escape)
			EndIf
			If has_escape
				s = PatternReplaceStr(s, "\\.", escape_char_map_inv)
			EndIf
			Return (s, j + 1)

		Else
			last = x
		EndIf
	Next
	decode_error(str, i, "expected closing quote for string")
EndFunction


Function parse_number(str, i)

	Local x = next_char(str, i, delim_chars)
	Local s = MidStr(str, i, x - i)

	Local n = ToNumber(s)
	If IsNil(n)
		decode_error(str, i, "invalid number '" .. s .. "'")
	EndIf
	Return (n, x)
EndFunction


Function parse_literal(str, i)

	Local x = next_char(str, i, delim_chars)
	Local word = MidStr(str, i, x - i)
	If Not HaveItem(literals, word)
		decode_error(str, i, "invalid literal '" .. word .. "'")
	EndIf
	Return (literal_map[word], x)
EndFunction


Function parse_array(str, i)

	Local res = {}
	Local n = 0
	i = i + 1
	Repeat
		Local x
		i = next_char(str, i, space_chars, True)
		; Empty / End of array?
		If MidStr(str, i, 1) = "]"
			i = i + 1
			Break
		EndIf
		; Read token
		x, i = parse(str, i)
		res[n] = x
		n = n + 1
		; Next token
		i = next_char(str, i, space_chars, True)
		Local c = MidStr(str, i, 1)
		i = i + 1
		If c = "]" Then Break
		If c <> "," Then decode_error(str, i, "expected ']' or ','")
	Forever
	Return (res, i)
EndFunction


Function parse_object(str, i)
	
	Local res = {}
	i = i + 1

	Repeat
		Local key, value
		i = next_char(str, i, space_chars, True)

		; Empty / End of object?
		If MidStr(str, i, 1) = "}"
			i = i + 1
			Break
		EndIf
		; Read key
		If MidStr(str, i, 1) <> "\""
			decode_error(str, i, "expected string for key")
		EndIf
		key, i = parse(str, i)
		; Read ':' delimiter
		i = next_char(str, i, space_chars, True)
		If MidStr(str, i, 1) <> ":"
			decode_error(str, i, "expected ':' after key")
		EndIf
		i = next_char(str, i + 1, space_chars, True)
		; Read value
		value, i = parse(str, i)
		; Set
		res[key] = value
		; next token
		i = next_char(str, i, space_chars, True)
		Local c = MidStr(str, i, 1)
		i = i + 1
		If c = "}" Then Break
		If c <> "," Then decode_error(str, i, "expected '}' or ','")
	Forever
	Return (res, i)
EndFunction


Function parse(str, idx)
	
	Local char_func_map = {
		[ "\"" ] = parse_string,
		[ "0" ] = parse_number,
		[ "1" ] = parse_number,
		[ "2" ] = parse_number,
		[ "3" ] = parse_number,
		[ "4" ] = parse_number,
		[ "5" ] = parse_number,
		[ "6" ] = parse_number,
		[ "7" ] = parse_number,
		[ "8" ] = parse_number,
		[ "9" ] = parse_number,
		[ "-" ] = parse_number,
		[ "t" ] = parse_literal,
		[ "f" ] = parse_literal,
		[ "n" ] = parse_literal,
		[ "[" ] = parse_array,
		[ "{" ] = parse_object,
	}
	
	Local c = MidStr(str, idx, 1)
	
	If HaveItem(char_func_map, c)
		Local f = char_func_map[c]
	
		If f
			Return (f(str, idx))
		EndIf
	EndIf
	decode_error(str, idx, "unexpected character '" .. c .. "'")
	
EndFunction


Function decode(str)
	
	/* Check str parameter is a string */
	Local str_type = GetType(str)
	If str_type <> #STRING
		Local type_as_string = "unknown type"
		Switch str_type
			Case #NUMBER
				type_as_string = "number"
			Case #TABLE
				type_as_string = "table"
			Case #FUNCTION
				type_as_string = "function"
			Case #USERDATA
				type_as_string = "userdata"
			Case #LIGHTUSERDATA
				type_as_string = "lightuserdata"
			Case #THREAD
				type_as_string = "thread"
			Case #NIL
				type_as_string = "nil"
		EndSwitch
			
		Error("expected argument of type string, got " .. type_as_string)
	EndIf
	
	Local res, idx = parse(str, next_char(str, 0, space_chars, True))
	idx = next_char(str, idx, space_chars, True)
	If idx <= StrLen(str)
		decode_error(str, idx, "trailing garbage")
	EndIf
	Return (res)	
EndFunction

Function MyDebug(...)

	Local Function PrettyPrint(item, name, indent)

		DebugPrintNR(RepeatStr(" ", indent))

		If GetType(item) = #TABLE
			If StrLen(name) <> 0
				DebugPrintNR(name, " : ")
			EndIf
		Else
			If StrLen(name) = 0
				DebugPrintNR(item, " ")
			Else
				DebugPrintNR(name, " : ", item)
			EndIf	
		EndIf
	
		Switch GetType(item)
			Case #NUMBER
				DebugPrint("")
			Case #STRING
				DebugPrint("")
			Case #TABLE
				DebugPrint("{")

				For k,v In Pairs(item)
					PrettyPrint(v, k, indent + 2)
				Next
		 
				DebugPrintNR(RepeatStr(" ", indent) .. "}")
				
				If StrLen(name) <> 0
					DebugPrint(" // ", name)
				Else
					DebugPrint("")
				EndIf
				
			Case #FUNCTION
				DebugPrint(" (function)")
			Case #USERDATA
				DebugPrint(" (userdata)")
			Case #LIGHTUSERDATA
				DebugPrint(" (lightuserdata)")
			Case #THREAD
				DebugPrint(" (thread)")
			Case #NIL
				DebugPrint(" (nil)")
		EndSwitch
	
	
	EndFunction

	For k, v In IPairs(arg)
		PrettyPrint(v, "param" .. k+1, 0)
		DebugPrint("")
	Next
	
EndFunction

;MyDebug(decode(FileToString("test2.json")))

the decode fonction can be given a json structure as string and returns the corresponding Hollywood table.

MyDebug function was only necessary during debugging of this code. It can be safely removed.
parse_array fixed after bug detected by Clyde.

Post Reply