Last active
July 4, 2024 11:32
-
-
Save Guevara-chan/2d10691e0146aae4c96ff534978529f8 to your computer and use it in GitHub Desktop.
RayLib h2nim autoconverter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- # | |
# RayLib h2nim autoconverter v0.05 | |
# Developed in 2*20 by Guevara-chan | |
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- # | |
[fs, rl, http] = [require('fs'), require('readline'), require('https')] if process? | |
#.{ [Procedures] | |
sanity = (name, undes = /\*/g) -> | |
name = name.replace(undes, '') | |
if name in ['type', 'end', 'from', 'div', 'ptr'] then name + 'x' else name | |
# ---------------------- # | |
type_conv = (type, name) -> | |
wrap_ptr = (id, rtype = '') -> if id?.startsWith '*' then 'pointer' else rtype | |
tdef = switch type # Return type | |
when 'const void', 'void' then if name is '@' then 'void' else wrap_ptr name | |
when 'long', 'int' then wrap_ptr name, 'int32' | |
when 'float' then 'float32' | |
when 'double' then 'float64' | |
when 'const char' then 'cstring' | |
when 'unsigned int' then 'uint32' | |
when 'unsigned char' then 'uint8' | |
when 'unsigned short' then 'uint16' | |
when 'const unsigned char' then 'UncheckedArray[byte]' | |
else # Pointer stuff. | |
if type.endsWith('*') then "proc()#{type_conv(type[0..-2], '@')}" | |
else if name?.startsWith('**') and type is 'char' then "cstringArray" | |
else (if name?.startsWith '*' then "ptr #{type}" else type).replace 'const ', 'ptr ' | |
tdef = ": #{tdef}" if tdef | |
return tdef | |
# ---------------------- # | |
c2nim = (line) -> | |
# 1-line definitions. | |
esort = (a, b) -> parseInt(a.split('=')[1]) - parseInt(b.split('=')[1]) | |
emap = (x) ->"#{x} #{x.remark ? ''}" | |
block = () => @noparse = 1 + (@noparse ? 0); '' | |
unblock = () => @noparse = Math.max 0, (@noparse ? 0)-1; '' | |
# Compex subdef. | |
efilter = (x) => | |
if '=' in x | |
[name, field_val] = x.split('=').map((y) -> y.trim()) | |
if parseInt(field_val) > 0 and field_val is @prev_val | |
@excess.push "const #{name}* = #{field_val}"; return false | |
@prev_val = field_val | |
return true | |
# Aux microclass. | |
class StringRem extends String | |
constructor: (text, remark) -> super text; @remark = remark | |
# Main parser. | |
if @delay? then [line, @delay] = [@delay + line, undefined] # Line contination. | |
comment = if (remarked = line.split('//'))[1]? then "# #{remarked[1].trim()}" else '' | |
conv = switch (words = (rawcode = remarked[0]).trimRight().split ' ')[0] | |
when 'RLAPI', 'RAYGUIDEF', 'RMDEF', 'PHYSACDEF' # [Func def] | |
if not (')' in rawcode) then @delay = rawcode; break # Multi-line definition. | |
break unless ';' in rawcode if words[0] isnt 'RMDEF' # Reimplementation. | |
words = (halves = rawcode.split '(')[0].split ' ' # First signature half before '(' | |
name = words[words.length-1] # Func name. | |
rtype = type_conv words[1..-2].join(' '), name # Return type. | |
name = name.replace(/\*/g, '') | |
argdef = if 'void' isnt (args = halves[1].trim()[0..(if ';' in rawcode then -3 else -2)]) # Args. | |
(for arg in args.split ',' | |
argsig = arg.trim().split ' ' # Typing signature. | |
aname = argsig[argsig.length-1] # Argument name. | |
if argsig[0] is '...' then meta = 'varargs, '; break # Variable arg array. | |
else "#{sanity(aname)}#{type_conv(argsig[0..-2].join(' '), aname)}" # Ordinary arg. | |
).join('; ') | |
else '' # Empty arg list. | |
["proc #{name}*(#{argdef})#{rtype} {.#{words[0]}, #{meta ? ''}importc: \"#{name}\".}", | |
comment].join(' ') # Adding remarks if provided | |
when '' # [Field] | |
words = rawcode.trim().split(' ') | |
if (determ = words[0]) | |
if determ is '//' # ~In-struct comment. | |
comment = ' ' + comment | |
if @buffer? then (@buffer.push(comment); '') else comment | |
else if '=' in words or not (" " in rawcode.trim()) # ~Enum field. | |
@buffer?.push new StringRem(rawcode.trimRight().replace(',', ''), comment); '' | |
else if determ.startsWith('#') # ~Compile-time conditional. | |
if (meta = words[words.length-1]) in ['RLAPI','RAYGUIDEF','inline','static'] # Proc call signature. | |
break if meta is 'inline' and not ('__declspec(dllexport)' in words) # Disable multipragma. | |
meta = if meta isnt 'RAYGUIDEF' then 'libraylib' else @feed[0..-3]# 90% api already in libraylib | |
if @head_pragma is undefined | |
@head_pragma = "{.pragma: #{words[1]}, cdecl, discardable, dynlib: \"#{meta}\" & LEXT.}" | |
else if determ is '#include' and words[1].includes 'raylib' then "import #{words[1][1..-4]}" | |
else if determ is 'struct' then block() # ~Anon subdefinition. | |
else if determ is "}" then unblock() # ~End anon subdefinition | |
else if determ is 'typedef' or '}' in rawcode or '(' in rawcode or rawcode.startsWith ' ' | |
'' # ~Double-indented code. | |
else # ~Struct field. | |
tdef = words[0..-2].join(' ') # Type definition. | |
name = if "," in tdef # ..Multiple fields | |
tdef = words[0] | |
words[1..].join(' ')[0..-2].replace(/,/g, '!,') | |
else words[words.length-1][0..-2] # ...Ordinary field. | |
typing = type_conv tdef, name | |
if '[' in name # ..Array fields. | |
dim = name.split('[') | |
[name, typing] = [dim[0], ": array[0..#{parseInt(dim[1][0..-2])-1}, #{typing[2..]}]"] | |
if name is @prev_val then " \# Skipped another #{name}" | |
else @prev_val = name; " #{sanity(name).replace(/\!/g, "*")}*#{typing} #{comment}" | |
else if line.startsWith '/' then comment | |
when 'typedef' # [Type def] | |
switch determ = words[1] | |
when 'struct' # ~Object def. | |
if words[2] is 'VertexBuffer' then @buffer = undefined; unblock() # !QUICKFIX! | |
meta = "type #{words[2]}* {.bycopy.} = object" + (if '}' in rawcode | |
field = words[5].split('[') | |
"\n #{field[0]}*: array[0..#{parseInt(field[1][0..-2])-1}, #{type_conv(words[4])[2..]}]" | |
else '') | |
when 'enum' then @buffer = ['type ']; '' # ~Enum def. | |
else | |
if rawcode.includes(')(') # ~Callback def. | |
[dummy, rtype, ..., name] = cb_header = rawcode.split(')(')[0].split(' ') | |
if rtype is 'unsigned' then rtype += " " + cb_header[2].replace(/\W/g, "") | |
"type #{name.replace(/\W/g, "")}* = proc()#{type_conv(rtype)}" | |
else "type #{words[2][0..-2]}* = #{words[1]}" if determ isnt 'void' # ~Type redef. | |
when '#define' # [Macro def] | |
chans = rawcode.trimRight().split('{ ')[1][0..-3].split(', ') if '{' in rawcode and '}' in rawcode | |
compose = (type, fields) -> | |
"template #{words[1]}*(): auto = #{type}(#{chans.map((x,i)->fields[i]+": #{x}").join(', ')}) #{comment}" | |
if 'CLITERAL(Color){' in words then compose 'Color', 'rgba' # Color const. | |
else if '(Vector2){' in words then compose 'Vector2', 'xy' # Vector const. | |
else if words[1] is 'RLAPI' then "{.pragma: RLAPI, cdecl, discardable, dynlib: \"libraylib\" & LEXT.}" | |
else if words[1] is 'RAYLIB_H' # Raylib main identitifier. | |
"""converter int2in32* (self: int): int32 = self.int32 | |
const LEXT* = when defined(windows):\".dll\" | |
elif defined(macosx): \".dylib\" | |
else: \".so\" | |
""" | |
else if (name = words[1]).toUpperCase() isnt name # Function alias. | |
@excess.push (if name is 'SpriteFont' then 'type' else "const") + " #{name}* = #{words[words.length-1]}" | |
"\# Definition of #{name} was moved to EOF." | |
else if (rep = words[words.length-1].split('_'))[0] is rep[1] # Special case for insane definitions. | |
"\# #{rawcode}" | |
else "template #{words[1]}*(): auto = #{words[words.length-1]}" # Simple macro. | |
when '}' # [End def] | |
if @buffer? # Enum finisher. | |
if words[1] # Enum name (postfix) | |
@buffer[0] += "#{name = words[1][0..-2]}* = enum" # Enumeration name. | |
@buffer.push "converter #{name}2int32* (self: #{name}): int32 = self.int32" # Autoconvert. | |
([conv, @buffer] = [@buffer.sort(esort).filter(efilter).map(emap).join('\n'), undefined])[0] | |
else unblock() | |
else unblock() # Proc body finisher. | |
when 'void','int','float','unsigned','bool','char','const','static','{',' | |
Color','Rectangle','Vector2','Font','Matrix' then block() # [Func body] | |
when '/*' then block() # [Multi-line comment] | |
when '*/' then unblock() # [End multiline comment] | |
when '*' then "# #{line[2..]}" # [Header comment] | |
when '#if' # [Conditional compilation] | |
if 'defined(GRAPHICS_API_OPENGL_33)' in words then undefined # !QUICKFIX! | |
else if words[1] isnt 'defined(RAYMATH_IMPLEMENTATION)' and words[1].includes 'IMPLEMENTATION' then block() | |
when '#elif' # [Conditional compilation branch] | |
if words[1] is 'defined(GRAPHICS_API_OPENGL_ES2)' then block() | |
return conv unless @noparse | |
# ---------------------- # | |
parse = (fpath) -> | |
[url, src] = ["https://raw.githubusercontent.com/raysan5/#{fpath}", fpath[1+fpath.lastIndexOf('/')..]] | |
fname = src[..-3] | |
if process? # Node version. | |
unless fs.existsSync src # Getting missing headers. | |
storage = fs.createWriteStream src | |
http.get url, (resp) -> | |
resp.on 'data', (chunk) -> storage.write chunk | |
resp.on 'end', () -> parse fpath | |
return {feed: src} | |
parser = rl.createInterface {input: fs.createReadStream src, terminal: false} | |
parser.feed = src | |
parser.out = fs.createWriteStream "#{fname}.nim" | |
parser.excess = [] | |
parser.on 'line', (line) -> @out.write "#{code}\n" if code = c2nim.call @, line | |
parser.on 'close', () -> @out.write @excess.join '\n' | |
else # Browser version. | |
parser = {feed: src, excess: []} | |
fetch(url).then((resp) -> resp.text()).then (code) -> | |
saveAs new Blob([code.split('\n').map(c2nim.bind parser).filter(Boolean).concat(parser.excess).join('\n')], | |
{type: "text/plain;charset=utf-8"}), "#{fname}.nim" | |
return parser | |
#.} [Procedures] | |
# ==Main code== | |
console.log '->', job.feed for job in [ | |
"raylib/master/src/raylib.h","raygui/master/src/raygui.h","raylib/master/src/rlgl.h","raylib/master/src/raymath.h" | |
].map(parse) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment