naylib icon indicating copy to clipboard operation
naylib copied to clipboard

Plan to rewrite the generator scripts.

Open planetis-m opened this issue 1 year ago • 4 comments

The scripts that are used to generate the wrapper have grown in complexity and are difficult to understand and modify. Experience has shown that trying to modify the produced nim code from a representation like, json, xml, etc, that the generators are using, leads to horrible, unmaintanable code. This is better done at the nim AST level. So to evolve the scripts, keep the maintenance cost down, make them easily extensive, the two separate tasks of parsing the C header definitions and outputting bindings, and making an idiomatic nim wrapper based on those bindings, should use two different programs. The bindings parser, can use the raylib_parser for a start, or eventually be ported to c2nim/furthark. It doesn't attempt to do any transformations and use the c types. This is necessary as upstream will not improve it to parse other headers than raylib.h correctly, it half works with rlgl.h and requires manual intervention. Another program the wrapper generator, makes use of the compiler as a library, and parses the bindings it traverses the nim ast and writes our wrapper in a file.

planetis-m avatar May 05 '24 14:05 planetis-m

mwe for the second program:

import
  compiler/[ast, parser, idents, astalgo, pathutils, condsyms, renderer,
  options, nimconf, extccomp, modulegraphs], std/[os, strutils]

# need to run ./koch checksums

proc str(n: PNode): string =
  case n.kind
  of nkStrLit..nkTripleStrLit:
    result = n.strVal
  of nkIdent:
    result = n.ident.s
  of nkSym:
    result = n.sym.name.s
  of nkOpenSymChoice, nkClosedSymChoice:
    result = n.sons[0].sym.name.s
  else:
    assert false

proc basename(n: PNode): PNode =
  case n.kind
  of nkIdent: result = n
  of nkPragmaExpr:
    result = basename(n[0])
  of nkPostfix, nkPrefix:
    result = basename(n[1])
  of nkAccQuoted:
    result = basename(n[0])
  else:
    assert false

proc eqIdent(a: string; b: string): bool =
  result = cmpIgnoreStyle(a, b) == 0

proc eqIdent(a: PNode, b: string): bool =
  result = cmpIgnoreStyle(a.basename.str, b) == 0

proc eqIdent(a, b: PNode): bool =
  result = cmpIgnoreStyle(a.basename.str, b.basename.str) == 0

proc groupMatrixFieldsByRow(node: PNode) =
  const MatN = 4 # assumes a 4x4 matrix
  case node.kind
  of nkTypeSection:
    for child in node.items:
      if child.kind == nkTypeDef and eqIdent(child[0], "Matrix"):
        let reclist = child[2][2]
        if reclist.len != MatN*MatN: return
        for i in countdown(reclist.len-1, 0, MatN):
          for j in countdown(i-1, i-MatN+1):
            reclist[i].sons.insert(reclist[j][0])
        for i in countdown(reclist.len-1, 0):
          if (i + 1) mod 4 != 0:
            reclist.sons.delete(i)
  else:
    discard
  for child in node.items:
    groupMatrixFieldsByRow(child)

proc main =
  # Create a new configuration and module graph
  let conf = newConfigRef()
  let cache = newIdentCache()
  let graph = newModuleGraph(cache, conf)
  # Initialize defines and load configurations
  condsyms.initDefines(conf.symbols)
  conf.projectName = "stdinfile"
  conf.projectFull = "stdinfile".AbsoluteFile
  conf.projectPath = canonicalizePath(conf, getCurrentDir().AbsoluteFile).AbsoluteDir
  conf.projectIsStdin = true
  loadConfigs(DefaultConfig, cache, conf, graph.idgen)
  # Initialize external compiler variables
  extccomp.initVars(conf)
  # Parse input file
  let filename = "input.nim"
  let node = parseString(readFile(filename), cache, conf)
  # Apply postprocessing steps
  node.groupMatrixFieldsByRow()
  # Render the module to an output file
  renderModule(node, filename, {renderDocComments})

main()

planetis-m avatar May 05 '24 18:05 planetis-m

Something similar was done in https://github.com/nim-lang/c2nim/blob/araq-gobject/gobject2nim.nim

planetis-m avatar May 06 '24 13:05 planetis-m

I was ignoring the easiest solution which is a wrapping macro like: https://github.com/nim-lang/opengl/blob/master/src/opengl/private/errors.nim

Thanks to that you have the ability to influence the wrapper per project with defines:

image

Maybe per https://github.com/planetis-m/naylib/issues/102 this can be used to make raylib accept int/float types instead of int32/float32 for people that don't care.

There're helpful macros for this purpose as well: https://github.com/geekrelief/genit

planetis-m avatar Jun 20 '24 18:06 planetis-m

It's great that you're documenting the progress here via issues!

Xkonti avatar Jul 26 '24 02:07 Xkonti

Nah waste of time, instead the existing scripts were refactored.

planetis-m avatar Oct 05 '24 12:10 planetis-m