The underlying code for parsing objects is used as a built-in function

Propose new features and changes
arcticir
Posts: 693
Joined: 17 Nov 2013, 11:32

The underlying code for parsing objects is used as a built-in function

Post by arcticir » 16 Jun 2021, 04:14

Use the underlying code of the parsed object as a built-in function, i.e., serialize the string that matches the ahk rule.

Code: Select all

obj:={exe:a_ahkpath}, arr:=[3,2,1]
These are the most familiar and most used data structures, but they can only be parsed at startup, they can only be code, not data.

Why can't they be used as a data format?
For example obj:=parse("{exe:a_ahkpath}")


If this were implemented, it would be incredibly easy for us to read configuration files to objects.

For example, I need to dynamically call the function through the configuration file

Code: Select all

fnuc="PostMessage"
params=[0x112, 0xF060,0,,'%hwnd%']
This is something you can't easily achieve with any other task data format, whether it's TOML, JSON
These data formats do not allow to leave blank (i.e. the 4th parameter is blank)
If I use these formats, I have to write it like this

Code: Select all

params=[0x112, 0xF060,0,0,'%hwnd%']
params=[0x112, 0xF060,0,"",'%hwnd%']
But this triggers the Control parameter of "PostMessage" to take effect, which leads to an error.

I finally had to give up using "PostMessage" and use DllCall instead

Code: Select all

fnuc="DllCall"
params=["PostMessage", "Ptr","%hwnd%", "UInt", "274", "Ptr", "61536", "Ptr", "0"]

I don't know how everyone does a similar conversion process, but I need to go through the tedious process of reading and then replacing them one by one using the regular

edit="%A_ScriptDir%\default\SciTE\SciTE.exe"
to:
While RegExMatch(str, "iU)%[a-z]_[\w]{2,15}%" ,f)
str:=strReplace(str,f.value(),%f.value(1)%)


If there is parse()

edit="A_ScriptDir '\default\SciTE\SciTE.exe'"
parse(str)
hitman
Posts: 21
Joined: 10 Aug 2014, 06:47

Re: The underlying code for parsing objects is used as a built-in function

Post by hitman » 23 Jun 2021, 22:59

Code: Select all



ahk_obj:=
(LTrim Join
	{
		exe:a_ahkpath,
		arr:[3,2,1]
	}
)

str:=obj2str(ahk_obj)
MsgBox %str%

obj:=str2obj(str)

MsgBox % obj.exe
for k,v in obj.arr
	MsgBox % v


obj2str(_obj)  {
      if Isobject( _obj )  {
         isArray := true
         for key in _obj
            if !( key = A_Index || isArray := false )
               break
         for k, v in _obj
            str .= ( A_Index = 1 ? "" : ",`r`n" ) . ( isArray ? "" :obj2str(k) . ":" ) . obj2str(v)
         return isArray ? "[" str "]" : "`r`n{`r`n" str "`r`n}"
      }
      else if !(_obj*1 = "" || RegExMatch(_obj, "\s"))
         return _obj
      for k, v in [["\", "\\"], [A_Tab, "\t"], ["""", "\"""], ["/", "\/"], ["`n", "\n"], ["`r", "\r"], [Chr(12), "\f"], [Chr(08), "\b"]]
         _obj := StrReplace( _obj, v[1], v[2] )
      Return """" _obj """"
   }

str2obj(jsonStr){
	for k, v in [["\\", "\"]]
         jsonStr := StrReplace( jsonStr, v[1], v[2] )
	for k, v in [["\", "\\"]]
         jsonStr := StrReplace( jsonStr, v[1], v[2] )
	SC := ComObjCreate("ScriptControl") 
	SC.Language := "JScript"
	ComObjError(false)
	jsCode =
	(
	function arrangeForAhkTraversing(obj){
		if(obj instanceof Array){
			for(var i=0 ; i<obj.length ; ++i)
				obj[i] = arrangeForAhkTraversing(obj[i]) ;
			return ['array',obj] ;
		}else if(obj instanceof Object){
			var keys = [], values = [] ;
			for(var key in obj){
				keys.push(key) ;
				values.push(arrangeForAhkTraversing(obj[key])) ;
			}
			return ['object',[keys,values]] ;
		}else
			return [typeof obj,obj] ;
	}
	)
	SC.ExecuteStatement(jsCode "; obj=" jsonStr)
	return convertJScriptObjToAhks( SC.Eval("arrangeForAhkTraversing(obj)") )
}


convertJScriptObjToAhks(jsObj){
	if(jsObj[0]="object"){
		obj := {}, keys := jsObj[1][0], values := jsObj[1][1]
		loop % keys.length
			obj[keys[A_INDEX-1]] := convertJScriptObjToAhks( values[A_INDEX-1] )
		return obj
	}else if(jsObj[0]="array"){
		array := []
		loop % jsObj[1].length
			array.insert(convertJScriptObjToAhks( jsObj[1][A_INDEX-1] ))
		return array
	}else
		return jsObj[1]
}
Last edited by hitman on 14 Oct 2021, 20:48, edited 1 time in total.
User avatar
Delta Pythagorean
Posts: 626
Joined: 13 Feb 2017, 13:44
Location: Somewhere in the US
Contact:

Re: The underlying code for parsing objects is used as a built-in function

Post by Delta Pythagorean » 27 Jun 2021, 17:03

I guess JSON is useless in this context?

[AHK]......: v2.0.6 | 64-bit
[OS].......: Windows 11 | 22H2 (OS Build: 22621.1555)
[GITHUB]...: github.com/DelPyth
[PAYPAL]...: paypal.me/DelPyth
[DISCORD]..: tophatcat


You should consider migrating to v2. Practice with small features first such as learning how to do Guis!
Remember to use [code] ... [/code] for your multi-line scripts for the forums.
hitman
Posts: 21
Joined: 10 Aug 2014, 06:47

Re: The underlying code for parsing objects is used as a built-in function

Post by hitman » 15 Oct 2021, 08:08

https://github.com/cocobelgica/AutoHotkey-SerDes

Code: Select all

;#Include SerDes.ahk

ref := ["Hello"]
obj := [{"Hello":"Hello`nWorld"}, {(ref):"World"}, ref] ;// Object to serialize

;          1 2                         3 4
;// Shows '[ {"Hello":"Hello`nWorld"}, { ["Hello"]:"World"}, $4]'
MsgBox % out := SerDes(obj) ;// Serialize and display output

obj := SerDes(out)          ;// De-serialize
MsgBox % obj[1].Hello       ;// displays "Hello`nWorld" -> Escape sequence
MsgBox % obj[2][obj[3]]     ;// displays "World" -> Object reference at work
return

/* Function: SerDes
 *     Serialize an AHK object to string and optionally dumps it into a file.
 *     De-serialize a 'SerDes()' formatted string to an AHK object.
 * AHK Version: Requires v1.1+ OR v2.0-a049+
 * License: WTFPL (http://www.wtfpl.net/)
 *
 * Syntax (Serialize):
 *     str   := SerDes( obj [ ,, indent ] )
 *     bytes := SerDes( obj, outfile [ , indent ] )
 * Parameter(s):
 *     str       [retval]   - String representation of the object.
 *     bytes     [retval]   - Bytes written to 'outfile'.
 *     obj       [in]       - AHK object to serialize.
 *     outfile   [in, opt]  - The file to write to. If no absolute path is
 *                            specified, %A_WorkingDir% is used.
 *     indent    [in, opt]  - If indent is an integer or string, then array
 *                            elements and object members will be pretty-printed
 *                            with that indent level. Blank(""), the default, OR
 *                            0, selects the most compact representation. Using
 *                            an integer indent indents that many spaces per level.
 *                            If indent is a string, (such as "`t"), that string
 *                            is used to indent each level. Negative integer is
 *                            treated as positive.
 *
 * Syntax (Deserialize):
 *     obj := SerDes( src )
 * Parameter(s):
 *     obj       [retval]   - An AHK object.
 *     src       [in]       - Either a 'SerDes()' formatted string or the path
 *                            to the file containing 'SerDes()' formatted text.
 *                            If no absolute path is specified, %A_WorkingDir%
 *                            is used.
 * Remarks:
 *     Serialized output is similar to JSON except for escape sequences which
 *     follows AHK's specification. Also, strings, numbers and objects are
 *     allowed as 'object/{}' keys unlike JSON which restricts it to string
 *     data type only.
 *     Object references, including circular ones, are supported and notated
 *     as '$n', where 'n' is the 1-based index of the referenced object in the
 *     heirarchy tree when encountered during enumeration (for-loop) OR as it
 *     appears from left to right (for string representation) as marked by an
 *     opening brace or bracket. See diagram below:
 *     1    2
 *     {"a":["string"], "b":$2} -> '$2' references the object stored in 'a'
 */
SerDes(src, out:="", indent:="") {
	if IsObject(src) {
		ret := _SerDes(src, indent)
		if (out == "")
			return ret
		if !(f := FileOpen(out, "w"))
			throw "Failed to open file: '" out "' for writing."
		bytes := f.Write(ret), f.Close()
		return bytes ;// return bytes written when dumping to file
	}
	if FileExist(src) {
		if !(f := FileOpen(src, "r"))
			throw "Failed to open file: '" src "' for reading."
		src := f.Read(), f.Close()
	}
	;// Begin de-serialization routine
	static is_v2 := (A_AhkVersion >= "2"), q := Chr(34) ;// Double quote
	     , push  := Func(is_v2 ? "ObjPush"     : "ObjInsert")
	     , ins   := Func(is_v2 ? "ObjInsertAt" : "ObjInsert")
	     , set   := Func(is_v2 ? "ObjRawSet"   : "ObjInsert")
	     , pop   := Func(is_v2 ? "ObjPop"      : "ObjRemove")
	     , del   := Func(is_v2 ? "ObjRemoveAt" : "ObjRemove")
	static esc_seq := { ;// AHK escape sequences
	(Join Q C
		"``": "``",  ;// accent
		(q):  q,     ;// double quote
		"n":  "`n",  ;// newline
		"r":  "`r",  ;// carriage return
		"b":  "`b",  ;// backspace
		"t":  "`t",  ;// tab
		"v":  "`v",  ;// vertical tab
		"a":  "`a",  ;// alert (bell)
		"f":  "`f"   ;// formfeed
	)}
	;// Extract string literals
	strings := [], i := 0, end := 0-is_v2 ;// v1.1=0, v2.0-a=-1 -> SubStr()
	while (i := InStr(src, q,, i+1)) {
		j := i
		while (j := InStr(src, q,, j+1))
			if (SubStr(str := SubStr(src, i+1, j-i-1), end) != "``")
				break
		if !j
			throw "Missing close quote(s)."
		src := SubStr(src, 1, i) . SubStr(src, j+1)
		k := 0
		while (k := InStr(str, "``",, k+1)) {
			if InStr(q "``nrbtvaf", ch := SubStr(str, k+1, 1))
				str := SubStr(str, 1, k-1) . esc_seq[ch] . SubStr(str, k+2)
			else throw "Invalid escape sequence: '``" . ch . "'" 
		}
		%push%(strings, str) ;// strings.Insert(str) / strings.Push(str)
	}
	;// Begin recursive descent to parse markup
	pos := 0
	, is_key := false ;// if true, active data is to be used as associative array key
	, refs := [], kobj := [] ;// refs=object references, kobj=objects as keys
	, stack := [tree := []]
	, is_arr := Object(tree, 1)
	, next := q "{[01234567890-" ;// chars considered valid when encountered
	while ((ch := SubStr(src, ++pos, 1)) != "") {
		if InStr(" `t`n`r", ch)
			continue
		if !InStr(next, ch) ;// validate current char
			throw "Unexpected char: '" ch "'"
		is_array := is_arr[_obj := stack[1]] ;// active container object
		;// Associative/Linear array opening
		if InStr("{[", ch) {
			val := {}, is_arr[val] := ch == "[", %push%(refs, &val)
			if is_key
				%ins%(kobj, 1, val), key := val
			is_array? %push%(_obj, val) : %set%(_obj, key, is_key ? 0 : val)
			, %ins%(stack, 1, val), is_key := ch == "{"
			, next := q "{[0123456789-$" (is_key ? "}" : "]") ;// Chr(NumGet(ch, "Char")+2)
		}
		;// Associative/Linear array closing
		else if InStr("}]", ch) {
			next := is_arr[stack[2]] ? "]," : "},"
			if (kobj[1] == %del%(stack, 1))
				key := %del%(kobj, 1), next := ":"
		}
		;// Token
		else if InStr(",:", ch) {
			if (_obj == tree)
				throw "Unexpected char: '" ch "' -> there is no container object."
			next := q "{[0123456789-$", is_key := (!is_array && ch == ",")
		}
		;// String | Number | Object reference
		else {
			if (ch == q) { ;// string
				val := %del%(strings, 1)
			} else { ;// number / object reference
				if (is_ref := (ch == "$")) ;// object reference token
					pos += 1
				val := SubStr(src, pos, (SubStr(src, pos) ~= "[\]\}:,\s]|$")-1)
				if (Abs(val) == "")
					throw "Invalid number: " val
				pos += StrLen(val)-1, val += 0
				if is_ref {
					if !ObjHasKey(refs, val)
						throw "Invalid object reference: $" val
					val := Object(refs[val]), is_ref := false
				}
			}
			if is_key
				key := val, next := ":"
			else
				is_array? %push%(_obj, val) : %set%(_obj, key, val)
				, next := is_array ? "]," : "},"
		}
	}
	return tree[1]
}
;// Helper function, serialize object to string -> internal use only
_SerDes(obj, indent:="", lvl:=1, refs:=false) { ;// lvl,refs=internal parameters
	static q := Chr(34) ;// Double quote, for v1.1 & v2.0-a compatibility
	
	if IsObject(obj) {
		/* In v2, an exception is thrown when using ObjGetCapacity() on a
		 * non-standard AHK object (e.g. COM, Func, RegExMatch, File)
		 */
		if (ObjGetCapacity(obj) == "")
			throw "SerDes(): Only standard AHK objects are supported." ; v1.1
		if !refs
			refs := {}
		if ObjHasKey(refs, obj) ;// Object references, includes circular
			return "$" refs[obj] ;// return notation = $(index_of_object)
		refs[obj] := NumGet(&refs + 4*A_PtrSize)+1

		for k in obj
			is_array := k == A_Index
		until !is_array

		if (Abs(indent) != "") {
			spaces := Abs(indent), indent := ""
			Loop % spaces
				indent .= " "
		}
		indt := ""
		Loop % indent ? lvl : 0
			indt .= indent

		lvl += 1, out := "" ;// , len := NumGet(&obj+4*A_PtrSize) -> unreliable
		for k, v in obj {
			if !is_array
				out .= _SerDes(k,,, refs) . ( indent ? ": " : ":" ) ;// object(s) used as keys are not indented
			out .= _SerDes(v, indent, lvl, refs) . ( indent ? ",`n" . indt : "," )
		}
		if (out != "") {
			out := Trim(out, ",`n" . indent)
			if (indent != "")
				out := "`n" . indt . out . "`n" . SubStr(indt, 1, -StrLen(indent)) ;// trim 1 level of indentation
		}
		return is_array ? "[" out "]" : "{" out "}"
	}
	
	else if (ObjGetCapacity([obj], 1) == "")
		return obj
	
	static esc_seq := { ;// AHK escape sequences
	(Join Q C
		(q):  "``" . q,  ;// double quote
		"`n": "``n",     ;// newline
		"`r": "``r",     ;// carriage return
		"`b": "``b",     ;// backspace
		"`t": "``t",     ;// tab
		"`v": "``v",     ;// vertical tab
		"`a": "``a",     ;// alert (bell)
		"`f": "``f"      ;// formfeed
	)}
	i := -1
	while (i := InStr(obj, "``",, i+2))
		obj := SubStr(obj, 1, i-1) . "````" . SubStr(obj, i+1)
	for k, v in esc_seq {
		/* StringReplace/StrReplace workaround routine for v1.1 and v2.0-a
		 * compatibility. TODO: Compare w/ RegExReplace(), use RegExReplace()??
		 */
		i := -1
		while (i := InStr(obj, k,, i+2))
			obj := SubStr(obj, 1, i-1) . v . SubStr(obj, i+1)
	}
	return q . obj . q
}
lexikos
Posts: 9494
Joined: 30 Sep 2013, 04:07
Contact:

Re: The underlying code for parsing objects is used as a built-in function

Post by lexikos » 17 Oct 2021, 02:32

The program does not parse the "data structure" at startup. It parses an expression, which produces a data structure when evaluated. The expression itself is stored in persistent memory, and is never freed while the script is running. The code for parsing the expression is entirely unsuitable for parsing arbitrary JSON data.
Post Reply

Return to “Wish List”