; ------------------------------------------------------------------------
; HeavyThing x86_64 assembly language library and showcase programs
; Copyright © 2015-2018 2 Ton Digital
; Homepage: https://2ton.com.au/
; Author: Jeff Marrison <jeff@2ton.com.au>
;
; This file is part of the HeavyThing library.
;
; HeavyThing is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License, or
; (at your option) any later version.
;
; HeavyThing is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License along
; with the HeavyThing library. If not, see <http://www.gnu.org/licenses/>.
; ------------------------------------------------------------------------
;
; json.inc: json "object" support...
; a "general purpose" string-based json object container of sorts
; that supports the required JSON type vars
;
; this is intentionally messy, but gives decent versatility elsewhere in use-case scenarios
;
json_value = 0
json_array = 1
json_object = 2
json_name_ofs = 0
json_type_ofs = 8 ; dd, one of the above contains (json_value, json_array, json_object)
json_value_ofs = 16 ; these two share space in the object, but differ in use, so two separate names for them
json_contents_ofs = 16
json_size = 24
if used json$newvalue | defined include_everything
; two arguments: rdi == string name, rsi == string value
; returns new json object in rax, does not assume ownership of either argument
falign
json$newvalue:
prolog json$newvalue
sub rsp, 16
mov [rsp], rdi
mov [rsp+8], rsi
call string$copy
mov [rsp], rax
mov rdi, [rsp+8]
call string$copy
mov [rsp+8], rax
mov edi, json_size
call heap$alloc
mov rdi, [rsp]
mov rsi, [rsp+8]
mov [rax+json_name_ofs], rdi
mov qword [rax+json_type_ofs], json_value
mov [rax+json_value_ofs], rsi
add rsp, 16
epilog
end if
if used json$newvalue_nocopy | defined include_everything
; two arguments: rdi == string name, rsi == string value
; returns new json object in rax, ASSUMES OWNERSHIP OF BOTH name and value arguments
falign
json$newvalue_nocopy:
prolog json$newvalue_nocopy
sub rsp, 16
mov [rsp], rdi
mov [rsp+8], rsi
mov edi, json_size
call heap$alloc
mov rdi, [rsp]
mov rsi, [rsp+8]
mov [rax+json_name_ofs], rdi
mov qword [rax+json_type_ofs], json_value
mov [rax+json_value_ofs], rsi
add rsp, 16
epilog
end if
if used json$newarray | defined include_everything
; single argument: rdi == string name
; returns new json object in rax, does not assume ownership of the name (copies it)
falign
json$newarray:
prolog json$newarray
push rdi
call string$copy
mov [rsp], rax
mov edi, json_size
call heap$alloc
mov rdi, [rsp]
mov [rax+json_name_ofs], rdi
mov qword [rax+json_type_ofs], json_array
mov [rsp], rax
call list$new
mov rdi, [rsp]
mov [rdi+json_contents_ofs], rax
pop rax
epilog
end if
if used json$newarray_nocopy | defined include_everything
; single argument: rdi == string name
; returns new json object in rax, ASSUMES OWNERSHIP of the name
falign
json$newarray_nocopy:
prolog json$newarray_nocopy
push rdi
mov edi, json_size
call heap$alloc
mov rdx, [rsp]
mov [rax+json_name_ofs], rdx
mov qword [rax+json_type_ofs], json_array
mov [rsp], rax
call list$new
mov rdi, [rsp]
mov [rdi+json_contents_ofs], rax
pop rax
epilog
end if
if used json$newobject | defined include_everything
; single argument: rdi == string name
; returns new json object in rax, does not assume ownership of the name (copies it)
falign
json$newobject:
prolog json$newobject
push rdi
call string$copy
mov [rsp], rax
mov edi, json_size
call heap$alloc
mov rdi, [rsp]
mov [rax+json_name_ofs], rdi
mov qword [rax+json_type_ofs], json_object
mov [rsp], rax
mov edi, 1
call stringmap$new
mov rdi, [rsp]
mov [rdi+json_contents_ofs], rax
pop rax
epilog
end if
if used json$newobject_nocopy | defined include_everything
; single argument: rdi == string name
; returns new json object in rax, ASSUMES OWNERSHIP of the name
falign
json$newobject_nocopy:
prolog json$newobject_nocopy
push rdi
mov edi, json_size
call heap$alloc
mov rdx, [rsp]
mov [rax+json_name_ofs], rdx
mov qword [rax+json_type_ofs], json_object
mov [rsp], rax
mov edi, 1
call stringmap$new
mov rdi, [rsp]
mov [rdi+json_contents_ofs], rax
pop rax
epilog
end if
if used json$copy | defined include_everything
; single argument: rdi == json object
; returns a newly copied one in rax (deep copies everything if any)
falign
json$copy:
prolog json$copy
sub rsp, 24
mov [rsp], rdi
mov rdi, [rdi+json_name_ofs]
call string$copy
mov rdi, [rsp]
mov [rsp+8], rax
cmp dword [rdi+json_type_ofs], json_object
je .isobject
cmp dword [rdi+json_type_ofs], json_array
je .isarray
; else, it is a value
mov rdi, [rdi+json_value_ofs]
call string$copy
mov [rsp+16], rax
mov edi, json_size
call heap$alloc
mov rdi, [rsp+8]
mov rsi, [rsp+16]
mov [rax+json_name_ofs], rdi
mov qword [rax+json_type_ofs], json_value
mov [rax+json_value_ofs], rsi
add rsp, 24
epilog
calign
.isobject:
call stringmap$new
mov rcx, [rsp] ; get back the original object we are copying
mov [rsp+16], rax
mov rdx, rax ; our argument for foreach_arg
mov rdi, [rcx+json_contents_ofs]
mov rsi, .objectcopy
call stringmap$foreach_arg
mov edi, json_size
call heap$alloc
mov rdi, [rsp+8]
mov rsi, [rsp+16]
mov [rax+json_name_ofs], rdi
mov qword [rax+json_type_ofs], json_object
mov [rax+json_contents_ofs], rsi
add rsp, 24
epilog
; this is called for every key/value in the contents stringmap, along with rdx as the destination map
calign
.objectcopy:
sub rsp, 24
mov [rsp], rdi
mov [rsp+8], rsi
mov [rsp+16], rdx
call string$copy
mov [rsp], rax
mov rdi, [rsp+8]
call json$copy
mov rdi, [rsp+16]
mov rsi, [rsp] ; key
mov rdx, rax ; value
call stringmap$insert
add rsp, 24
ret
calign
.isarray:
call list$new
mov rcx, [rsp] ; get back the original object we are copying
mov [rsp+16], rax
mov rdx, rax ; our argument for foreach_arg
mov rdi, [rcx+json_contents_ofs]
mov rsi, .arraycopy
call list$foreach_arg
mov edi, json_size
call heap$alloc
mov rdi, [rsp+8]
mov rsi, [rsp+16]
mov [rax+json_name_ofs], rdi
mov qword [rax+json_type_ofs], json_array
mov [rax+json_contents_ofs], rsi
add rsp, 24
epilog
; this is called for every item in the contents list/array, along with rsi as the destination new list
calign
.arraycopy:
push rsi
call json$copy
pop rdi
mov rsi, rax
call list$push_back
ret
end if
json_name_ofs = 0
json_type_ofs = 8 ; dd, one of the above contains (json_value, json_array, json_object)
json_value_ofs = 16 ; these two share space in the object, but differ in use, so two separate names for them
json_contents_ofs = 16
if used json$destroy | defined include_everything
; single argument: rdi == json object
; this cleans everything up depending on type, and heap$free's rdi
falign
json$destroy:
prolog json$destroy
push rdi
mov rdi, [rdi+json_name_ofs]
call heap$free
mov rdi, [rsp]
mov edx, dword [rdi+json_type_ofs]
mov ecx, json_object
mov eax, json_array
cmp edx, ecx
je .isobject
cmp edx, eax
je .isarray
; else, it is a value
mov rdi, [rdi+json_value_ofs]
call heap$free
pop rdi
call heap$free
epilog
calign
.isobject:
mov rdi, [rdi+json_contents_ofs]
push rdi
mov rsi, .objectcleaner
call stringmap$clear
pop rdi
call heap$free
pop rdi
call heap$free
epilog
; this gets called for every key/value in the object's contents map, free the string, and json$destroy the value
calign
.objectcleaner:
push rdi
mov rdi, rsi
call json$destroy
pop rdi
call heap$free
ret
calign
.isarray:
mov rdi, [rdi+json_contents_ofs]
push rdi
mov rsi, json$destroy
call list$clear
pop rdi
call heap$free
pop rdi
call heap$free
epilog
end if
if used json$appendchild | defined include_everything
; two arguments: rdi == json object, rsi == child object to add to this one
; NOTE: this only makes sense (obviously) for json_array and json_object types, and will _do nothing_ if type is json_value
; ALSO NOTE: this does not make a _new copy_ of the child passed in rsi, tosses it straight in (and thus assumes ownership)
falign
json$appendchild:
prolog json$appendchild
cmp dword [rdi+json_type_ofs], json_value
je .pebcak
cmp dword [rdi+json_type_ofs], json_object
je .isobject
; else, it is an array
mov rdi, [rdi+json_contents_ofs] ; load up the contents list
call list$push_back
epilog
calign
.isobject:
; we need a copy of the child object's name to use as a key for our stringmap
push rdi rsi
mov rdi, [rsi+json_name_ofs]
call string$copy
pop rsi rdi
mov rdi, [rdi+json_contents_ofs] ; load up the contents stringmap
mov rdx, rsi ; value
mov rsi, rax ; key
call stringmap$insert
epilog
calign
.pebcak:
epilog
end if
if used json$tostring | defined include_everything
; single argument: rdi == json object
; returns new string representation in JSON form/encoding in rax
json$tostring:
prolog json$tostring
sub rsp, 16
mov [rsp], rdi
call buffer$new
mov [rsp+8], rax
mov rdi, [rsp]
mov rsi, rax
call .appendtobuffer
; so now, this object and all its descendents (if any) were converted to UTF32 in our buffer
; now we need to construct a string from it
mov rdx, [rsp+8] ; our buffer
mov rdi, [rdx+buffer_itself_ofs]
mov rsi, [rdx+buffer_length_ofs]
call string$from_utf32
mov [rsp], rax
mov rdi, [rsp+8]
call buffer$destroy
mov rax, [rsp]
add rsp, 16
epilog
; so this is re-entrantly called for every object... rdi == json object, rsi == destination buffer
calign
.appendtobuffer:
; TODO: none of my stuff uses extended UTF16 as name characters, this won't encode properly if the UTF16 is enabled, and hte name itself contains >0x10000 codes
push rsi rdi
mov rdx, [rdi+json_name_ofs]
cmp qword [rdx], 0
je .noname
mov rdi, rsi ; destination buffer
mov rsi, [rdx] ; string length in characters
add rsi, 3 ; space for leading/trailing quotes and a colon
shl rsi, 2 ; in utf32
call buffer$reserve
mov rdi, [rsp] ; our object
mov rsi, [rsp+8] ; destination buffer
mov rdx, [rsi+buffer_endptr_ofs] ; endptr to the buffer
mov rcx, [rdi+json_name_ofs]
mov dword [rdx], '"'
if string_bits = 32
mov rdi, rdx
add rdi, 4 ;
mov rsi, rcx
add rsi, 8
mov rdx, [rcx]
shl rdx, 2
call memcpy
else
mov rsi, rdx
add rsi, 4
mov rdi, rcx
call string$to_utf32
end if
; add our close quote and colon
mov rdi, [rsp] ; our object
mov rsi, [rsp+8] ; destination buffer
mov rdx, [rsi+buffer_endptr_ofs] ; endptr to the buffer
mov rcx, [rdi+json_name_ofs]
add rdx, 4
mov r8, [rcx]
shl r8, 2
add rdx, r8
add r8, 12
mov dword [rdx], '"'
mov dword [rdx+4], ':'
; bytes to add is in r8 nice and neatlike
add [rsi+buffer_endptr_ofs], r8
add [rsi+buffer_length_ofs], r8
calign
.noname:
; either there was no name, or we already added it quoted with a trailing colon
cmp dword [rdi+json_type_ofs], json_object
je .isobject
cmp dword [rdi+json_type_ofs], json_array
je .isarray
; else, it is a value
; because we don't actually do separate types for json values (null, boolean, etc)
; we do a bit of cheating here as well for wellknown types... NOTE: depending on the JS enviro
; it is possible to get a normal javascript environment to also incorrectly do these... and it has never caused me any problems
; since all of mine still end up the same regardless of their "origin/base" type...
; IN OTHER WORDS: null, true, false get placed in here unquoted, similar to numeric-only ones
mov rsi, [rdi+json_value_ofs]
mov rdi, .str_null
call string$equals
test eax, eax
jnz .value_unquoted
mov rdx, [rsp] ; our object
mov rsi, [rdx+json_value_ofs]
mov rdi, .str_true
call string$equals
test eax, eax
jnz .value_unquoted
mov rdx, [rsp] ; our object
mov rsi, [rdx+json_value_ofs]
mov rdi, .str_false
call string$equals
test eax, eax
jnz .value_unquoted
; next up, see if it is a number
mov rdx, [rsp]
mov rdi, [rdx+json_value_ofs]
; for bigint strings, string$isnumber returns true when we really still want it quoted
cmp qword [rdi], 20
jae @f
call string$isnumber
test eax, eax
jnz .value_unquoted
@@:
; otherwise, quote and encode the value
mov rdx, [rsp] ; our object
mov rdi, [rsp+8] ; our destination buffer
mov rcx, [rdx+json_value_ofs]
mov rsi, [rcx]
shl rsi, 4 ; *16 to be absolutely certain there is enough room in our buffer (x4 for chars, x4 again for UTF32)
add rsi, 16 ; for quotes on either end + 2 reserve chars
call buffer$reserve
mov rdi, [rsp] ; our object
mov rsi, [rsp+8] ; our destination buffer
mov rdx, [rsi+buffer_endptr_ofs]
mov rcx, [rdi+json_value_ofs]
mov r8, [rcx] ; length of the string in characters
add rcx, 8 ; rcx now pointing to the start of the actual string contents
mov dword [rdx], '"'
mov r9d, 1 ; how many characters we have written
add rdx, 4
calign
.quotedloop:
test r8, r8
jz .endquotedloop
if string_bits = 32
mov eax, dword [rcx]
add rcx, 4
sub r8, 1
else
movzx eax, word [rcx]
add rcx, 2
sub r8, 1
end if
cmp eax, 9
je .quotedtab
cmp eax, 13
je .quotedcr
cmp eax, 10
je .quotedlf
cmp eax, '"'
je .quotedquote
cmp eax, '\'
je .quotedbackslash
cmp eax, 32
jb .quotedhexval
mov dword [rdx], eax
add r9, 1
add rdx, 4
jmp .quotedloop
cleartext .str_null, 'null'
cleartext .str_true, 'true'
cleartext .str_false, 'false'
calign
.value_unquoted:
mov rdx, [rsp] ; our object
mov rdi, [rsp+8] ; our destination buffer
mov rcx, [rdx+json_value_ofs]
mov rsi, [rcx]
shl rsi, 2 ; in bytes
add rsi, 8 ; make sure there is always room for +2 chars
call buffer$reserve
mov rdi, [rsp] ; our object
mov rsi, [rsp+8] ; our destination buffer
mov rdx, [rsi+buffer_endptr_ofs]
mov rcx, [rdi+json_value_ofs]
mov r8, [rcx] ; length of the string in characters
add rcx, 8 ; rcx now pointing to the start of the actual string contents
xor r9d, r9d
calign
.unquotedloop:
test r8, r8
jz .endunquotedloop
if string_bits = 32
mov eax, dword [rcx]
add rcx, 4
sub r8, 1
else
movzx eax, word [rcx]
add rcx, 2
sub r8, 1
end if
mov dword [rdx], eax
add r9, 1
add rdx, 4
jmp .unquotedloop
calign
.endunquotedloop:
shl r9, 2
add [rsi+buffer_endptr_ofs], r9
add [rsi+buffer_length_ofs], r9 ; append_nocopy
pop rdi rsi
ret
calign
.quotedtab:
mov dword [rdx], '\'
mov dword [rdx+4], 't'
add rdx, 8
add r9, 2
jmp .quotedloop
calign
.quotedcr:
mov dword [rdx], '\'
mov dword [rdx+4], 'r'
add rdx, 8
add r9, 2
jmp .quotedloop
calign
.quotedlf:
mov dword [rdx], '\'
mov dword [rdx+4], 'n'
add rdx, 8
add r9, 2
jmp .quotedloop
calign
.quotedquote:
mov dword [rdx], '\'
mov dword [rdx+4], '"'
add rdx, 8
add r9, 2
jmp .quotedloop
calign
.quotedbackslash:
mov dword [rdx], '\'
mov dword [rdx+4], '\'
add rdx, 8
add r9, 2
jmp .quotedloop
calign
.quotedhexval:
; eax < 32, so we need to encode this as a \uXXXX
mov dword [rdx], '\'
mov dword [rdx+4], 'u'
mov dword [rdx+8], '0'
mov dword [rdx+12], '0'
push r12
mov r10d, eax
shr r10d, 4
mov r11d, '0'
mov r12d, '1'
test r10d, r10d
cmovz r10d, r11d
cmovnz r10d, r12d
mov dword [rdx+16], r10d
mov r10d, eax
and r10d, 0xf
mov r11d, r10d
mov r12d, r10d
add r11d, '0'
add r12d, 'a'
cmp r10d, 10
cmovb r10d, r11d
cmovae r10d, r12d
mov dword [rdx+20], r10d
pop r12
add rdx, 24
add r9, 6
jmp .quotedloop
calign
.endquotedloop:
mov dword [rdx], '"'
add r9, 1
shl r9, 2
add rdx, 4
add [rsi+buffer_endptr_ofs], r9
add [rsi+buffer_length_ofs], r9 ; append_nocopy
pop rdi rsi
ret
calign
.isobject:
mov rdx, [rdi+json_contents_ofs]
mov rcx, [rdx+_avlofs_right] ; node count in the contents map
mov rdi, rsi ; our destination buffer
mov rsi, rcx
add rsi, 2
shl rsi, 2
call buffer$reserve
mov r8, [rsp+8] ; our destination buffer
mov r9, [r8+buffer_endptr_ofs]
mov dword [r9], '{'
add qword [r8+buffer_endptr_ofs], 4
add qword [r8+buffer_length_ofs], 4
mov rcx, [rsp] ; our object
mov rdi, [rcx+json_contents_ofs] ; our contents stringmap
mov rsi, .objectappend
mov rdx, [rsp+8] ; our destination buffer
call stringmap$foreach_arg
mov rdi, [rsp] ; our object
mov rsi, [rdi+json_contents_ofs]
mov rcx, [rsi+_avlofs_right] ; node count in the contents map
test rcx, rcx
jz .objectnochildren
mov r8, [rsp+8]
mov r9, [r8+buffer_endptr_ofs]
sub r9, 4
mov dword [r9], '}'
pop rdi rsi
ret
calign
.objectnochildren:
mov r8, [rsp+8] ; our destination buffer
mov r9, [r8+buffer_endptr_ofs]
mov dword [r9], '}'
add qword [r8+buffer_endptr_ofs], 4
add qword [r8+buffer_length_ofs], 4
pop rdi rsi
ret
calign
.isarray:
mov rdx, [rdi+json_contents_ofs] ; our contents list
mov rcx, [rdx] ; the list size (in items)
mov rdi, rsi ; our destination buffer
mov rsi, rcx ; how many characters we need
add rsi, 2
shl rsi, 2
call buffer$reserve
mov r8, [rsp+8] ; our destination buffer
mov r9, [r8+buffer_endptr_ofs]
mov dword [r9], '['
add qword [r8+buffer_endptr_ofs], 4
add qword [r8+buffer_length_ofs], 4
mov rcx, [rsp] ; our object
mov rdi, [rcx+json_contents_ofs] ; our contents list
mov rsi, .arrayappend
mov rdx, [rsp+8]
call list$foreach_arg
mov rdi, [rsp] ; our object
mov rsi, [rdi+json_contents_ofs]
mov rcx, [rsi] ; the list size itself
test rcx, rcx
jz .nochildren
mov r8, [rsp+8] ; our destination buffer
mov r9, [r8+buffer_endptr_ofs]
sub r9, 4
mov dword [r9], ']'
pop rdi rsi
ret
calign
.nochildren:
mov r8, [rsp+8] ; our destination buffer
mov r9, [r8+buffer_endptr_ofs]
mov dword [r9], ']'
add qword [r8+buffer_endptr_ofs], 4
add qword [r8+buffer_length_ofs], 4
pop rdi rsi
ret
calign
.arrayappend:
push rsi
call .appendtobuffer
pop rsi
mov r8, [rsi+buffer_endptr_ofs]
mov dword [r8], ','
add qword [rsi+buffer_endptr_ofs], 4
add qword [rsi+buffer_length_ofs], 4
ret
; this one gets called with the string key in rdi, our json object in rsi, and our destination buffer in rdx
calign
.objectappend:
mov rdi, rsi
mov rsi, rdx
push rdx
call .appendtobuffer
pop rdx
mov r8, [rdx+buffer_endptr_ofs]
mov dword [r8], ','
add qword [rdx+buffer_endptr_ofs], 4
add qword [rdx+buffer_length_ofs], 4
ret
end if
if used json$stringvalue | defined include_everything
; single argument in rdi: json object
; returns the string value in rax (if and only if the type of the object is json_value)
; returns zero in rax if it wasn't a json_value
falign
json$stringvalue:
prolog json$stringvalue
cmp dword [rdi+json_type_ofs], json_value
jne .zeroret
mov rax, [rdi+json_value_ofs]
epilog
calign
.zeroret:
xor eax, eax
epilog
end if
if used json$name | defined include_everything
; single argument in rdi: json object
; returns the name in rax
; NOTE: this is just here for reference, since you can get the name out by [rdi] anyway
falign
json$name:
prolog json$name
mov rax, [rdi+json_name_ofs]
epilog
end if
if used json$arraylength | defined include_everything
; single argument in rdi: json object (which must be a json_array)
; returns the length of the array in rax
falign
json$arraylength:
prolog json$arraylength
cmp dword [rdi+json_type_ofs], json_array
jne .zeroret
mov rsi, [rdi+json_contents_ofs]
mov rax, [rsi] ; list_size offset is 0
epilog
calign
.zeroret:
xor eax, eax
epilog
end if
if used json$valueat | defined include_everything
; two arguments: json object (which must be a json_array) in rdi, index in rsi
; returns the json_object at the specified index (NOTE: this has to _walk_ the list, you sure you want to be using this?)
; (will return null if not a json_array, or index invalid)
falign
json$valueat:
prolog json$valueat
cmp dword [rdi+json_type_ofs], json_array
jne .zeroret
mov rcx, rsi
mov rsi, [rdi+json_contents_ofs]
mov rdx, [rsi+8] ; list_first offset is 8
test rdx, rdx
jz .zeroret
test rcx, rcx
jnz .walk
mov rax, [rdx+_list_valueofs]
epilog
calign
.walk:
mov rdx, [rdx+_list_nextofs]
test rdx, rdx
jz .zeroret
sub rcx, 1
jnz .walk
mov rax, [rdx+_list_valueofs]
epilog
calign
.zeroret:
xor eax, eax
epilog
end if
if used json$foreach | defined include_everything
; two arguments: json object (which can either be json_array or json_object), function to call in rsi
; NOTE: json_array foreach passes only the json object in rdi
; json_object foreach passes the name in rdi, and the object in rsi
falign
json$foreach:
prolog json$foreach
cmp dword [rdi+json_type_ofs], json_value
je .bork
cmp dword [rdi+json_type_ofs], json_array
je .isarray
; else, it is an object
mov rdi, [rdi+json_contents_ofs]
call stringmap$foreach
epilog
calign
.isarray:
mov rdi, [rdi+json_contents_ofs]
call list$foreach
epilog
calign
.bork:
epilog
end if
if used json$foreach_arg | defined include_everything
; three arguments: json object (which can either be json_array or json_object), function to call in rsi, arbitrary arg passed in rdx
; NOTE: json_array foreach_arg passes the json object in rdi and the arbitrary arg in rsi
; json_object foreach_arg passes the name in rdi, object in rsi, and the arbitrary arg in rdx
falign
json$foreach_arg:
prolog json$foreach_arg
cmp dword [rdi+json_type_ofs], json_value
je .bork
cmp dword [rdi+json_type_ofs], json_array
je .isarray
; else, it is an object
mov rdi, [rdi+json_contents_ofs]
call stringmap$foreach_arg
epilog
calign
.isarray:
mov rdi, [rdi+json_contents_ofs]
call list$foreach_arg
epilog
calign
.bork:
epilog
end if
if used json$hasvaluebyname | defined include_everything
; two arguments: json object (which must be a json_object) in rdi, string name in rsi to check for
; returns bool in eax
falign
json$hasvaluebyname:
prolog json$hasvaluebyname
cmp dword [rdi+json_type_ofs], json_object
jne .zeroret
mov rdi, [rdi+json_contents_ofs]
call stringmap$find
test rax, rax
jz .zeroret
mov eax, 1
epilog
calign
.zeroret:
xor eax, eax
epilog
end if
if used json$getvaluebyname | defined include_everything
; two arguments: json object (which must be a json_object) in rdi, string name in rsi to get
; returns json object in rax or null if kakked
falign
json$getvaluebyname:
prolog json$getvaluebyname
cmp dword [rdi+json_type_ofs], json_object
jne .zeroret
mov rdi, [rdi+json_contents_ofs]
call stringmap$find
test rax, rax
jz .zeroret
mov rax, [rax+_avlofs_value]
epilog
calign
.zeroret:
xor eax, eax
epilog
end if
if used json$parse_object | defined include_everything
; two arguments: string to parse in rdi, bool in esi as to whether or not to expect a leading function name
; returns json object in rax, or null on parse error
falign
json$parse_object:
prolog json$parse_object
push rbx r12 r13 r14 r15
mov r12, rdi
mov r15, rsi
call buffer$new
mov rbx, rax
mov rdi, r12
xor esi, esi
cmp qword [rdi], 0
je .nullret
call string$skip_whitespace ; preserves rdi, ret in rax
cmp rax, [rdi]
jae .nullret
mov r12, rdi ; save our string
mov r13, [rdi] ; its length in characters
mov r14, rax ; our starting position
test r15d, r15d
jz .noleadingfunctionname
call .parse_string ; this modifies r14
mov r15, rax ; object name saved/stored
mov rdi, r12
mov rsi, r14
call string$skip_whitespace
cmp rax, r13
jae .nullret_clearobjectname
mov r14, rax
; check the character at our new pos to see if it is a colon, and if so, skip it and any whitespace that follows
if string_bits = 32
cmp dword [r12+r14*4+8], ':'
else
cmp word [r12+r14*2+8], ':'
end if
jne .noleadingfunctionname
add r14, 1
mov rdi, r12
mov rsi, r14
call string$skip_whitespace
cmp rax, r13
jae .nullret_clearobjectname
mov r14, rax
calign
.noleadingfunctionname:
; so we may or may not have an object name in r15, next task is to parse the object
call .parse_object
test rax, rax
jnz .retokay_checkname
test r15, r15
jz .nullret
calign
.nullret_clearobjectname:
mov rdi, r15
call heap$free
mov rdi, rbx
call buffer$destroy
pop r15 r14 r13 r12 rbx
xor eax, eax
epilog
calign
.nullret:
mov rdi, rbx
call buffer$destroy
pop r15 r14 r13 r12 rbx
xor eax, eax
epilog
calign
.retokay_checkname:
test r15, r15
jz .retokay
; else, save our object return
mov r12, rax
mov rdi, [rax+json_name_ofs]
call heap$free
mov rax, r12
mov [rax+json_name_ofs], r15
calign
.retokay:
mov r14, rax
mov rdi, rbx
call buffer$destroy
mov rax, r14
pop r15 r14 r13 r12 rbx
epilog
calign
.parse_string:
; we'll return a string in rax, can be quoted or unquoted
; there is a buffer sitting in wait in rbx for our parse operation, reset it first
mov rdi, rbx
call buffer$reset
; pos is in r14, length of string in r13, string itself in r12
; it is assumed on entry that r14 < r13
if string_bits = 32
cmp dword [r12+r14*4+8], '"'
else
cmp word [r12+r14*2+8], '"'
end if
jne .parse_string_unquoted
; else, we have a quoted string
add r14, 1
calign
.parse_string_quotedloop:
; make sure we have enough buffer room:
mov rcx, [rbx+buffer_size_ofs]
sub rcx, qword [rbx+buffer_length_ofs]
cmp rcx, 32
jae .parse_string_quoted_nogrow
mov rdi, rbx
mov esi, 4096
call buffer$reserve
calign
.parse_string_quoted_nogrow:
cmp r14, r13
jae .parse_string_doreturn
if string_bits = 32
mov eax, [r12+r14*4+8]
else
movzx eax, word [r12+r14*2+8]
end if
cmp eax, '"'
je .parse_string_quoted_close
mov rsi, .parse_string_quotedloop ; save our loop entry
cmp eax, '\'
je .parse_string_escaped
; else, add it to our buffer
mov rdx, [rbx+buffer_endptr_ofs]
if string_bits = 32
mov dword [rdx], eax
add qword [rbx+buffer_endptr_ofs], 4
add qword [rbx+buffer_length_ofs], 4
else
mov word [rdx], ax
add qword [rbx+buffer_endptr_ofs], 2
add qword [rbx+buffer_length_ofs], 2
end if
add r14, 1
jmp .parse_string_quotedloop
calign
.parse_string_escaped:
add r14, 1 ; skip the \
cmp r14, r13
jae .parse_string_doreturn
if string_bits = 32
mov eax, dword [r12+r14*4+8]
else
movzx eax, word [r12+r14*2+8]
end if
cmp eax, '"'
je .parse_string_escaped_addchar
cmp eax, '\'
je .parse_string_escaped_addchar
cmp eax, '/'
je .parse_string_escaped_addchar
cmp eax, 'b'
je .parse_string_escaped_backspace
mov ecx, 13
cmp eax, 'f'
cmove eax, ecx
je .parse_string_escaped_addchar
cmp eax, 'r'
cmove eax, ecx
je .parse_string_escaped_addchar
mov ecx, 10
cmp eax, 'n'
cmove eax, ecx
je .parse_string_escaped_addchar
mov ecx, 9
cmp eax, 't'
cmove eax, ecx
je .parse_string_escaped_addchar
cmp eax, 'u'
je .parse_string_escaped_hexcode
; otherwise, we have nfi what this is
add r14, 1
jmp rsi
calign
.parse_string_escaped_addchar:
mov rdx, [rbx+buffer_endptr_ofs]
if string_bits = 32
mov dword [rdx], eax
add qword [rbx+buffer_endptr_ofs], 4
add qword [rbx+buffer_length_ofs], 4
else
mov word [rdx], ax
add qword [rbx+buffer_endptr_ofs], 2
add qword [rbx+buffer_length_ofs], 2
end if
add r14, 1
jmp rsi
calign
.parse_string_escaped_hexcode:
; we got a \u, which means we have 4 hex digits of a utf16 code?
add r14, 1
mov rcx, r13
sub rcx, r14
cmp rcx, 4
jb .parse_string_doreturn
; we need to construct a string with a proper lenght prefix, we can use our buffer for that, even though it won't be aligned
mov rdi, [rbx+buffer_endptr_ofs]
mov qword [rdi], 4
push rsi
if string_bits = 32
mov eax, dword [r12+r14*4+8]
mov ecx, dword [r12+r14*4+12]
mov edx, dword [r12+r14*4+16]
mov r8d, dword [r12+r14*4+20]
mov dword [rdi+8], eax
mov dword [rdi+12], ecx
mov dword [rdi+16], edx
mov dword [rdi+24], edx
mov esi, 16
call string$to_int_radix
else
movzx eax, word [r12+r14*2+8]
movzx ecx, word [r12+r14*2+10]
movzx edx, word [r12+r14*2+12]
movzx r8d, word [r12+r14*2+14]
mov word [rdi+8], ax
mov word [rdi+10], cx
mov word [rdi+12], dx
mov word [rdi+14], r8w
mov esi, 16
call string$to_int_radix
end if
add r14, 3 ; 3 here because addchar adds one as well
; so now we have an integer value (hopefully in eax)
pop rsi
jmp .parse_string_escaped_addchar
calign
.parse_string_escaped_backspace:
add r14, 1
cmp qword [rbx+buffer_length_ofs], 0
je .parse_string_escaped_backspace_next
if string_bits = 32
sub qword [rbx+buffer_endptr_ofs], 4
sub qword [rbx+buffer_length_ofs], 4
else
sub qword [rbx+buffer_endptr_ofs], 2
sub qword [rbx+buffer_length_ofs], 2
end if
calign
.parse_string_escaped_backspace_next:
jmp rsi
calign
.parse_string_unquoted:
mov rcx, [rbx+buffer_size_ofs]
sub rcx, qword [rbx+buffer_length_ofs]
cmp rcx, 32
jae .parse_string_unquoted_nogrow
mov rdi, rbx
mov esi, 4096
call buffer$reserve
calign
.parse_string_unquoted_nogrow:
cmp r14, r13
jae .parse_string_doreturn
if string_bits = 32
mov eax, [r12+r14*4+8]
else
movzx eax, word [r12+r14*2+8]
end if
; determine if eax is a space, and if so, skip it and return
cmp eax, 32
ja .parse_string_unquoted_notspace
mov ecx, eax
sub ecx, 1
mov r8d, 1
shl r8d, cl
test r8d, 2147488512
jz .parse_string_unquoted_notspace
; else, we hit a space
add r14, 1
jmp .parse_string_doreturn
calign
.parse_string_unquoted_notspace:
mov rsi, .parse_string_unquoted
cmp eax, '\'
je .parse_string_escaped
; else, add it to our buffer
mov rdx, [rbx+buffer_endptr_ofs]
if string_bits = 32
mov dword [rdx], eax
add qword [rbx+buffer_endptr_ofs], 4
add qword [rbx+buffer_length_ofs], 4
else
mov word [rdx], ax
add qword [rbx+buffer_endptr_ofs], 2
add qword [rbx+buffer_length_ofs], 2
end if
add r14, 1
jmp .parse_string_unquoted
calign
.parse_string_quoted_close:
add r14, 1 ; skip the close quote
; convert the buffer contents in rbx to a string and return
mov rdi, [rbx+buffer_itself_ofs]
mov rsi, [rbx+buffer_length_ofs]
if string_bits = 32
call string$from_utf32
else
call string$from_utf16
end if
ret
calign
.parse_string_doreturn:
; convert the buffer contents in rbx to a string and return
mov rdi, [rbx+buffer_itself_ofs]
mov rsi, [rbx+buffer_length_ofs]
if string_bits = 32
call string$from_utf32
else
call string$from_utf16
end if
ret
calign
.parse_object:
mov rdi, r12
mov rsi, r14
call string$skip_whitespace
cmp rax, r13
jae .parse_object_nullret
mov r14, rax
; make sure the character we are sitting on is an openbrace, nullret if not
if string_bits = 32
cmp dword [r12+r14*4+8], '{'
else
cmp word [r12+r14*2+8], '{'
end if
jne .parse_object_nullret
; otherwise, we need a new object
call string$new
mov rdi, rax
call json$newobject_nocopy
sub rsp, 16
mov [rsp], rax ; our [rsp] == our newly created json object (with a 0 length name)
add r14, 1
calign
.parse_object_loop:
cmp r14, r13
jae .parse_object_nullret_cleanit
mov rdi, r12
mov rsi, r14
call string$skip_whitespace
cmp rax, r13
jae .parse_object_nullret_cleanit
mov r14, rax
; check to see if we are an empty object (char at r14 openbrace?)
if string_bits = 32
mov eax, dword [r12+r14*4+8]
else
movzx eax, word [r12+r14*2+8]
end if
cmp eax, '}'
je .parse_object_emptyobject
cmp eax, '"'
jne .parse_object_nullret_cleanit
; else, we have a variable name, quoted
call .parse_string
; we now have a string in rax that is our vname
mov [rsp+8], rax
mov rdi, r12
mov rsi, r14
call string$skip_whitespace
cmp rax, r13
jae .parse_object_nullret_cleanwithname
mov r14, rax
; make sure the next character we are sitting on is a :
if string_bits = 32
cmp dword [r12+r14*4+8], ':'
else
cmp word [r12+r14*2+8], ':'
end if
jne .parse_object_nullret_cleanwithname
add r14, 1
mov rdi, r12
mov rsi, r14
call string$skip_whitespace
cmp rax, r13
jae .parse_object_nullret_cleanwithname
mov r14, rax
; now we can need to parse the actual value, which needs our arguments:
mov rdi, [rsp] ; the json object
mov rsi, [rsp+8] ; the value name
; .parse_value swallows the vname (assumes ownership of it)
; so afterwards, all we are concerned with is the object in [rsp]
call .parse_value ; returns bool in eax as to whether it succeeded or not
test eax, eax
jz .parse_object_nullret_cleanit
mov rdi, r12
mov rsi, r14
call string$skip_whitespace
cmp rax, r13
jae .parse_object_nullret_cleanit
mov r14, rax
; character here can be either a close brace or a comma, anything else == death on a stick
if string_bits = 32
mov eax, dword [r12+r14*4+8]
else
movzx eax, word [r12+r14*2+8]
end if
cmp eax, '}'
je .parse_object_emptyobject ; not really empty, but does the same thing we need
cmp eax, ','
jne .parse_object_nullret_cleanit
add r14, 1
jmp .parse_object_loop
calign
.parse_object_emptyobject:
add r14, 1
mov rdi, r12
mov rsi, r14
call string$skip_whitespace
mov r14, rax
mov rax, [rsp]
add rsp, 16
ret
calign
.parse_object_nullret_cleanit:
mov rdi, [rsp]
add rsp, 16
call json$destroy
xor eax, eax
ret
calign
.parse_object_nullret_cleanwithname:
mov rdi, [rsp]
call json$destroy
mov rdi, [rsp+8]
call heap$free
add rsp, 16
xor eax, eax
ret
calign
.parse_object_nullret:
xor eax, eax
ret
; this one gets two "proper" args: rdi == the json object, rsi == the string of our value name
; string goods in r12..r14 are valid
; we assume ownership of the string in rsi (meaning no matter what, we must clean it up or place/use it)
; we return bool in eax as to whether we succeeded or not
calign
.parse_value:
push rsi rdi
if string_bits = 32
mov eax, dword [r12+r14*4+8]
else
movzx eax, word [r12+r14*2+8]
end if
cmp eax, '"' ; quoted string
je .parse_value_quoted
cmp eax, '.' ; this is (from what I gather) not from spec, but i have seen in the wild: "varname":.5
je .parse_value_number
cmp eax, '-'
je .parse_value_number
cmp eax, '0'
jb .parse_value_notnumber
cmp eax, '9'
jbe .parse_value_number
calign
.parse_value_notnumber:
cmp eax, '{'
je .parse_value_object
cmp eax, '['
je .parse_value_array
cmp eax, 't' ; true
je .parse_value_true
cmp eax, 'f' ; false
je .parse_value_false
cmp eax, 'n' ; null
je .parse_value_null
cmp eax, 'E' ; again, not spec, but i have confirmed (broken-assed) behaviour in the wild of: "varname":E
je .parse_value_weirde
calign
.parse_value_kakkedret:
mov rdi, [rsp+8]
call heap$free ; free our string
xor eax, eax ; false return
pop rdi rsi
ret
calign
.parse_value_object:
call .parse_object
test rax, rax
jz .parse_value_kakkedret ; will free the string
mov rdi, [rax+json_name_ofs]
mov rsi, [rsp+8] ; the vname
mov [rax+json_name_ofs], rsi
mov [rsp+8], rax ; save the object temporarily
call heap$free ; free the previous name
mov rdi, [rsp] ; the parent object
mov rsi, [rsp+8]
call json$appendchild
pop rdi rsi
mov eax, 1
ret
calign
.parse_value_array:
add r14, 1
cmp r14, r13
jae .parse_value_kakkedret
; create a new json array object, give it the vname
mov rdi, [rsp+8]
call json$newarray_nocopy
mov [rsp+8], rax ; save our array object
mov rdi, [rsp]
mov rsi, rax
call json$appendchild ; add it to the parent object
; check for the special case if an empty array
if string_bits = 32
mov eax, dword [r12+r14*4+8]
else
movzx eax, word [r12+r14*2+8]
end if
cmp eax, ']'
je .parse_value_array_empty
; loop through the array contents
calign
.parse_value_array_loop:
mov rdi, r12
mov rsi, r14
call string$skip_whitespace
cmp rax, r13
jae .parse_value_array_kakked
mov r14, rax
; we need a new empty string for the arg to .parse_value
call string$new
; reentrantly call .parse_value, with the parent arg set to the array
mov rdi, [rsp+8]
mov rsi, rax
call .parse_value
test eax, eax
jz .parse_value_array_kakked
mov rdi, r12
mov rsi, r14
call string$skip_whitespace
cmp rax, r13
jae .parse_value_array_kakked
mov r14, rax
if string_bits = 32
mov eax, dword [r12+r14*4+8]
else
movzx eax, word [r12+r14*2+8]
end if
cmp eax, ','
je .parse_value_array_nextitem
cmp eax, ']'
jne .parse_value_array_kakked
add r14, 1
pop rdi rsi
mov eax, 1
ret
calign
.parse_value_array_nextitem:
add r14, 1
jmp .parse_value_array_loop
calign
.parse_value_array_empty:
add r14, 1
pop rdi rsi
mov eax, 1
ret
calign
.parse_value_array_kakked:
; now, because we already consumed vname, and we already added our array to the parent object
; we don't need to do anything other than just return false
pop rdi rsi
xor eax, eax
ret
calign
.parse_value_quoted:
call .parse_string
; string is now in rax of its actual value, vname is in [rsp+8], and the object is in [rsp]
mov rdi, [rsp+8]
mov rsi, rax
call json$newvalue_nocopy
mov rdi, [rsp]
mov rsi, rax
call json$appendchild
pop rdi rsi
mov eax, 1
ret
calign
.parse_value_number:
mov rdi, r12 ; setup the string parm for our substr call
mov rsi, r14 ; save the starting position
add r14, 1
; now walk forward while we have what looks like numeric goods
calign
.parse_value_number_loop:
cmp r14, r13
jae .parse_value_number_done
if string_bits = 32
mov eax, dword [r12+r14*4+8]
else
movzx eax, word [r12+r14*2+8]
end if
cmp eax, '-'
je .parse_value_number_next
cmp eax, '.'
je .parse_value_number_next
cmp eax, 'e'
je .parse_value_number_next
cmp eax, 'E'
je .parse_value_number_next
cmp eax, '+'
je .parse_value_number_next
cmp eax, '0'
jb .parse_value_number_done
cmp eax, '9'
ja .parse_value_number_done
calign
.parse_value_number_next:
add r14, 1
jmp .parse_value_number_loop
calign
.parse_value_number_done:
mov rdx, r14
sub rdx, rsi
call string$substr
; string is now in rax of the substr value, vname is in [rsp+8], and the object is in [rsp]
mov rdi, [rsp+8]
mov rsi, rax
call json$newvalue_nocopy
mov rdi, [rsp]
mov rsi, rax
call json$appendchild
pop rdi rsi
mov eax, 1
ret
calign
.parse_value_weirde:
; per the brief comment above, there is at least one broken browser that sends
; "varname":E
; if the _string_ value is indeed an E by itself
; Sooo... to accommodate this broken-assed behaviour, if the next character after the E is a comma or a }, we will
; go ahead and accept it, otherwise, we return false due to invalid json encoding
mov rcx, r14
add rcx, 1
cmp rcx, r13
jae .parse_value_kakkedret
if string_bits = 32
mov eax, dword [r12+rcx*4+8]
else
movzx eax, word [r12+rcx*2+8]
end if
cmp eax, ','
je .parse_value_weirde_ok
cmp eax, '}'
jne .parse_value_kakkedret
calign
.parse_value_weirde_ok:
mov rdi, r12
mov rsi, r14
mov edx, 1
call string$substr
; string is now in rax of the substr value, vname is in [rsp+8], and the object is in [rsp]
mov rdi, [rsp+8]
mov rsi, rax
call json$newvalue_nocopy
mov rdi, [rsp]
mov rsi, rax
call json$appendchild
pop rdi rsi
mov eax, 1
ret
cleartext .truestr, 'true'
calign
.parse_value_true:
add r14, 4
mov rdi, .truestr
call string$copy
; string is now in rax of the value, vname is in [rsp+8], and the object is in [rsp]
mov rdi, [rsp+8]
mov rsi, rax
call json$newvalue_nocopy
mov rdi, [rsp]
mov rsi, rax
call json$appendchild
pop rdi rsi
mov eax, 1
ret
cleartext .falsestr, 'false'
calign
.parse_value_false:
add r14, 5
mov rdi, .falsestr
call string$copy
; string is now in rax of the value, vname is in [rsp+8], and the object is in [rsp]
mov rdi, [rsp+8]
mov rsi, rax
call json$newvalue_nocopy
mov rdi, [rsp]
mov rsi, rax
call json$appendchild
pop rdi rsi
mov eax, 1
ret
cleartext .nullstr, 'null'
calign
.parse_value_null:
add r14, 4
mov rdi, .nullstr
call string$copy
; string is now in rax of the value, vname is in [rsp+8], and the object is in [rsp]
mov rdi, [rsp+8]
mov rsi, rax
call json$newvalue_nocopy
mov rdi, [rsp]
mov rsi, rax
call json$appendchild
pop rdi rsi
mov eax, 1
ret
end if