Skip to content

Instantly share code, notes, and snippets.

@Guevara-chan
Last active October 5, 2023 01:45
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Guevara-chan/2d10691e0146aae4c96ff534978529f8 to your computer and use it in GitHub Desktop.
Save Guevara-chan/2d10691e0146aae4c96ff534978529f8 to your computer and use it in GitHub Desktop.
RayLib h2nim autoconverter
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- #
# 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