├── .gitignore ├── LICENSE ├── README.md ├── cli_anim.svg ├── src ├── webidl2nim.nim └── webidl2nim │ ├── ast.nim │ ├── ast_gen.nim │ ├── ast_repr.nim │ ├── deps.nim │ ├── grammar.nim │ ├── lexer.nim │ ├── object_signatures.nim │ ├── object_signatures_dsl.nim │ ├── parser.nim │ ├── tokens.nim │ ├── translate_types_dsl.nim │ ├── translator.nim │ └── unode.nim ├── tests └── config.nims └── webidl2nim.nimble /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | nimblecache/ 3 | htmldocs/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ASVIEST 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webidl2nim 2 | Tool to translate webidl code to Nim (js target). 3 | ## Cli 4 | After installing you can just type: 5 | ```bash 6 | webidl2nim 7 | ``` 8 | and translate code via cli 9 | ![cli animation](./cli_anim.svg) 10 | ## Quickstart 11 | ```nim 12 | import webidl2nim 13 | import std/[deques, sequtils, sugar] 14 | import pkg/npeg 15 | 16 | let t = tokenize""" 17 | interface Hello-Webidl { 18 | }; 19 | """ 20 | let c = parseCode(t).stack.toSeq 21 | 22 | let translator {.used.} = Translator( 23 | settings: TranslatorSettings( 24 | optionalAttributePolicy: GenDeferredProcs,#UseDefaultVal,#GenDeferredProcs, 25 | features: { 26 | MethodCallSyntax, 27 | NamespaceJsFieldBinding, 28 | ObjConstrRequired, 29 | ReadonlyAttributes 30 | }, 31 | exportCode: true, 32 | onIdent: (node: NimUNode, isDecl: bool) => 33 | node 34 | .nep1Rename(isDecl) 35 | .keywordToAccQuoted() 36 | .makePublic 37 | ), 38 | ) 39 | 40 | import "$nim"/compiler/[ast, renderer] 41 | echo translator.translate(c).assemble(translator.imports).to(PNode) 42 | ``` 43 | Output: 44 | ```nim 45 | type 46 | HelloWebidl* = ref object of JsRoot 47 | ``` 48 | ## User defined types 49 | webidl2nim support to adding new user types. 50 | ```nim 51 | translateTypesDsl MyMapping: 52 | HTML5Canvas: 53 | import pkg/html5_canvas 54 | -> Canvas 55 | translator.addMapping MyMapping 56 | ``` 57 | 58 | ## Features 59 | #### import std lib modules that needed for definition. 60 | ```webidl 61 | interface NumberWrapper { 62 | attribute bigint num; 63 | }; 64 | ``` 65 | Output: 66 | ```nim 67 | import 68 | std / jsbigints 69 | 70 | type 71 | NumberWrapper* = ref object of JsRoot 72 | num* {.importc: "num".}: JsBigInt 73 | ``` 74 | #### automatically reorder code. 75 | ```webidl 76 | interface NumberWrapper { 77 | type-from-future sum(short ...num); 78 | }; 79 | 80 | typedef unsigned long type-from-future; 81 | ``` 82 | Output: 83 | ```nim 84 | type 85 | TypeFromFuture* = distinct uint32 86 | NumberWrapper* = ref object of JsRoot 87 | 88 | proc sum*(self: NumberWrapper; num: varargs[int16]): TypeFromFuture 89 | {.importjs: "#.$1(#)".} 90 | ``` 91 | #### method call syntax support (UFCS) 92 | ```webidl 93 | interface NumberWrapper { 94 | unsigned long long sum(short ...num); 95 | }; 96 | ``` 97 | Output (with method call syntax): 98 | ```nim 99 | type 100 | NumberWrapper* = ref object of JsRoot 101 | 102 | proc sum*(self: NumberWrapper; num: varargs[int16]): uint64 103 | {.importjs: "#.$1(#)".} 104 | ``` 105 | Output (without method call syntax): 106 | ```nim 107 | type 108 | NumberWrapper* = ref object of JsRoot 109 | 110 | proc sum*(self: NumberWrapper; num: varargs[int16]): uint64 {.importc.} 111 | ``` 112 | 113 | ## Unsupported things: 114 | In webidl when value out of bounds of limited size types, value casting to type. 115 | ```webidl 116 | [Exposed=Window] 117 | interface GraphicsContext { 118 | undefined setColor(octet red, octet green, octet blue); 119 | }; 120 | ``` 121 | ```js 122 | var context = getGraphicsContext(); 123 | context.setColor(-1, 255, 257); // it's equals to context.setColor(255, 255, 1) 124 | ``` 125 | It removes the benefits of static typing, so it unsupported. 126 | -------------------------------------------------------------------------------- /cli_anim.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 53 | 76 | 77 | 78 | [admin@archlinux ~]$ [admin@archlinux ~]$ w [admin@archlinux ~]$ we [admin@archlinux ~]$ web [admin@archlinux ~]$ webi [admin@archlinux ~]$ webidl2nim [admin@archlinux ~]$ webidl2nim ---------------------------------------------------------------------------------------- Write webidl code: interface NumberWrapper { TypeFromFuture sum(optional unsigned long long a = 4, optional short num);};typedef unsigned long TypeFromFuture; typedef unsigned long TypeFromFuture;---------------------------------------------------------------------------------------- Output Nim code: type TypeFromFuture* = distinct uint32 NumberWrapper* = ref object of JsRoot proc sum*(self: NumberWrapper; a: uint64 = 4; num: int16): TypeFromFuture {.importc.}proc sum*(self: NumberWrapper; a: uint64 = 4): TypeFromFuture {.importc.}[admin@archlinux ~]$ e [admin@archlinux ~]$ ex [admin@archlinux ~]$ exi [admin@archlinux ~]$ exit [admin@archlinux ~]$ exitexit 79 | -------------------------------------------------------------------------------- /src/webidl2nim.nim: -------------------------------------------------------------------------------- 1 | import webidl2nim/[lexer, parser, translator, unode, translate_types_dsl] 2 | 3 | export translator 4 | export translate_types_dsl 5 | export unode 6 | 7 | export parseCode 8 | export tokenize 9 | 10 | when isMainModule: 11 | # cli 12 | import std/[deques, sequtils, strutils, sugar, options, terminal] 13 | from os import walkPattern 14 | import pkg/[npeg, cligen] 15 | import packages/docutils/highlite 16 | import "$nim"/compiler/[ast, renderer] 17 | 18 | template writeColored(color: ForegroundColor, bright: bool = false, body: untyped) = 19 | if cliShowColor: 20 | stdout.setForegroundColor(color, bright) 21 | body 22 | 23 | if cliShowColor: 24 | stdout.resetAttributes() 25 | stdout.flushFile() 26 | 27 | proc writeCenter(s: string)= 28 | stdout.writeLine center(s, terminalWidth()) 29 | 30 | proc writeSep() = 31 | stdout.writeLine "-".repeat(terminalWidth()) 32 | 33 | template writeNimHighlight(code: string)= 34 | var toknizr: GeneralTokenizer 35 | initGeneralTokenizer(toknizr, code) 36 | while true: 37 | getNextToken(toknizr, langNim) 38 | case toknizr.kind 39 | of gtEof: break # End Of File (or string) 40 | of gtWhitespace: 41 | stdout.resetAttributes() 42 | stdout.write substr(code, toknizr.start, toknizr.length + toknizr.start - 1) 43 | of gtOperator: 44 | var s = substr(code, toknizr.start, toknizr.length + toknizr.start - 1) 45 | 46 | writeColored(if s == "*": fgRed else: fgYellow, s == "*"): 47 | stdout.write substr(code, toknizr.start, toknizr.length + toknizr.start - 1) 48 | of gtDecNumber..gtFloatNumber, gtValue: 49 | writeColored(fgGreen, true): 50 | stdout.write substr(code, toknizr.start, toknizr.length + toknizr.start - 1) 51 | of gtKeyword: 52 | writeColored(fgBlue, true): 53 | stdout.write substr(code, toknizr.start, toknizr.length + toknizr.start - 1) 54 | of gtComment, gtLongComment: 55 | writeColored(fgBlack, true): 56 | stdout.write substr(code, toknizr.start, toknizr.length + toknizr.start - 1) 57 | else: 58 | stdout.resetAttributes() 59 | stdout.write substr(code, toknizr.start, toknizr.length + toknizr.start - 1) 60 | 61 | proc cli( 62 | features: set[Feature] = { 63 | ReadonlyAttributes, MethodCallSyntax, 64 | ObjConstrRequired, NamespaceJsFieldBinding 65 | }, 66 | outputFile = "stdout", 67 | inputFile = "stdin", 68 | nep1 = true, 69 | exportCode = true, 70 | allowUndeclared = false, 71 | cliShowColor = true, 72 | cliOutFileListing = 10, 73 | optionalAttributePolicy = GenDeferredProcs 74 | ): string = 75 | var s = "" 76 | if inputFile == "stdin": 77 | writeColored(fgGreen, false): 78 | writeSep() 79 | writeCenter "Write webidl code: " 80 | writeSep() 81 | 82 | var enterCnt = 0 83 | while enterCnt < 2: 84 | let line = readLine(stdin) 85 | if line == "": 86 | inc enterCnt 87 | else: 88 | enterCnt = 0 89 | s.add line & "\n" 90 | 91 | else: 92 | for path in walkPattern(inputFile): 93 | let f = open(path) 94 | s.add readAll(f) 95 | f.close() 96 | 97 | let t = tokenize s 98 | let c = parseCode(t).stack.toSeq 99 | var tr {.used.} = Translator( 100 | settings: TranslatorSettings( 101 | optionalAttributePolicy: optionalAttributePolicy, 102 | features: features, 103 | exportCode: exportCode, 104 | onIdent: (node: NimUNode, isDecl: bool) => 105 | node 106 | .applyOn(nep1, (node: NimUNode) => nep1Rename(node, isDecl)) 107 | .keywordToAccQuoted() 108 | .applyOn(exportCode, makePublic) 109 | ), 110 | ) 111 | 112 | let outNode = tr.translate(c, allowUndeclared).assemble(tr.imports).to(PNode) 113 | let rendered = renderTree(outNode, {}) 114 | if outputFile == "stdout": 115 | writeColored(fgYellow, false): 116 | writeSep() 117 | writeCenter "Output Nim code: " 118 | writeSep() 119 | writeNimHighlight(rendered) 120 | # stdout.write rendered 121 | else: 122 | renderModule(outNode, outputFile, {}) 123 | writeColored(fgYellow, false): 124 | writeSep() 125 | writeCenter "Output Nim code successfully rendered into " & outputFile 126 | writeColored(fgYellow, true): 127 | if cliOutFileListing != 0: 128 | writeCenter "Small listing of this: " 129 | writeColored(fgYellow, false): 130 | writeSep() 131 | 132 | if cliOutFileListing != 0: 133 | var i = 0 134 | for line in splitLines(rendered): 135 | writeNimHighlight line 136 | stdout.write "\n" 137 | if i == cliOutFileListing: 138 | break 139 | inc i 140 | 141 | if i < countLines(rendered): 142 | stdout.writeLine "..." 143 | 144 | dispatch cli, help = { 145 | "features": "used webidl2nim features", 146 | "inputFile": "input file", 147 | "outputFile": "output file", 148 | "nep1": "rename idents, following nep1", 149 | "exportCode": "make generated code public", 150 | "allowUndeclared": "allow undeclared identifiers in webidl code" 151 | } 152 | -------------------------------------------------------------------------------- /src/webidl2nim/ast.nim: -------------------------------------------------------------------------------- 1 | type 2 | SpecialOperationKeyword* {.pure.} = enum 3 | Getter = "getter" 4 | Setter = "setter" 5 | Deleter = "deleter" 6 | 7 | NodeKind* {.pure.} = enum 8 | Empty 9 | Interface 10 | InterfaceMember 11 | Dictionary 12 | DictionaryMember 13 | Enum #Ident StrLit* 14 | Namespace 15 | NamespaceMember 16 | Mixin 17 | MixinMember 18 | 19 | Typedef 20 | Includes 21 | 22 | Ident 23 | Idents 24 | Generic 25 | Union 26 | 27 | IdentDefs#Ident [Type] StrLit | IntLit | FloatLit 28 | 29 | Type 30 | Partial 31 | Constructor 32 | Stringifier 33 | Static 34 | 35 | ConstStmt#IdentDefs 36 | Readonly 37 | Attribute 38 | Operation 39 | RegularOperation 40 | SpecialOperation 41 | Required 42 | 43 | Argument 44 | ArgumentList 45 | OptionalArgument 46 | SimpleArgument 47 | 48 | StrLit 49 | BoolLit 50 | IntLit 51 | FloatLit 52 | 53 | Ellipsis 54 | Special 55 | 56 | Iterable 57 | Maplike 58 | Setlike 59 | 60 | Inherit 61 | 62 | Async 63 | Callback 64 | 65 | Node* = object 66 | case kind*: NodeKind 67 | of Special: 68 | specOpKw*: SpecialOperationKeyword 69 | 70 | of Empty, Ellipsis: 71 | discard 72 | of FloatLit: 73 | floatVal*: float 74 | of IntLit: 75 | intVal*: int 76 | of BoolLit: 77 | boolVal*: bool 78 | of StrLit, Ident: 79 | strVal*: string 80 | else: 81 | sons*: seq[Node] 82 | 83 | const 84 | InheritancedDecls* = {Interface, Dictionary} 85 | 86 | proc cmp*(l, r: Node): bool= 87 | l.kind == r.kind and ( 88 | case l.kind: 89 | of Special: 90 | l.specOpKw == r.specOpKw 91 | of Empty, Ellipsis: 92 | true 93 | of FloatLit: 94 | l.floatVal == r.floatVal 95 | of IntLit: 96 | l.intVal == r.intVal 97 | of BoolLit: 98 | l.boolVal == r.boolVal 99 | of StrLit, Ident: 100 | l.strVal == r.strVal 101 | else: 102 | if l.sons.len != r.sons.len: 103 | return false 104 | var res = true 105 | for i in 0..l.sons.high: 106 | res = res and cmp(l.sons[i], r.sons[i]) 107 | res 108 | ) 109 | 110 | proc `==`*(l, r: Node): bool= cmp(l, r) 111 | 112 | func isEmpty*(n: Node): bool= 113 | n.kind == Empty 114 | 115 | func inner*(n: Node): Node= 116 | n.sons[0] 117 | 118 | func `[]`*(n: Node, i: int): Node= 119 | n.sons[i] 120 | 121 | func isVariardic*(n: Node): bool = 122 | case n.kind: 123 | of SimpleArgument: 124 | n[1].kind == Ellipsis 125 | of OptionalArgument: 126 | n.inner[2].isEmpty 127 | of Argument: 128 | n.inner.isVariardic 129 | of ArgumentList: 130 | var res = false 131 | for i in n.sons: 132 | res = res or i.isVariardic 133 | res 134 | of RegularOperation: 135 | n[2].isVariardic 136 | of SpecialOperation: 137 | n.inner.isVariardic 138 | of Operation: 139 | n.inner.isVariardic 140 | else: 141 | false 142 | 143 | 144 | proc name*(n: Node): Node= 145 | case n.kind: 146 | of Empty: n 147 | of Ident: n 148 | of RegularOperation: 149 | n.inner.name 150 | of SpecialOperation: 151 | n[1].name 152 | of Operation: 153 | n.inner.name 154 | else: n.inner.name#.strVal 155 | 156 | # proc `name=`*(n: sink Node, newN: Node)= 157 | # case n.kind: 158 | # of Empty, Ident: 159 | # n = newN 160 | # of RegularOperation: 161 | # n[0].name = newN 162 | # of SpecialOperation: 163 | # n[1].name = newN 164 | # of Operation: 165 | # n[0].name = newN 166 | # else: 167 | # discard#.strVal 168 | 169 | proc add*(self: var Node, other: Node): Node {.discardable.} = 170 | self.sons.add other 171 | 172 | proc skipNodes*(n: Node, kinds: set[NodeKind]): Node = 173 | result = n 174 | while result.kind in kinds: result = result.inner 175 | -------------------------------------------------------------------------------- /src/webidl2nim/ast_gen.nim: -------------------------------------------------------------------------------- 1 | import std/sequtils 2 | import std/sugar 3 | 4 | import ast 5 | 6 | 7 | func empty*(): Node= 8 | Node(kind: Empty) 9 | 10 | func strLit*(s: string): Node= 11 | Node(kind: StrLit, strVal: s) 12 | 13 | func boolLit*(b: bool): Node= 14 | Node(kind: BoolLit, boolVal: b) 15 | 16 | func intLit*(i: int): Node= 17 | Node(kind: IntLit, intVal: i) 18 | 19 | func floatLit*(f: float): Node= 20 | Node(kind: FloatLit, floatVal: f) 21 | 22 | func specialKeyword*(kw: SpecialOperationKeyword): Node= 23 | Node(kind: Special, specOpKw: kw) 24 | 25 | 26 | 27 | func ident*(name: string): Node = 28 | Node(kind: Ident, strVal: name) 29 | 30 | func idents*(nodes: openArray[Node] = @[]): Node = 31 | #unsigned long long 32 | assert nodes.all(x => x.kind == Ident) 33 | 34 | Node(kind: Idents, sons: nodes.toSeq) 35 | 36 | func generic*(base: Node, args: openArray[Node] = []): Node= 37 | assert base.kind == Ident 38 | assert args.all(x => x.kind == Type) 39 | 40 | Node(kind: Generic, sons: base & args.toSeq) 41 | 42 | func generic*(base: Node, arg: Node): Node= 43 | generic(base, [arg]) 44 | 45 | func union*(types: openArray[Node]): Node= 46 | assert types.all(x => x.kind == Type) 47 | 48 | Node(kind: Union, sons: types.toSeq) 49 | 50 | 51 | 52 | func typeStmt*(typeRest: Node; extendedAttributes = empty()): Node= 53 | assert typeRest.kind in {Ident, Idents, Generic, Union} 54 | assert extendedAttributes.kind in {Empty} 55 | 56 | Node(kind: Type, sons: @[typeRest, extendedAttributes]) 57 | 58 | 59 | func identDefs*(name, t: Node; default = empty()): Node= 60 | assert name.kind == Ident 61 | assert t.kind == Type 62 | assert default.kind in {Empty, IntLit, FloatLit, BoolLit, StrLit} 63 | 64 | Node(kind: IdentDefs, sons: @[name, t, default]) 65 | 66 | 67 | func constStmt*(identDefs: Node): Node= 68 | assert: 69 | identDefs.kind == IdentDefs and 70 | not identDefs[2].isEmpty 71 | 72 | Node(kind: ConstStmt, sons: @[identDefs]) 73 | 74 | func constructor*(args: Node): Node= 75 | assert args.kind == ArgumentList 76 | Node(kind: Constructor, sons: @[args]) 77 | 78 | func optionalArgument*(identDefs: Node): Node= 79 | assert identDefs.kind == IdentDefs 80 | Node(kind: OptionalArgument, sons: @[identDefs]) 81 | 82 | func ellipsis*(): Node= 83 | Node(kind: Ellipsis) 84 | 85 | func callback*(name, signature: Node): Node= 86 | assert name.kind == Ident 87 | assert: 88 | signature.kind == Operation and 89 | signature[0].kind == RegularOperation and 90 | signature[0][0].isEmpty 91 | 92 | Node(kind: Callback, sons: @[name, signature]) 93 | 94 | func simpleArgument*(identDefs: Node, ellipsis: Node = empty()): Node= 95 | assert identDefs.sons[2].isEmpty 96 | assert ellipsis.kind in {Ellipsis, Empty} 97 | 98 | Node(kind: SimpleArgument, sons: @[identDefs, ellipsis]) 99 | 100 | func argument*(arg: Node): Node= 101 | assert arg.kind in {OptionalArgument, SimpleArgument} 102 | Node(kind: Argument, sons: @[arg]) 103 | 104 | func argumentList*(args: openArray[Node]): Node= 105 | assert args.all(x => x.kind == Argument) 106 | Node(kind: ArgumentList, sons: args.toSeq) 107 | 108 | func regularOperation*(name, t, args: Node): Node= 109 | assert name.kind in {Ident, Empty} 110 | assert t.kind == Type 111 | assert args.kind == ArgumentList 112 | 113 | Node(kind: RegularOperation, sons: @[name, t, args]) 114 | 115 | func specialOperation*(op, spec: Node): Node= 116 | assert spec.kind == Special 117 | assert op.kind == RegularOperation 118 | 119 | Node(kind: SpecialOperation, sons: @[op, spec]) 120 | 121 | 122 | func operation*(op: Node): Node= 123 | assert op.kind in {RegularOperation, SpecialOperation} 124 | 125 | Node(kind: Operation, sons: @[op]) 126 | 127 | func attribute*(name, t: Node): Node= 128 | assert name.kind == Ident 129 | assert t.kind == Type 130 | 131 | Node(kind: Attribute, sons: @[name, t]) 132 | 133 | func staticStmt*(member: Node): Node= 134 | assert: 135 | member.kind in {Attribute, RegularOperation} or 136 | (member.kind == Readonly and member.sons[0].kind == Attribute) 137 | 138 | Node(kind: Static, sons: @[member]) 139 | 140 | func required*(identDefs: Node): Node= 141 | assert: 142 | identDefs.kind == IdentDefs and 143 | identDefs.sons[2].kind == Empty 144 | 145 | Node(kind: Required, sons: @[identDefs]) 146 | 147 | func readonly*(member: Node): Node= 148 | assert member.kind in {Attribute, Maplike, Setlike} 149 | 150 | Node(kind: Readonly, sons: @[member]) 151 | 152 | func enumStmt*(name: Node, members: openArray[Node]): Node= 153 | assert name.kind == Ident 154 | assert members.all(x => x.kind == StrLit) 155 | 156 | Node(kind: Enum, sons: name & members.toSeq) 157 | 158 | func typedef*(name, baseType: Node): Node= 159 | assert name.kind == Ident 160 | assert baseType.kind == Type 161 | 162 | Node(kind: Typedef, sons: @[name, baseType]) 163 | 164 | func includes*(dst, src: Node): Node= 165 | assert dst.kind == Ident 166 | assert src.kind == Ident 167 | 168 | Node(kind: Includes, sons: @[dst, src]) 169 | 170 | func namespaceMember*(member: Node): Node= 171 | assert: 172 | member.kind == ConstStmt or 173 | member.kind == Operation and member.sons[0].kind == RegularOperation or 174 | member.kind == Readonly and member.sons[0].kind == Attribute 175 | 176 | Node(kind: NamespaceMember, sons: @[member]) 177 | 178 | func namespace*(name: Node, members: openArray[Node]): Node= 179 | assert name.kind == Ident 180 | assert members.all(x => x.kind == NamespaceMember) 181 | 182 | Node(kind: Namespace, sons: name & members.toSeq) 183 | 184 | func dictionary*(name, inheritance: Node, 185 | members: openArray[Node]): Node= 186 | assert name.kind == Ident 187 | assert inheritance.kind in {Ident, Empty}, "Expected Ident or Empty, but got " & $inheritance.kind 188 | assert members.all(x => x.kind == DictionaryMember) 189 | 190 | Node(kind: Dictionary, sons: @[name, inheritance] & members.toSeq) 191 | 192 | func dictionaryMember*(member: Node): Node = 193 | assert member.kind in {Required, IdentDefs} 194 | Node(kind: DictionaryMember, sons: @[member]) 195 | 196 | proc mixinStmt*(name: Node, members: openArray[Node]): Node = 197 | assert members.all(x => x.kind == MixinMember) 198 | Node(kind: Mixin, sons: @[name] & members.toSeq) 199 | 200 | proc mixinMember*(member: Node): Node = 201 | assert: 202 | member.kind in {ConstStmt, Stringifier, Stringifier} or 203 | member.kind == Operation and member.inner.kind == RegularOperation or 204 | member.kind == Readonly and member.inner.kind == Attribute or 205 | member.kind == Attribute 206 | 207 | Node(kind: MixinMember, sons: @[member]) 208 | 209 | 210 | func stringifier*(attribute: Node): Node= 211 | assert: 212 | attribute.kind == Attribute or 213 | attribute.kind == Readonly and attribute.sons[0].kind == Attribute 214 | 215 | Node(kind: Stringifier, sons: @[attribute]) 216 | 217 | func stringifier*(): Node= 218 | Node(kind: Stringifier, sons: @[]) 219 | 220 | template keyValDecl(nodeKind: NodeKind, valueT: Node): Node= 221 | assert valueT.kind == Type 222 | 223 | Node(kind: nodeKind, sons: @[empty(), valueT]) 224 | 225 | template keyValDecl(nodeKind: NodeKind, keyT, valueT: Node): Node= 226 | assert valueT.kind == Type 227 | assert keyT.kind == Type 228 | 229 | Node(kind: nodeKind, sons: @[keyT, valueT]) 230 | 231 | func iterableStmt*(valueT: Node): Node= Iterable.keyValDecl(valueT) 232 | func iterableStmt*(keyT, valueT: Node): Node= Iterable.keyValDecl(keyT, valueT) 233 | 234 | func maplike*(keyT, valueT: Node): Node= Maplike.keyValDecl(keyT, valueT) 235 | func setlike*(valueT: Node): Node= Setlike.keyValDecl(valueT) 236 | 237 | func inherit*(attribute: Node): Node= 238 | Node(kind: Inherit, sons: @[attribute]) 239 | 240 | func interfaceStmt*(name: Node, inheritance: Node, 241 | members: openArray[Node]): Node= 242 | assert name.kind == Ident 243 | assert inheritance.kind in {Ident, Empty} 244 | assert members.all(x => x.kind == InterfaceMember) 245 | 246 | Node(kind: Interface, sons: @[name, inheritance] & members.toSeq) 247 | 248 | func interfaceMember*(member: Node): Node= 249 | #! it not support async iterable 250 | assert: 251 | member.kind in { 252 | ConstStmt, Operation, 253 | Stringifier, Static, 254 | Readonly, 255 | Iterable, Maplike, Setlike, 256 | Attribute, 257 | Inherit, 258 | Constructor, 259 | } 260 | 261 | Node(kind: InterfaceMember, sons: @[member]) 262 | 263 | func partial*(definition: Node): Node= 264 | assert: 265 | definition.kind == Namespace or 266 | (definition.kind == Interface and 267 | definition.sons[2..^1].all(x => x.kind != Constructor)) or 268 | (definition.kind == Dictionary and definition.sons[1].isEmpty) or 269 | definition.kind == Mixin 270 | 271 | 272 | Node(kind: Partial, sons: @[definition]) 273 | -------------------------------------------------------------------------------- /src/webidl2nim/ast_repr.nim: -------------------------------------------------------------------------------- 1 | import std/strformat 2 | import std/[sequtils, strutils, sugar] 3 | 4 | import ast 5 | 6 | proc `$`*(node: Node): string= 7 | case node.kind: 8 | of Empty: "" 9 | of Ellipsis: "..." 10 | of Special: $node.specOpKw 11 | of Ident: node.strVal 12 | of StrLit: '"' & node.strVal & '"' 13 | of IntLit: $node.intVal 14 | of FloatLit: $node.floatVal 15 | of BoolLit: $node.boolVal 16 | 17 | of Type: fmt"{$node.sons[1]}{$node.sons[0]}" 18 | 19 | of IdentDefs: 20 | fmt"{$node.sons[1]} {$node.sons[0]}" & ( 21 | if not node.sons[2].isEmpty: 22 | fmt" = {node.sons[2]}" 23 | else: 24 | "" 25 | ) 26 | 27 | of Includes: 28 | fmt"{$node.sons[0]} includes {$node.sons[1]};" 29 | 30 | of Idents: 31 | node.sons.map(x => $x).join(" ") 32 | 33 | of Generic: 34 | $node[0] & "<" & node.sons[1..^1].map(x => $x).join(", ") & ">" 35 | 36 | of Iterable, Maplike, SetLike: 37 | if node[0].isEmpty: 38 | ($node.kind).toLower & "<" & $node.sons[1] & ">" 39 | else: 40 | ($node.kind).toLower & "<" & node.sons[0..1].map(x => $x).join(", ") & ">" 41 | 42 | of Inherit: 43 | fmt"inherit {$node[0]}" 44 | 45 | of Union: 46 | '(' & node.sons.map(x => $x).join(" or ") & ')' 47 | 48 | of Attribute: 49 | fmt"attribute {$node.sons[1]} {$node.sons[0]}" 50 | 51 | of Required: 52 | fmt"required {$node.sons[0]}" 53 | 54 | of Partial: 55 | fmt"partial {$node.sons[0]}" 56 | 57 | of Readonly: 58 | fmt"readonly {$node.sons[0]}" 59 | 60 | of Static: 61 | fmt"static {$node.sons[0]}" 62 | 63 | of Typedef: 64 | fmt"typedef {$node.sons[1]} {$node.sons[0]};" 65 | 66 | of OptionalArgument: 67 | fmt"optional {$node.sons[0]}" 68 | 69 | of Stringifier: 70 | fmt"stringifier {$node.sons[0]}" 71 | 72 | of ConstStmt: 73 | fmt"const {$node.sons[0]}" 74 | 75 | of SimpleArgument: 76 | $node[0][1] & ( 77 | if not node[1].isEmpty: 78 | $node[1] 79 | else: 80 | "" 81 | ) & ' ' & 82 | $node[0][0] 83 | 84 | of Argument: 85 | $node.sons[0] 86 | 87 | of Callback: 88 | fmt"callback {$node[0]} = {$node[1]}" 89 | 90 | of RegularOperation: 91 | fmt"{$node.sons[1]}" & ( 92 | if not node.sons[0].isEmpty: 93 | " " & $node.sons[0] 94 | else: 95 | " " 96 | ) & '(' & $node.sons[2] & ')' 97 | 98 | of SpecialOperation: 99 | fmt"{$node.sons[1]} {$node.sons[0]}" 100 | 101 | of Operation: 102 | $node.sons[0] 103 | 104 | of ArgumentList: 105 | $node.sons.map(x => $x).join(", ") 106 | 107 | of Enum: 108 | fmt"enum {$node.sons[0]} " & '{' & '\n' & 109 | node.sons[1..^1].map(x => indent($x, 2)).join(",\n") & 110 | "\n};" 111 | 112 | of Namespace: 113 | fmt"namespace {$node.sons[0]} " & '{' & '\n' & 114 | node.sons[1..^1].map(x => indent($x, 2) & ";").join("\n") & 115 | "\n};" 116 | 117 | of NamespaceMember, InterfaceMember, DictionaryMember: 118 | $node[0] 119 | 120 | of Dictionary: 121 | fmt"dictionary {$node.sons[0]}" & ( 122 | if not node.sons[1].isEmpty: 123 | fmt": {$node.sons[1]} " 124 | else: 125 | " " 126 | ) & 127 | '{' & '\n' & 128 | node.sons[2..^1].map(x => indent($x, 2) & ";").join("\n") & 129 | "\n};" 130 | 131 | of Interface: 132 | fmt"interface {$node.sons[0]}" & ( 133 | if not node.sons[1].isEmpty: 134 | fmt": {$node.sons[1]} " 135 | else: 136 | " " 137 | ) & 138 | '{' & '\n' & 139 | node.sons[2..^1].map(x => indent($x, 2) & ";").join("\n") & 140 | "\n};" 141 | 142 | else: 143 | "" 144 | 145 | when isMainModule: 146 | import ast_gen 147 | 148 | echo typedef( 149 | ident"GPUBufferUsageFlags", 150 | typeStmt(idents([ident"unsigned", ident"long"]), empty()) 151 | ) 152 | 153 | echo typedef( 154 | ident"GPUBindingResource", 155 | typeStmt( 156 | union [ident"GPUSampler".typeStmt, ident"GPUTextureView".typeStmt, ident"GPUBufferBinding".typeStmt], 157 | ) 158 | ) 159 | echo interfaceStmt( 160 | ident"A", 161 | ident"B", 162 | [constStmt(identDefs(ident"test", typeStmt(ident"string")))] 163 | ) 164 | -------------------------------------------------------------------------------- /src/webidl2nim/deps.nim: -------------------------------------------------------------------------------- 1 | import options 2 | import std/[sets, tables] 3 | import ast 4 | import tokens 5 | 6 | type 7 | DeclDeps*[S] = object 8 | inheritance*: Option[S] 9 | usedTypes: HashSet[S] 10 | 11 | partialMembers*: seq[Node] #Fields from partial decls 12 | mixinMembers*: seq[Node] 13 | includes*: HashSet[S] 14 | 15 | DepsFinder* = ref object 16 | deps*: Table[string, DeclDeps[string]] 17 | skipUndeclared*: bool 18 | containsProcs*: seq[proc (n: Node): bool {.noSideEffect.}] 19 | 20 | proc init*( 21 | _: type DepsFinder, 22 | skipUndeclared: bool = true, 23 | containsProcs: seq[proc (n: Node): bool {.noSideEffect.}] = @[] 24 | ): auto = 25 | DepsFinder( 26 | deps: initTable[string, DeclDeps[string]](), 27 | skipUndeclared: skipUndeclared, 28 | containsProcs: containsProcs 29 | ) 30 | 31 | using self: DepsFinder 32 | 33 | proc tryInitDeps(self; ident: Node)= 34 | assert ident.kind == Ident 35 | let s = ident.strVal 36 | if s notin self.deps: 37 | self.deps[s] = DeclDeps[string].default 38 | 39 | proc countDeps*(self; s: string; countTable: var CountTable[string]) = 40 | # quite expensive operation, maybe better to use recursion ? 41 | countTable.inc s 42 | var deps = @[self.deps[s]] 43 | var globalUsed = HashSet[string].default 44 | while deps.len > 0: 45 | let i = deps.pop() 46 | var used = i.includes + i.usedTypes 47 | if i.inheritance.isSome: 48 | used.incl i.inheritance.get() 49 | 50 | used = used - globalUsed 51 | globalUsed.incl used 52 | 53 | while used.len > 0: 54 | var nextDecl = used.pop() 55 | if nextDecl in self.deps: 56 | deps.add self.deps[nextDecl] 57 | elif not self.skipUndeclared: 58 | raise newException(CatchableError, "Identifier " & nextDecl & " not found") 59 | 60 | countTable.inc s 61 | 62 | 63 | proc setInheritance(deps: var DeclDeps[string], inheritance: Node)= 64 | deps.inheritance = 65 | if inheritance.kind == Empty: 66 | none(string) 67 | else: 68 | some(inheritance.strVal) 69 | 70 | proc updateUsedTypes( 71 | node: Node, 72 | deps: var DeclDeps[string], 73 | containsProcs: seq[proc (n: Node): bool {.noSideEffect.}] 74 | ) = 75 | template updateUsedTypes(node: Node, deps: var DeclDeps[string]): untyped = 76 | updateUsedTypes(node, deps, containsProcs) 77 | 78 | case node.kind: 79 | of Operation: 80 | let op = 81 | if node.inner.kind == RegularOperation: 82 | node.inner 83 | else: 84 | node.inner[0] 85 | 86 | assert op.kind == RegularOperation 87 | updateUsedTypes(op[1], deps) 88 | updateUsedTypes(op[2], deps) 89 | 90 | of ConstStmt: 91 | updateUsedTypes(node.inner, deps) 92 | 93 | of Attribute: 94 | updateUsedTypes(node[1], deps) 95 | 96 | of Readonly: 97 | if (let attribute = node.inner; attribute).kind == Attribute: 98 | updateUsedTypes(attribute, deps) 99 | 100 | of Setlike: 101 | updateUsedTypes(node[1], deps) 102 | 103 | of Maplike: 104 | updateUsedTypes(node[0], deps) 105 | updateUsedTypes(node[1], deps) 106 | 107 | of ArgumentList: 108 | for i in node.sons: 109 | updateUsedTypes(i, deps) 110 | 111 | of Argument: 112 | updateUsedTypes(node.inner[0], deps) 113 | 114 | of IdentDefs: 115 | updateUsedTypes(node[1], deps) 116 | 117 | of Type: 118 | if (let i = node.inner; i).kind == Ident: 119 | block usedTypes: 120 | if i.strVal in keywordNames: 121 | break usedTypes 122 | 123 | for contains in containsProcs: 124 | if contains(i): 125 | break usedTypes 126 | 127 | deps.usedTypes.incl i.strVal 128 | 129 | else: 130 | for i in node.sons: 131 | updateUsedTypes(i, deps) 132 | 133 | of Required: 134 | updateUsedTypes(node.inner, deps) 135 | 136 | of Union: 137 | for i in node.sons: 138 | updateUsedTypes(i, deps) 139 | 140 | else: 141 | discard 142 | 143 | 144 | proc findInterfaceLikeDeps(self; node: Node, fromPartial = false)= 145 | assert node.kind in {Interface, Dictionary, Namespace, Mixin} 146 | self.tryInitDeps(node[0]) 147 | 148 | if node.kind in {Interface, Dictionary} and not fromPartial: 149 | self.deps[node[0].strVal].setInheritance(node[1]) 150 | 151 | let startIdx = 152 | if node.kind in {Interface, Dictionary}: 153 | 2 154 | else: 155 | 1 156 | 157 | for i in node.sons[startIdx..^1]: 158 | updateUsedTypes( 159 | i[0], 160 | self.deps[node[0].strVal], 161 | self.containsProcs 162 | ) 163 | 164 | proc findDeps*(self; node: Node)= 165 | case node.kind: 166 | of Interface, Dictionary, Namespace: 167 | findInterfaceLikeDeps(self, node) 168 | of Mixin: 169 | findInterfaceLikeDeps(self, node) 170 | self.deps[node[0].strVal].mixinMembers &= node.sons[1..^1] 171 | of Typedef: 172 | self.tryInitDeps(node.name) 173 | updateUsedTypes( 174 | node[1], 175 | self.deps[node[0].strVal], 176 | self.containsProcs 177 | ) 178 | of Enum: 179 | self.tryInitDeps(node.name) 180 | of Includes: 181 | # node[1] must be mixin 182 | self.tryInitDeps(node[0]) 183 | self.deps[node[0].strVal].includes.incl node[1].strVal 184 | of Partial: 185 | # print self.deps[node.inner[0].strVal] 186 | findInterfaceLikeDeps(self, node.inner, true) 187 | # print self.deps[node.inner[0].strVal] 188 | 189 | let membersStartIdx = 190 | if node.inner.kind in {Interface, Dictionary}: 191 | 2 192 | else: 193 | 1 194 | 195 | self.deps[node.inner[0].strVal].partialMembers &= 196 | node.inner.sons[membersStartIdx..^1] 197 | # print self.deps[node.inner[0].strVal] 198 | of Callback: 199 | case (let i = node[1]; i).kind: 200 | of Operation: 201 | self.tryInitDeps(node.name) 202 | updateUsedTypes(i, self.deps[node.name.strVal], self.containsProcs) 203 | else: 204 | discard 205 | else: 206 | discard 207 | 208 | when isMainModule: 209 | import lexer 210 | import parser 211 | import deques, sequtils 212 | 213 | var t = tokenize""" 214 | interface mixin GPUObjectBase { 215 | attribute USVString label; 216 | }; 217 | 218 | partial interface mixin GPUObjectBase { 219 | attribute USVString labelavd; 220 | }; 221 | 222 | 223 | interface Base { 224 | attribute USVString test; 225 | }; 226 | 227 | interface mixin GPUObjectBase2 { 228 | attribute USVString labeled_test; 229 | }; 230 | 231 | interface GPUObjectBaseLol: Base { 232 | attribute USVString labelab; 233 | }; 234 | 235 | partial interface GPUObjectBaseLol { 236 | attribute USVString PI; 237 | }; 238 | 239 | GPUObjectBaseLol includes GPUObjectBase; 240 | GPUObjectBaseLol includes GPUObjectBase2; 241 | 242 | """ 243 | t = tokenize""" 244 | typedef unsigned long GPUBufferDynamicOffset; 245 | """ 246 | 247 | var c = parseCode(t).stack.toSeq 248 | echo t.len 249 | var finder = DepsFinder( 250 | deps: initTable[string, DeclDeps[string]]() 251 | ) 252 | for i in c: 253 | finder.findDeps(i) 254 | 255 | var ct = initCountTable[string]() 256 | for i in finder.deps.keys: 257 | finder.countDeps(i, ct) 258 | echo ct 259 | -------------------------------------------------------------------------------- /src/webidl2nim/grammar.nim: -------------------------------------------------------------------------------- 1 | {.used.} 2 | import pkg/npeg 3 | 4 | grammar definitions: 5 | Definitions <- 6 | *(extended_attributes.ExtendedAttributeList * Definition) 7 | Definition <- 8 | callback.Callback | 9 | interfaces.Interface | 10 | partial.Partial | 11 | namespace.Namespace | 12 | 13 | dictionary.Dictionary | 14 | enums.Enum | 15 | typedef.Typedef | 16 | includes.IncludesStatement# | 17 | 18 | 19 | grammar extended_attributes: 20 | ExtendedAttributeList <- ?( 21 | [tLBracket] * ExtendedAttribute * *([tComma] * ExtendedAttribute) * [tRBracket] 22 | ) 23 | 24 | ExtendedAttribute <- 25 | ([tLPar] * ?ExtendedAttributeInner * [tRPar] * ?ExtendedAttribute) | 26 | ([tLBracket] * ?ExtendedAttributeInner * [tRBracket] * ?ExtendedAttribute) | 27 | ([tLCurly] * ?ExtendedAttributeInner * [tRCurly] * ?ExtendedAttribute) | 28 | Other * ?ExtendedAttribute 29 | 30 | ExtendedAttributeInner <- 31 | ([tLPar] * ?ExtendedAttributeInner * [tRPar] * ?ExtendedAttributeInner) | 32 | ([tLBracket] * ?ExtendedAttributeInner * [tRBracket] * ?ExtendedAttributeInner) | 33 | ([tLCurly] * ?ExtendedAttributeInner * [tRBracket] * ?ExtendedAttributeInner) | 34 | OtherOrComma * ?ExtendedAttributeInner 35 | 36 | Other <- 37 | [tInteger] | 38 | [tDecimal] | 39 | [tIdentifier] | 40 | [tString] | 41 | [tOther] | 42 | [tColon] | 43 | [tSemiColon] | 44 | [tDot] | 45 | [tEllipsis] | #TODO: add * 46 | [tLess] | 47 | [tAssign] | 48 | [tMore] | 49 | [tQuestion] 50 | 51 | 52 | OtherOrComma <- (Other | [tComma]) 53 | 54 | grammar types: 55 | Type <- ( 56 | SingleType | 57 | (UnionType * Null) 58 | ): 59 | capture typeStmt(p.pop()) 60 | 61 | TypeWithExtendedAttributes <- 62 | extended_attributes.ExtendedAttributeList * Type 63 | 64 | SingleType <- ( 65 | DistinguishableType | 66 | >[tAny] | 67 | PromiseType 68 | ): 69 | if capture.len > 1: 70 | capture ident($ $1) 71 | 72 | UnionType <- ( 73 | [tLPar] * UnionMemberType * 74 | [tOr] * 75 | UnionMemberType * *([tOr] * UnionMemberType) * [tRPar] 76 | ): capture union(popSameKindNodes(Type)) 77 | 78 | UnionMemberType <- ( 79 | (extended_attributes.ExtendedAttributeList * DistinguishableType) | 80 | UnionType * Null 81 | ): capture typeStmt(p.pop()) 82 | 83 | DistinguishableType <- ( 84 | PrimitiveType | 85 | StringType | 86 | BufferRelatedType | 87 | DistinguishableTypeGenerics | 88 | RecordType | 89 | >[tIdentifier] | 90 | >[tObject] | 91 | >[tSymbol] | 92 | >[tUndefined] 93 | ) * Null: 94 | if capture.len > 1: 95 | capture ident($ $1) 96 | 97 | DistinguishableTypeGenerics <- ( 98 | (>[tSequence] * [tLess] * TypeWithExtendedAttributes * [tMore]) | 99 | (>[tFrozenArray] * [tLess] * TypeWithExtendedAttributes * [tMore]) | 100 | (>[tObservableArray] * [tLess] * TypeWithExtendedAttributes * [tMore]) 101 | ): 102 | capture generic( 103 | ident($ $1), 104 | p.pop() 105 | ) 106 | 107 | PrimitiveType <- ( 108 | UnsignedIntegerType | 109 | UnrestrictedFloatType | 110 | >[tBoolean] | 111 | >[tByte] | 112 | >[tOctet] | 113 | >[tBigint] 114 | ): 115 | if capture.len > 1: 116 | capture ident($ $1) 117 | 118 | UnrestrictedFloatType <- ( 119 | (>[tUnrestricted] * FloatType) | 120 | FloatType 121 | ): genIdents(capture) 122 | 123 | FloatType <- >([tFloat] | [tDouble]) 124 | 125 | UnsignedIntegerType <- ( 126 | (>[tUnsigned] * IntegerType) | 127 | IntegerType 128 | ): genIdents(capture) 129 | 130 | IntegerType <- 131 | >[tShort] | 132 | (>[tLong] * ?>[tLong]) 133 | 134 | StringType <- ( 135 | >[tByteString] | 136 | >[tDOMString] | 137 | >[tUSVString] 138 | ): capture ident($ $1) 139 | 140 | PromiseType <- ( 141 | >[tPromise] * [tLess] * Type * [tMore] 142 | ): 143 | capture generic( 144 | ident($ $1), 145 | p.pop() 146 | ) 147 | 148 | RecordType <- ( 149 | >[tRecord] * [tLess] * StringType * [tComma] * TypeWithExtendedAttributes * [tMore] 150 | ): 151 | let 152 | t2 = p.pop() 153 | t1 = typeStmt p.pop() #StringType is ident 154 | 155 | capture generic( 156 | ident($ $1), 157 | [t1, t2] 158 | ) 159 | 160 | BufferRelatedType <- >( 161 | [tArrayBuffer] | 162 | [tDataView] | 163 | [tInt8Array] | 164 | [tInt16Array] | 165 | [tInt32Array] | 166 | [tUint8Array] | 167 | [tUint16Array] | 168 | [tUint32Array] | 169 | [tUint8ClampedArray] | 170 | [tBigInt64Array] | 171 | [tBigUint64Array] | 172 | [tFloat32Array] | 173 | [tFloat64Array] 174 | ): capture ident($ $1) 175 | 176 | Null <- ?[tQuestion] 177 | 178 | 179 | grammar constants: 180 | Const <- ( 181 | [tConst] * ConstType * >[tIdentifier] * [tAssign] * 182 | ConstValue * ([tSemiColon] | E"; after const decl not found") 183 | ): 184 | let val = p.pop() 185 | capture constStmt identDefs( 186 | ident ($1).strVal, 187 | p.pop(), 188 | val 189 | ) 190 | 191 | ConstValue <- ( 192 | BooleanLiteral | 193 | FloatLiteral | 194 | >[tInteger] 195 | ): 196 | if capture.len > 1: 197 | capture intLit(($1).intVal) 198 | 199 | BooleanLiteral <- >([tTrue] | [tFalse]): 200 | capture boolLit( 201 | case ($1).kind: 202 | of tTrue: 203 | true 204 | of tFalse: 205 | false 206 | else: 207 | raise newException(CatchableError, "Non bool tokens in boolLit") 208 | ) 209 | 210 | FloatLiteral <- >( 211 | [tDecimal] | 212 | [tMinusInfinity] | 213 | [tInfinity] | 214 | [tNaN] 215 | ): 216 | capture floatLit( 217 | case ($1).kind: 218 | of tDecimal: 219 | ($1).floatVal 220 | of tMinusInfinity: 221 | NegInf 222 | of tInfinity: 223 | Inf 224 | of tNaN: 225 | NaN 226 | else: 227 | raise newException(CatchableError, "Non float tokens in floatLit") 228 | ) 229 | 230 | ConstType <- ( 231 | types.PrimitiveType | 232 | ConstTypeIdent 233 | ): capture typeStmt(p.pop()) 234 | 235 | 236 | ConstTypeIdent <- >[tIdentifier]: 237 | capture ident($ $1) 238 | 239 | grammar arguments: 240 | ArgumentList <- ?(Argument * *([tComma] * Argument)): 241 | capture argumentList(popSameKindNodes(Argument)) 242 | 243 | Argument <- extended_attributes.ExtendedAttributeList * ArgumentRest: 244 | capture argument(p.pop()) 245 | 246 | ArgumentRest <- 247 | SimpleArgument | 248 | OptionalArgument 249 | 250 | OptionalArgument <- ( 251 | [tOptional] * types.TypeWithExtendedAttributes * 252 | ArgumentName * default.Default 253 | ): 254 | let default = 255 | if (%1).kind == Type: 256 | empty() 257 | else: 258 | p.pop() 259 | 260 | capture optionalArgument( 261 | identDefs( 262 | ident($ $1), 263 | p.pop(), 264 | default 265 | ), 266 | ) 267 | 268 | SimpleArgument <- (types.Type * ?>[tEllipsis] * ArgumentName): 269 | let 270 | isVariardic = capture.len > 2 271 | nameToken = 272 | if isVariardic: 273 | $2 274 | else: 275 | $1 276 | 277 | #TODO: transform variardic(with ...) to nim varargs 278 | 279 | capture simpleArgument( 280 | identDefs( 281 | ident($nameToken), 282 | p.pop() 283 | ), 284 | 285 | if isVariardic: 286 | ellipsis() 287 | else: 288 | empty() 289 | ) 290 | 291 | ArgumentName <- 292 | ArgumentNameKeyword | 293 | >[tIdentifier] 294 | 295 | 296 | ArgumentNameKeyword <- >( 297 | [tAsync] | 298 | [tAttribute] | 299 | [tCallback] | 300 | [tConst] | 301 | [tConstructor] | 302 | [tDeleter] | 303 | [tDictionary] | 304 | [tEnum] | 305 | [tGetter] | 306 | [tIncludes] | 307 | [tInherit] | 308 | [tInterface] | 309 | [tIterable] | 310 | [tMaplike] | 311 | [tMixin] | 312 | [tNamespace] | 313 | [tPartial] | 314 | [tReadonly] | 315 | [tRequired] | 316 | [tSetlike] | 317 | [tSetter] | 318 | [tStatic] | 319 | [tStringifier] | 320 | [tTypedef] | 321 | [tUnrestricted] 322 | ) 323 | 324 | 325 | 326 | 327 | grammar attributes: 328 | ReadWriteAttribute <- AttributeRest 329 | InheritAttribute <- [tInherit] * AttributeRest: 330 | capture inherit(p.pop()) 331 | 332 | OptionalReadOnlyAttribute <- ReadOnlyAttribute | AttributeRest 333 | ReadOnlyAttribute <- [tReadonly] * AttributeRest: 334 | capture readonly(p.pop()) 335 | 336 | AttributeRest <- [tAttribute] * types.TypeWithExtendedAttributes * AttributeName * [tSemiColon]: 337 | capture attribute(ident($ $1), p.pop()) 338 | 339 | AttributeName <- AttributeNameKeyword | >[tIdentifier] 340 | AttributeNameKeyword <- >( 341 | [tAsync] | 342 | [tRequired] 343 | ) 344 | 345 | grammar namespace: 346 | Namespace <- ( 347 | [tNamespace] * >[tIdentifier] * [tLCurly] * 348 | NamespaceMembers * 349 | [tRCurly] * [tSemiColon] 350 | ): 351 | capture namespace( 352 | ident ($1).strVal, 353 | popSameKindNodes(NamespaceMember) 354 | ) 355 | 356 | NamespaceMembers <- 357 | *(extended_attributes.ExtendedAttributeList * NamespaceMember) 358 | 359 | NamespaceMember <- ( 360 | operations.RegularOperation | 361 | ReadonlyAttribute | 362 | constants.Const 363 | ): 364 | var node = p.pop() 365 | if node.kind == RegularOperation: 366 | node = operation(node) 367 | 368 | capture namespaceMember(node) 369 | 370 | ReadonlyAttribute <- [tReadonly] * attributes.AttributeRest: 371 | capture readonly(p.pop()) 372 | 373 | 374 | grammar maplike: 375 | ReadWriteMaplike <- MaplikeRest 376 | MaplikeRest <- ( 377 | [tMaplike] * 378 | [tLess] * types.TypeWithExtendedAttributes * 379 | [tComma] * 380 | types.TypeWithExtendedAttributes * [tMore] * [tSemiColon] 381 | ): 382 | let 383 | valT = p.pop() 384 | keyT = p.pop() 385 | 386 | capture maplike(keyT, valT) 387 | 388 | grammar setlike: 389 | ReadWriteSetlike <- SetlikeRest 390 | SetlikeRest <- 391 | [tSetlike] * [tLess] * types.TypeWithExtendedAttributes * [tMore] * [tSemiColon]: 392 | capture setlike(p.pop()) 393 | 394 | grammar readonly: 395 | ReadOnlyMember <- [tReadonly] * ReadOnlyMemberRest: 396 | capture readonly(p.pop()) 397 | 398 | ReadOnlyMemberRest <- 399 | attributes.AttributeRest | 400 | maplike.MaplikeRest | 401 | setlike.SetlikeRest 402 | 403 | grammar stringifier: 404 | Stringifier <- CustomStringifier | StringifierWithAttribute 405 | 406 | CustomStringifier <- [tStringifier] * [tSemiColon]: 407 | capture stringifier() 408 | StringifierWithAttribute <- [tStringifier] * attributes.OptionalReadOnlyAttribute: 409 | capture stringifier(p.pop()) 410 | 411 | grammar static_member: 412 | StaticMember <- [tStatic] * StaticMemberRest 413 | StaticMemberRest <- ( 414 | attributes.OptionalReadOnlyAttribute | 415 | operations.RegularOperation 416 | ): capture staticStmt(p.pop()) 417 | 418 | grammar iterable: 419 | #! iterable implemented as generic 420 | Iterable <- IterableRest * [tSemiColon]: 421 | capture: 422 | if p.stack.len > 1 and (%2).kind == Type: 423 | let 424 | valT = p.pop() 425 | keyT = p.pop() 426 | 427 | iterableStmt(keyT, valT) 428 | else: 429 | iterableStmt(p.pop()) 430 | 431 | IterableRest <- [tIterable] * [tLess] * 432 | types.TypeWithExtendedAttributes * *([tComma] * types.TypeWithExtendedAttributes) * 433 | [tMore] 434 | 435 | grammar async_iterable: 436 | AsyncIterable <- [tAsync] * iterable.Iterable * ?([tLPar] * arguments.ArgumentList * [tRPar]) 437 | 438 | grammar constructor: 439 | Constructor <- [tConstructor] * [tLPar] * arguments.ArgumentList * [tRPar] * [tSemiColon]: 440 | capture constructor(p.pop()) 441 | 442 | grammar operations: 443 | Operation <- ( 444 | RegularOperation | 445 | SpecialOperation 446 | ): capture operation(p.pop()) 447 | 448 | RegularOperation <- types.Type * OperationRest: 449 | var 450 | args = p.pop() 451 | name = p.pop() 452 | t = p.pop() 453 | 454 | capture regularOperation( 455 | name, 456 | t, args 457 | ) 458 | 459 | SpecialOperation <- Special * RegularOperation: 460 | capture specialOperation( 461 | p.pop(), 462 | specialKeyword( 463 | case ($1).kind: 464 | of tGetter: 465 | Getter 466 | of tSetter: 467 | Setter 468 | of tDeleter: 469 | Deleter 470 | else: 471 | raise newException(CatchableError, "Invalid keyword in special") 472 | ) 473 | ) 474 | 475 | Special <- >([tGetter] | [tSetter] | [tDeleter]) 476 | 477 | OperationRest <- 478 | OperationName * [tLPar] * arguments.ArgumentList * [tRPar] * [tSemiColon] 479 | 480 | OperationName <- ?( 481 | OperationNameKeyword | 482 | >[tIdentifier] 483 | ): 484 | capture: 485 | if capture.len > 1: 486 | ident($ $1) 487 | else: 488 | empty() 489 | 490 | OperationNameKeyword <- 491 | >[tIncludes] 492 | 493 | 494 | 495 | grammar inheritance: 496 | Inheritance <- ?InheritanceRest: 497 | capture: 498 | if capture.len > 1: 499 | ident($ $1) 500 | else: 501 | empty() 502 | 503 | InheritanceRest <- [tColon] * >[tIdentifier] 504 | 505 | grammar default: 506 | Default <- ?([tAssign] * DefaultValue) 507 | DefaultValue <- 508 | constants.ConstValue | 509 | DefaultString | 510 | ([tLBracket] * [tRBracket]) | 511 | ([tLCurly] * [tRCurly]) | 512 | [tNull] | 513 | [tUndefined] 514 | 515 | DefaultString <- >[tString]: 516 | capture strLit ($1).strVal 517 | 518 | grammar dictionary: 519 | PartialDictionary <- ([tDictionary] * >[tIdentifier] * [tLCurly] * DictionaryMembers * [tRCurly] * [tSemiColon]): 520 | capture dictionary( 521 | ident($ $1), 522 | empty(), 523 | popSameKindNodes(DictionaryMember) 524 | ) 525 | 526 | Dictionary <- ([tDictionary] * >[tIdentifier] * inheritance.Inheritance * [tLCurly] * DictionaryMembers * [tRCurly] * [tSemiColon]): 527 | let members = popSameKindNodes(DictionaryMember) 528 | var inheritance = p.pop() 529 | if inheritance.kind == Type: inheritance = inheritance.inner 530 | 531 | capture dictionary( 532 | ident($ $1), 533 | inheritance, 534 | members 535 | ) 536 | 537 | DictionaryMembers <- *(extended_attributes.ExtendedAttributeList * MemberRest) 538 | MemberRest <- ( 539 | RequiredMember | 540 | TypeMember 541 | ): capture dictionaryMember(p.pop()) 542 | 543 | TypeMember <- types.Type * >[tIdentifier] * default.Default * [tSemiColon]: 544 | let (default, t) = 545 | if (%1).kind == Type: 546 | # no default val 547 | (empty(), p.pop()) 548 | else: 549 | (p.pop(), p.pop()) 550 | 551 | capture identDefs(ident($ $1), t, default) 552 | RequiredMember <- 553 | ([tRequired] * types.TypeWithExtendedAttributes * >[tIdentifier] * [tSemiColon]): 554 | capture required identDefs(ident($ $1), p.pop()) 555 | 556 | grammar enums: 557 | Enum <- ( 558 | [tEnum] * >[tIdentifier] * [tLCurly] * EnumValueList * [tRCurly] * [tSemiColon] 559 | ): 560 | let members = 561 | collect: 562 | for i in 2 ..< capture.len: 563 | strLit capture[i].s.strVal 564 | 565 | capture enumStmt( 566 | ident ($1).strVal, 567 | members 568 | ) 569 | 570 | # Name <- >[tIdentifier] 571 | EnumValueList <- >[tString] * *([tComma] * >[tString]) * ?[tComma]#{"str1", "str2"} 572 | 573 | grammar typedef: 574 | Typedef <- ( 575 | [tTypedef] * types.TypeWithExtendedAttributes * >[tIdentifier] * [tSemiColon] 576 | ): capture typedef(ident ($1).strVal, p.pop()) 577 | 578 | grammar includes: 579 | IncludesStatement <- >[tIdentifier] * [tIncludes] * >[tIdentifier] * [tSemiColon]: 580 | capture includes(ident($ $1), ident($ $2)) 581 | 582 | grammar mixins: 583 | MixinRest <- [tMixin] * >[tIdentifier] * [tLCurly] * MixinMembers * [tRCurly] * [tSemiColon]: 584 | capture mixinStmt(ident($ $1), popSameKindNodes(MixinMember)) 585 | 586 | MixinMembers <- *(extended_attributes.ExtendedAttributeList * MixinMember) 587 | MixinMember <- ( 588 | constants.Const | 589 | operations.RegularOperation | 590 | stringifier.Stringifier | 591 | (?[tReadonly] * attributes.AttributeRest) 592 | ): 593 | let member = 594 | if (%1).kind == RegularOperation: 595 | operation p.pop() 596 | else: 597 | p.pop() 598 | 599 | capture mixinMember(member) 600 | 601 | 602 | grammar partial: 603 | Partial <- [tPartial] * PartialDefinition 604 | PartialDefinition <- ( 605 | ([tInterface] * PartialInterfaceOrPartialMixin) | 606 | dictionary.PartialDictionary | 607 | namespace.Namespace 608 | ): capture partial(p.pop()) 609 | 610 | PartialInterfaceOrPartialMixin <- 611 | PartialInterfaceRest | 612 | mixins.MixinRest 613 | 614 | 615 | PartialInterfaceRest <- 616 | (>[tIdentifier] * [tLCurly] * PartialInterfaceMembers * [tRCurly] * [tSemiColon]): 617 | capture interfaceStmt( 618 | ident($ $1), 619 | empty(), 620 | popSameKindNodes(InterfaceMember) 621 | ) 622 | 623 | PartialInterfaceMembers <- 624 | *(extended_attributes.ExtendedAttributeList * PartialInterfaceMember) 625 | 626 | PartialInterfaceMember <- ( 627 | constants.Const | 628 | operations.Operation | 629 | stringifier.Stringifier | 630 | static_member.StaticMember | 631 | iterable.Iterable | 632 | async_iterable.AsyncIterable | 633 | readonly.ReadOnlyMember | 634 | 635 | attributes.ReadWriteAttribute | 636 | maplike.ReadWriteMaplike | 637 | setlike.ReadWriteSetlike | 638 | 639 | attributes.InheritAttribute 640 | ): capture interfaceMember(p.pop()) 641 | 642 | grammar interfaces: 643 | Interface <- [tInterface] * InterfaceOrMixin 644 | InterfaceOrMixin <- 645 | InterfaceRest | 646 | mixins.MixinRest 647 | 648 | InterfaceRest <- ( 649 | >[tIdentifier] * inheritance.Inheritance * [tLCurly] * InterfaceMembers * [tRCurly] * [tSemiColon] 650 | ): 651 | let 652 | members = popSameKindNodes(InterfaceMember) 653 | inheritance = p.pop() 654 | 655 | capture interfaceStmt( 656 | ident ($1).strVal, 657 | inheritance, 658 | members 659 | ) 660 | 661 | InterfaceMembers <- 662 | *(extended_attributes.ExtendedAttributeList * InterfaceMember) 663 | 664 | InterfaceMember <- ( 665 | partial.PartialInterfaceMember | 666 | constructor.Constructor 667 | ): 668 | if (%1).kind != InterfaceMember: 669 | # for constructor 670 | capture interfaceMember(p.pop()) 671 | 672 | grammar callback: 673 | #TODO: add callback inteface support 674 | Callback <- [tCallback] * CallbackRest#CallbackRestOrInterface 675 | 676 | CallbackRestOrInterface <- CallbackRest 677 | CallbackRest <- >[tIdentifier] * [tAssign] * types.Type * [tLPar] * arguments.ArgumentList * [tRPar] * [tSemiColon]: 678 | let 679 | args = p.pop() 680 | t = p.pop() 681 | capture callback( 682 | ident ($1).strVal, 683 | operation regularOperation(empty(), t, args) 684 | # p.pop() 685 | ) 686 | -------------------------------------------------------------------------------- /src/webidl2nim/lexer.nim: -------------------------------------------------------------------------------- 1 | import tokens 2 | import std/[strutils, sequtils, sugar, tables] 3 | import pkg/regex 4 | 5 | const 6 | punctuators = { 7 | "(": tLPar, 8 | ")": tRPar, 9 | ",": tComma, 10 | "...": tEllipsis, 11 | ":": tColon, 12 | ";": tSemiColon, 13 | "<": tLess, 14 | "=": tAssign, 15 | ">": tMore, 16 | "?": tQuestion, 17 | "*": tMul, 18 | "[": tLBracket, 19 | "]": tRBracket, 20 | "{": tLCurly, 21 | "}": tRCurly, 22 | }.toSeq 23 | 24 | keywords = collect: 25 | for i in tAsync..tRecord: 26 | ($i, i) 27 | 28 | alpha = {'a'..'z', 'A'..'Z', '0'..'9', '_', '-'} 29 | 30 | reTokens = ( 31 | ident: re2"[_-]?[A-Za-z][0-9A-Z_a-z-]*", 32 | str: re2("\\\"[^\\\"]*\\\""), 33 | comment: re2"\/\/.*|\/\*[\s\S]*?\*\/", 34 | 35 | intNorm: re2"-?[1-9][0-9]*", 36 | intOct: re2"-?0[Xx][0-9A-Fa-f]+", 37 | intHex: re2"-?0[0-7]*", 38 | decimal: re2"-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+)" 39 | ) 40 | 41 | import options 42 | type 43 | Lexer* = ref object 44 | buffPos: int 45 | 46 | template cmpStrings( 47 | self: var Lexer; 48 | s: string, 49 | tokS: string 50 | ): bool = 51 | tokS.len <= s.len - self.buffPos and 52 | s[self.buffPos..self.buffPos + tokS.high] == tokS 53 | 54 | proc tryParsePunctuator(self: var Lexer; s: string): Option[Token]= 55 | for (i, token) in static(punctuators): 56 | if self.cmpStrings(s, i): 57 | self.buffPos += i.len 58 | return some(Token(kind: token)) 59 | 60 | proc tryParseKeyword(self: var Lexer; s: string): Option[Token]= 61 | # let isBounded = 62 | for (keyword, token) in static(keywords): 63 | if self.cmpStrings(s, keyword): 64 | if s.len > self.buffPos + keyword.len and s[self.buffPos + keyword.len] in alpha: 65 | continue 66 | 67 | self.buffPos += keyword.len 68 | return some(Token(kind: token)) 69 | 70 | proc tryParseRegex(self: var Lexer; s: string, r: Regex2): Option[string] = 71 | let s1 = s[self.buffPos..^1] 72 | if s1.startsWith(r): 73 | var m = RegexMatch2.default() 74 | discard find(s1, r, m) 75 | m.boundaries = m.boundaries.a + self.buffPos .. m.boundaries.b + self.buffPos 76 | self.buffPos += m.boundaries.len 77 | return some(s[m.boundaries]) 78 | 79 | proc tryParseIntLit(self: var Lexer; s: string): Option[int] = 80 | if (let i = self.tryParseRegex(s, reTokens.intNorm); i).isSome: 81 | some(parseInt(i.get)) 82 | elif (let i = self.tryParseRegex(s, reTokens.intOct); i).isSome: 83 | some(parseHexInt(i.get)) 84 | elif (let i = self.tryParseRegex(s, reTokens.intHex); i).isSome: 85 | some(parseOctInt(i.get)) 86 | else: 87 | none(int) 88 | 89 | proc tryParseFloatLit(self: var Lexer; s: string): Option[float] = 90 | if (let f = self.tryParseRegex(s, reTokens.decimal); f).isSome: 91 | some(parseFloat(f.get)) 92 | else: 93 | none(float) 94 | 95 | proc eatWhitespace(self: var Lexer; s: string): bool = 96 | for i in s[self.buffPos..^1]: 97 | if i in {'\t', '\n', '\r', ' '}: 98 | inc self.buffPos 99 | result = true 100 | else: 101 | break 102 | 103 | # unsignedlonglong 104 | iterator lex(self: var Lexer; s: string): Token = 105 | while self.buffPos < s.len: 106 | if (let token = self.tryParsePunctuator(s); token).isSome: 107 | yield token.get() 108 | elif (let token = self.tryParseKeyword(s); token).isSome: 109 | yield token.get() 110 | elif self.eatWhitespace(s): discard 111 | elif (let s = self.tryParseRegex(s, reTokens.ident); s).isSome: 112 | yield Token(kind: tIdentifier, strVal: s.get()) 113 | elif (let s = self.tryParseRegex(s, reTokens.str); s).isSome: 114 | yield Token(kind: tString, strVal: s.get()[1..^2]) 115 | elif (let f = self.tryParseFloatLit(s); f).isSome: 116 | yield Token(kind: tDecimal, floatVal: f.get()) 117 | elif (let i = self.tryParseIntLit(s); i).isSome: 118 | yield Token(kind: tInteger, intVal: i.get()) 119 | elif (let comment = self.tryParseRegex(s, reTokens.comment); comment).isSome: 120 | discard 121 | else: 122 | var (ln, col) = (0, 0) 123 | var pos = 0 124 | for i in s: 125 | if i == '\n': 126 | inc ln 127 | col = 0 128 | 129 | if pos == self.buffPos: break 130 | inc pos 131 | 132 | raise newException(CatchableError, "Lexing error at " & "Ln " & $ln & ", Col " & $col) 133 | 134 | proc tokenize*(str: string): seq[Token] {.inline.} = 135 | var lexer = Lexer(buffPos: 0) 136 | for i in lexer.lex(str): 137 | result.add i 138 | 139 | when isMainModule: 140 | var lexer = Lexer(buffPos: 0) 141 | 142 | for i in lexer.lex"""typedef Int8Array ArrayBufferView;""": 143 | echo i, ":", i.kind 144 | -------------------------------------------------------------------------------- /src/webidl2nim/object_signatures.nim: -------------------------------------------------------------------------------- 1 | import object_signatures_dsl, unode 2 | 3 | signature setlike[T]: 4 | proc incl(self; value: T) {.js: "self.add(value)".} 5 | proc excl(self; value: T) {.js: "self.delete(value)".} 6 | proc `in`(value: T, self): bool {.js: "self.has(value)".} 7 | proc clear(self) {.js: "self.clear()".} 8 | proc len(self): int {.js: "self.size()".} 9 | # forEach 10 | 11 | signature readonlySetlike[T]: 12 | # forEach 13 | proc `in`(value: T, self): bool {.js: "self.has(value)".} 14 | proc len(self) {.js: "self.size()".} 15 | 16 | signature maplike[T]: 17 | # forEach entries, keys, values 18 | func entries(self): seq[tuple[key, value: cstring]] {.js: "Array.from(self)".} 19 | proc `[]=`(self; key, value: T) {.js: "self.set(key, value)".} 20 | proc `[]`(self; key: T): T {.js: "self.get(key)".} 21 | proc `in`(key: T, self): bool {.js: "self.has(key)".} 22 | proc del(self; key: T) {.js: "self.delete(key)".} 23 | proc clear(self) {.js: "self.clear()".} 24 | proc len(self): int {.js: "self.size()".} 25 | 26 | signature readonlyMaplike[T]: 27 | # forEach entries, keys, values 28 | func entries(self): seq[tuple[key, value: cstring]] {.js: "Array.from(self)".} 29 | proc `[]`(self; key: T): T {.js: "self.get(key)".} 30 | proc `in`(key: T, self): bool {.js: "self.has(key)".} 31 | proc len(self): int {.js: "self.size()".} 32 | -------------------------------------------------------------------------------- /src/webidl2nim/object_signatures_dsl.nim: -------------------------------------------------------------------------------- 1 | import macros, sequtils, strutils, sugar#, unode 2 | 3 | proc processProc(i: NimNode, gp: seq[string]): NimNode= 4 | let name = newStrLitNode i[0].repr 5 | let ident = ident"ident" 6 | 7 | let params = i.params[1..^1] 8 | var returnType = i.params[0] 9 | 10 | var paramsFlatten = newNimNode(nnkFormalParams) 11 | for i in params: 12 | let t = i[^2] 13 | for j in i[0..^3]: 14 | paramsFlatten.add newIdentDefs(j, t, i[^1]) 15 | 16 | var pragmaStr = i.pragma[0][1].strVal 17 | var consts = seq[string].default 18 | 19 | for i, e in paramsFlatten: 20 | consts.add chr(i + ord'a') & " = #" 21 | pragmaStr = pragmaStr.replace(e[0].strVal, $chr(i + ord'a')) 22 | 23 | pragmaStr = "(() => {" & consts.join", " & "; " & pragmaStr & "})()" 24 | 25 | var generatedParams = newNimNode(nnkBracket) 26 | for i in paramsFlatten: 27 | # generatedParams.add 28 | var (name, t, default) = (i[0], i[1], i[2]) 29 | let ns = name.strVal 30 | var ident = quote: `ident`(`ns`) 31 | if name.strVal == "self": 32 | t = name 33 | 34 | if default.kind != nnkEmpty: 35 | generatedParams.add quote do: 36 | unode(unkIdentDefs).add(`ident`, `t`, `default`) 37 | else: 38 | generatedParams.add quote do: 39 | unode(unkIdentDefs).add(`ident`, `t`, empty()) 40 | 41 | let pragma = quote: 42 | unode(unkExprColonExpr).add(`ident`("importjs"), strLit`pragmaStr`) 43 | 44 | var procName = quote: 45 | var name = `ident`(`name`) 46 | if exportCode: 47 | name = unode(unkPostfix).add(`ident`("*"), name) 48 | name 49 | 50 | result = quote: 51 | genRoutine( 52 | `procName`, 53 | `generatedParams`, 54 | empty(), 55 | ) 56 | 57 | if returnType.kind != nnkEmpty: 58 | result.add: 59 | if returnType.kind == nnkIdent and returnType.strVal in gp: 60 | quote: `returnType` 61 | else: 62 | let rtS = newStrLitNode(returnType.repr) 63 | quote: `ident`(`rtS`) 64 | else: 65 | result.add quote do: empty() 66 | 67 | result.add ident"unkProcDef" 68 | 69 | result.add quote do: 70 | unode(unkPragma).add(`pragma`) 71 | 72 | macro signature*(name, body):untyped= 73 | var gp = name[1..^1].mapIt(it.strVal) 74 | let paramsStr = "self" & gp 75 | var iteratorBody = newStmtList() 76 | 77 | for i in body: 78 | iteratorBody.add newNimNode(nnkYieldStmt).add(i.processProc(gp)) 79 | 80 | var params = collect: 81 | for i in paramsStr: 82 | newIdentDefs(ident(i), ident"NimUNode", newEmptyNode()) 83 | 84 | newProc( 85 | name[0].postfix "*", 86 | @[ 87 | ident"NimUNode", 88 | newIdentDefs(ident("exportCode"), ident"bool", newEmptyNode()) 89 | ] & params, 90 | iteratorBody, 91 | nnkIteratorDef 92 | ) -------------------------------------------------------------------------------- /src/webidl2nim/parser.nim: -------------------------------------------------------------------------------- 1 | import ast 2 | import ast_gen 3 | import tokens 4 | 5 | import npeg {.all.} 6 | import npeg/codegen {.all.} 7 | 8 | import grammar 9 | 10 | import sequtils, sugar 11 | import std/deques 12 | 13 | type 14 | ParserState* = object 15 | stack*: Deque[Node] 16 | args: seq[Node] 17 | 18 | proc push(p: var ParserState, n: Node) {.used.} = 19 | p.stack.addLast n 20 | 21 | proc pop(p: var ParserState): Node {.used.} = 22 | popLast p.stack 23 | 24 | proc popFirst(p: var ParserState): Node {.used.} = 25 | popFirst p.stack 26 | 27 | template `%`(i: int): Node {.used.} = 28 | p.stack[^i] 29 | 30 | template capture(n: Node) {.used.} = 31 | p.push(n) 32 | 33 | 34 | 35 | template genIdents(cap: untyped)= 36 | if cap.len <= 2: 37 | capture ident($cap[1].s) 38 | else: 39 | var node = idents() 40 | for i in 1 ..< cap.len: 41 | node.add ident($cap[i].s) 42 | capture(node) 43 | 44 | template popSameKindNodes(nodeKind: NodeKind): seq[Node] = 45 | var members: Deque[Node] 46 | while (%1).kind == nodeKind: 47 | members.addFirst p.pop() 48 | if p.stack.len <= 0: 49 | break 50 | members.toSeq 51 | 52 | var p* = peg(Definitions, Token, p: ParserState): 53 | Definitions <- definitions.Definitions 54 | 55 | 56 | proc parseCode*(t: openArray[Token]): ParserState= 57 | discard p.match(t, result) 58 | -------------------------------------------------------------------------------- /src/webidl2nim/tokens.nim: -------------------------------------------------------------------------------- 1 | import std/sugar 2 | 3 | type 4 | TokenKind* = enum 5 | tInteger 6 | tDecimal 7 | tIdentifier 8 | tString 9 | tWhitespace 10 | tComment 11 | tOther 12 | 13 | tComma = "," 14 | tColon = ":" 15 | tSemiColon = ";" 16 | tEllipsis = "..." 17 | tDot = "." 18 | tMul = "*" 19 | tQuestion = "?" 20 | 21 | 22 | tAssign = "=" 23 | tMore = ">" 24 | tLess = "<" 25 | 26 | tLPar = "(" 27 | tRPar = ")" 28 | 29 | tLBracket = "[" 30 | tRBracket = "]" 31 | 32 | tLCurly = "{" 33 | tRCurly = "}" 34 | 35 | #ArgumentNameKeyword 36 | tAsync = "async" 37 | tAttribute = "attribute" 38 | tCallback = "callback" 39 | tConst = "const" 40 | tConstructor = "constructor" 41 | tDeleter = "deleter" 42 | tDictionary = "dictionary" 43 | tEnum = "enum" 44 | tGetter = "getter" 45 | tIncludes = "includes" 46 | tInherit = "inherit" 47 | tInterface = "interface" 48 | tIterable = "iterable" 49 | tMaplike = "maplike" 50 | tMixin = "mixin" 51 | tNamespace = "namespace" 52 | tPartial = "partial" 53 | tReadonly = "readonly" 54 | tRequired = "required" 55 | tSetlike = "setlike" 56 | tSetter = "setter" 57 | tStatic = "static" 58 | tStringifier = "stringifier" 59 | tTypedef = "typedef" 60 | tUnrestricted = "unrestricted" 61 | tUnsigned = "unsigned" 62 | tOptional = "optional" 63 | 64 | tAny = "any" 65 | tOr = "or" 66 | 67 | tTrue = "true" 68 | tFalse = "false" 69 | 70 | tNaN = "NaN" 71 | tInfinity = "Infinity" 72 | tMinusInfinity = "-Infinity" 73 | 74 | #Types 75 | tSequence = "sequence" 76 | tObject = "object" 77 | tSymbol = "symbol" 78 | tFrozenArray = "FrozenArray" 79 | tObservableArray = "ObservableArray" 80 | tUndefined = "undefined" 81 | tNull = "null" 82 | tBoolean = "boolean" 83 | tByte = "byte" 84 | tOctet = "octet" 85 | tBigint = "bigint" 86 | tFloat = "float" 87 | tDouble = "double" 88 | tShort = "short" 89 | tLong = "long" 90 | #Buffer related type 91 | tArrayBuffer = "ArrayBuffer" 92 | tDataView = "DataView" 93 | tInt8Array = "Int8Array" 94 | tInt16Array = "Int16Array" 95 | tInt32Array = "Int32Array" 96 | tUint8Array = "Uint8Array" 97 | tUint16Array = "Uint16Array" 98 | tUint32Array = "Uint32Array" 99 | tUint8ClampedArray = "Uint8ClampedArray" 100 | tBigInt64Array = "BigInt64Array" 101 | tBigUint64Array = "BigUint64Array" 102 | tFloat32Array = "Float32Array" 103 | tFloat64Array = "Float64Array" 104 | 105 | tByteString = "ByteString" 106 | tDOMString = "DOMString" 107 | tUSVString = "USVString" 108 | 109 | tPromise = "Promise" 110 | tRecord = "record" 111 | 112 | Token* = object 113 | case kind*: TokenKind: 114 | of tInteger: 115 | intVal*: int 116 | of tDecimal: 117 | floatVal*: BiggestFloat 118 | of tIdentifier, tString: 119 | strVal*: string 120 | else: 121 | discard 122 | 123 | func `==`*(n: Token, t: TokenKind): bool = n.kind == t 124 | proc `$`*(t: Token): string= 125 | case t.kind: 126 | of tInteger: $t.intVal 127 | of tDecimal: $t.floatVal 128 | of tIdentifier: t.strVal 129 | of tString: '"' & t.strVal & '"' 130 | else: $t.kind 131 | 132 | const keywordNames* = collect: 133 | for i in tAsync..tRecord: 134 | $i 135 | -------------------------------------------------------------------------------- /src/webidl2nim/translate_types_dsl.nim: -------------------------------------------------------------------------------- 1 | import std/[options, tables, sets, sequtils] 2 | import ast 3 | from ast_gen import generic, empty 4 | import macros 5 | 6 | #! this file need code refactor 7 | 8 | proc webidlIdent(s: string): Node = 9 | Node(kind: Ident, strVal: s) 10 | 11 | type 12 | NimReplacementAction* = enum 13 | None 14 | Import 15 | 16 | NimReplacement* = object 17 | t: NimNode 18 | case action: NimReplacementAction 19 | of None: 20 | discard 21 | of Import: 22 | imports: HashSet[string] 23 | 24 | GenericArgKind* {.pure.} = enum 25 | Regular 26 | Sequence 27 | 28 | GenericArg = object 29 | case kind: GenericArgKind 30 | of Regular: 31 | name*: string 32 | of Sequence: 33 | discard 34 | 35 | GenericArgs = seq[GenericArg] 36 | GenericArgsChangeDesc = tuple[before, after: GenericArgs] 37 | 38 | Index = object 39 | case backwards: bool 40 | of true: 41 | backwardsIndex: BackwardsIndex 42 | of false: 43 | intIndex: int 44 | 45 | proc index(i: BackwardsIndex): Index= 46 | Index(backwards: true, backwardsIndex: i) 47 | 48 | proc index(i: int): Index= 49 | Index(backwards: false, intIndex: i) 50 | 51 | proc inc(i: var Index)= 52 | case i.backwards: 53 | of true: 54 | dec i.backwardsIndex 55 | of false: 56 | inc i.intIndex 57 | 58 | proc `$`(i: Index): string {.used.} = 59 | case i.backwards: 60 | of true: 61 | '^' & $i.backwardsIndex.int 62 | of false: 63 | $i.intIndex 64 | 65 | proc getNode(i: Index): NimNode= 66 | case i.backwards: 67 | of true: 68 | prefix(i.backwardsIndex.int.newIntLitNode, "^") 69 | of false: 70 | newIntLitNode(i.intIndex) 71 | 72 | proc `==`(l,r: GenericArg): bool= 73 | if l.kind != r.kind: 74 | return 75 | case l.kind: 76 | of Sequence: 77 | true 78 | of Regular: 79 | l.name == r.name 80 | 81 | 82 | proc getIdents(t: NimNode): seq[NimNode]= 83 | if t.kind == nnkIdent: 84 | return @[t] 85 | 86 | t[0] & getIdents(t[1]) 87 | 88 | proc replacement(t: NimNode): NimReplacement= 89 | NimReplacement(action: None, t: t) 90 | 91 | iterator applyGenericArgDesc( 92 | changeDesc: GenericArgsChangeDesc 93 | ): NimNode = 94 | var (argsDescIn, argsDescOut) = changeDesc 95 | var seqPos: tuple[start: int, stop: BackwardsIndex]#stop right 96 | 97 | for i, val in argsDescIn: 98 | if val.kind == Sequence: 99 | seqPos.start = i 100 | seqPos.stop = ^(argsDescIn.len - i) 101 | break 102 | 103 | var 104 | vars = initTable[GenericArg, Index]() 105 | i: Index = 0.index 106 | 107 | for val in argsDescIn: 108 | case val.kind: 109 | of Regular: 110 | vars[val] = i 111 | of Sequence: 112 | #skip sequence in index 113 | i = seqPos.stop.index 114 | 115 | inc i 116 | 117 | for val in argsDescOut: 118 | case val.kind 119 | of Regular: 120 | if val in vars: 121 | yield vars[val].getNode 122 | of Sequence: 123 | if seqPos.start == 0 and seqPos.stop.int == 1: 124 | yield newEmptyNode() 125 | else: 126 | yield infix(seqPos.start.index.getNode, "..", seqPos.stop.index.getNode) 127 | 128 | proc translateGenericArgs(procName: NimNode, name: NimNode, changeDesc: GenericArgsChangeDesc): NimNode = 129 | assert name.kind == nnkIdent 130 | 131 | for i in applyGenericArgDesc(changeDesc): 132 | var curr = 133 | if i.kind == nnkEmpty: 134 | name 135 | else: 136 | newNimNode(nnkBracketExpr) 137 | .add(name) 138 | .add(i) 139 | 140 | curr = 141 | if i.kind in {nnkPrefix, nnkIntLit}: 142 | quote: @[`curr`.`procName`(imports)] 143 | else: 144 | quote do: 145 | `curr`.mapIt(it.`procName`(imports)) 146 | 147 | if result.isNil: 148 | result = curr 149 | else: 150 | result = infix(result, "&", curr) 151 | 152 | 153 | 154 | 155 | proc addGenericBranch(t, bGenericBase, aGenericBase, aGenericArgsIdx: NimNode): (NimNode, NimNode)= 156 | let 157 | args = macros.ident"args" 158 | ident = macros.ident"ident" 159 | 160 | cond = quote: `t`.kind == Generic and `t`.sons[0].strVal == `bGenericBase` 161 | body = quote: 162 | let `args` = `t`.sons[1..^1] 163 | unode(unkBracketExpr) 164 | .add(`aGenericBase`.`ident`) 165 | .add(`aGenericArgsIdx`)#.mapIt(it.strVal.ident)) 166 | 167 | 168 | (cond, body) 169 | 170 | proc addGenericToIdentBranch(t, bGenericBase, aName: NimNode): (NimNode, NimNode)= 171 | let 172 | ident = macros.ident"ident" 173 | 174 | cond = quote: `t`.kind == Generic and `t`.sons[0].strVal == `bGenericBase` 175 | body = quote: `ident`(`aName`) 176 | 177 | 178 | (cond, body) 179 | 180 | 181 | 182 | proc addIdentBranch(t, bName, aName: NimNode): (NimNode, NimNode)= 183 | let 184 | ident = macros.ident"ident" 185 | 186 | cond = quote: `t`.kind == Ident and `t`.strVal == `bName` 187 | body = quote: `ident`(`aName`) 188 | 189 | (cond, body) 190 | 191 | proc addIdentToGenericBranch(t, bName: NimNode, aGeneric: seq[NimNode]): (NimNode, NimNode)= 192 | let 193 | ident = macros.ident"ident" 194 | aGenericIdents = newNimNode(nnkBracket).add: 195 | aGeneric.mapIt(quote do: `ident`(`it`)) 196 | 197 | cond = quote: `t`.kind == Ident and `t`.strVal == `bName` 198 | body = quote: 199 | unode(unkBracketExpr).add(`aGenericIdents`) 200 | (cond, body) 201 | 202 | 203 | proc addIdentsBranch(t: NimNode, bIdentsStrs: openArray[NimNode], aName: NimNode): (NimNode, NimNode)= 204 | var signatureCond: NimNode 205 | for i, val in bIdentsStrs: 206 | let cond = quote: 207 | `t`.sons[`i`].strVal == `val` 208 | 209 | signatureCond = 210 | if signatureCond.isNil: cond 211 | else: infix(signatureCond, "and", cond) 212 | 213 | let 214 | ident = macros.ident"ident" 215 | 216 | identsLen = bIdentsStrs.len 217 | cond = quote: `t`.kind == Idents and `t`.sons.len == `identsLen` and `signatureCond` 218 | body = quote: `ident`(`aName`) 219 | 220 | (cond, body) 221 | 222 | proc parseGenericArgs(args: openArray[NimNode]): GenericArgs= 223 | for i in args: 224 | assert i.kind in {nnkIdent, nnkIntLit} 225 | 226 | if i.strVal == "_": 227 | result.add GenericArg(kind: Sequence) 228 | else: 229 | result.add GenericArg(kind: Regular, name: i.repr) 230 | 231 | proc translateTypesDslImpl( 232 | body: NimNode, 233 | res: var seq[(Node, NimReplacement)], 234 | genericsArgChangeDescs: var Table[Node, GenericArgsChangeDesc] 235 | )= 236 | for i in body: 237 | case i.kind: 238 | of nnkInfix: 239 | assert i[0].strVal == "->" 240 | let 241 | webidlType = i[1] 242 | nimType = i[2] 243 | 244 | case webidlType.kind: 245 | of nnkIdent, nnkObjectTy: 246 | var node = webidlIdent(webidlType.repr) 247 | # echo node 248 | res.add (node, nimType.replacement) 249 | 250 | of nnkTupleConstr: 251 | for i in webidlType: 252 | translateTypesDslImpl(newStmtList(infix(i, "->", nimType)), res, genericsArgChangeDescs) 253 | 254 | of nnkPar, nnkCommand: 255 | #! generic not supported 256 | var node = 257 | if webidlType.len > 0: 258 | Node(kind: Idents) 259 | else: 260 | Node(kind: Ident) 261 | 262 | for j in webidlType: 263 | if j.kind == nnkIdent: 264 | node.sons.add webidlIdent(j.strVal) 265 | else: 266 | node.sons.add getIdents(j).mapIt(webidlIdent(it.strVal)) 267 | 268 | res.add (node, nimType.replacement) 269 | 270 | of nnkBracketExpr: 271 | var node = webidlIdent(webidlType[0].strVal).generic 272 | genericsArgChangeDescs[node] = ( 273 | parseGenericArgs(webidlType[1..^1]), 274 | parseGenericArgs(nimType[1..^1]) 275 | ) 276 | 277 | res.add (node, nimType[0].replacement) 278 | 279 | else: 280 | discard 281 | 282 | of nnkCall: 283 | # Promise[_]: 284 | # `import` std/asyncjs 285 | # -> PromiseJs[_] 286 | 287 | var nimReplacement = NimReplacement(t: newEmptyNode()) 288 | var webidlType = i[0] 289 | 290 | var webidlTypeNode = 291 | case webidlType.kind: 292 | of nnkIdent, nnkObjectTy: 293 | webidlIdent(webidlType.repr) 294 | of nnkBracketExpr: 295 | webidlIdent(webidlType[0].strVal).generic 296 | else: 297 | empty() 298 | 299 | if webidlTypeNode.kind == Generic: 300 | genericsArgChangeDescs[webidlTypeNode] = GenericArgsChangeDesc.default 301 | genericsArgChangeDescs[webidlTypeNode].before = parseGenericArgs(webidlType[1..^1]) 302 | 303 | for field in i[1]: 304 | case field.kind: 305 | of nnkImportStmt: 306 | nimReplacement.action = Import 307 | nimReplacement.imports = [field[0].repr].toHashSet 308 | 309 | of nnkCommand: 310 | #import moduleName 311 | 312 | var op = 313 | if field[0].kind == nnkAccQuoted: field[0][0].strVal 314 | else: field[0].strVal 315 | 316 | assert op == "import" 317 | case op: 318 | of "import": 319 | nimReplacement.action = Import 320 | nimReplacement.imports = [field[1].repr].toHashSet 321 | 322 | of nnkPrefix: 323 | # -> NimType 324 | 325 | assert field[0].strVal == "->" 326 | var nimType = field[1] 327 | 328 | case nimType.kind: 329 | of nnkIdent: 330 | nimReplacement.t = nimType 331 | of nnkBracketExpr: 332 | # PromiseJs[_] 333 | nimReplacement.t = nimType[0] 334 | 335 | 336 | #! we need that webidl type is generic 337 | genericsArgChangeDescs[webidlTypeNode].after = parseGenericArgs(nimType[1..^1]) 338 | 339 | else: 340 | raise newException(CatchableError, "Invalid out nim type") 341 | 342 | else: 343 | discard 344 | 345 | if nimReplacement.t.kind == nnkEmpty: 346 | nimReplacement.t = webidlType 347 | 348 | # imports.incl nimReplacement.imports 349 | res.add (webidlTypeNode, nimReplacement) 350 | else: 351 | discard 352 | 353 | macro translateTypesDsl*(name: untyped, body: untyped): untyped= 354 | var 355 | nodes: seq[(Node, NimReplacement)] 356 | changeDescs: Table[Node, GenericArgsChangeDesc] 357 | translateTypesDslImpl(body, nodes, changeDescs) 358 | var 359 | procName = macros.ident(name.strVal & "Impl") 360 | t = macros.ident"t" 361 | n = macros.ident"n" 362 | containsName = macros.ident(name.strVal & "Contains") 363 | tContainer = macros.ident"tContainer" 364 | ident = macros.ident"ident" 365 | nestList = macros.ident"nestList" 366 | imports = macros.ident"imports" 367 | 368 | let ifNode = newNimNode(nnkIfStmt) 369 | 370 | var 371 | webidlIdentStrs = newNimNode(nnkBracket) 372 | webidlIdentsConds = seq[NimNode].default 373 | webidlGenericStrs = newNimNode(nnkBracket) 374 | 375 | nimIdents = newNimNode(nnkBracket) 376 | 377 | for i, (inNode, outNode) in nodes: 378 | var cond, body: NimNode 379 | 380 | case inNode.kind: 381 | of Ident: 382 | webidlIdentStrs.add newStrLitNode(inNode.strVal) 383 | 384 | (cond, body) = 385 | if outNode.t.kind == nnkBracketExpr: 386 | addIdentToGenericBranch( 387 | t, 388 | newStrLitNode(inNode.strVal), 389 | outNode.t.mapIt(newStrLitNode it.strVal) 390 | ) 391 | else: 392 | addIdentBranch( 393 | t, 394 | newStrLitNode(inNode.strVal), 395 | newStrLitNode(outNode.t.strVal) 396 | ) 397 | of Idents: 398 | var l = newNimNode(nnkBracket) 399 | for i, e in inNode.sons: 400 | let s = newStrLitNode(e.strVal) 401 | l.add quote do: 402 | `n`[`i`].strVal == `s` 403 | webidlIdentsConds.add nestList(ident"and", l) 404 | 405 | (cond, body) = addIdentsBranch( 406 | t, 407 | inNode.sons.mapIt(newStrLitNode(it.strVal)), 408 | newStrLitNode(outNode.t.strVal) 409 | ) 410 | of Generic: 411 | webidlGenericStrs.add newStrLitNode(inNode[0].strVal) 412 | 413 | (cond, body) = 414 | if changeDescs[inNode].after.len == 0: 415 | addGenericToIdentBranch( 416 | t, 417 | newStrLitNode(inNode.sons[0].strVal), 418 | newStrLitNode(outNode.t.strVal), 419 | ) 420 | else: 421 | let 422 | args = translateGenericArgs( 423 | procName, 424 | macros.ident"args", 425 | changeDescs[inNode], 426 | ) 427 | 428 | addGenericBranch( 429 | t, 430 | newStrLitNode(inNode.sons[0].strVal), 431 | newStrLitNode(outNode.t.strVal), 432 | args 433 | ) 434 | else: 435 | raise newException(CatchableError, "Unsupported node kind") 436 | 437 | # add import to imports 438 | if outNode.action == Import: 439 | if body.kind != nnkStmtList: 440 | body = newNimNode(nnkStmtList).add(body) 441 | 442 | var outType = body 443 | 444 | var importSeq = outNode.imports.toSeq 445 | body = newNimNode(nnkStmtList).add quote do: 446 | `imports`.incl `importSeq`.toHashSet 447 | body.add outType 448 | 449 | if outNode.t.kind == nnkIdent: 450 | nimIdents.add newStrLitNode(outNode.t.strVal) 451 | 452 | ifNode.add newNimNode(nnkElifBranch).add(cond, body) 453 | 454 | ifNode.add newNimNode(nnkElifBranch).add( 455 | quote do: `t`.kind == Ident, 456 | quote do: `ident`(`t`.strVal) 457 | ) 458 | 459 | ifNode.add newNimNode(nnkElifBranch).add( 460 | quote do: `t`.kind == Generic, 461 | quote do: 462 | unode(unkBracketExpr) 463 | .add(`t`.sons[0].strVal.`ident`) 464 | .add(`t`.sons[1..^1].mapIt(it.`procName`(`imports`))) 465 | ) 466 | 467 | ifNode.add newNimNode(nnkElse).add quote do: 468 | raise newException(CatchableError, "Invalid webidl type") 469 | 470 | var containsConds = newNimNode(nnkBracket).add: 471 | quote: 472 | `n`.kind == Ident and `n`.strVal in `webidlIdentStrs` 473 | 474 | if webidlGenericStrs.len > 0: 475 | containsConds.add quote do: 476 | `n`.kind == Generic and `n`[0].strVal in `webidlGenericStrs` 477 | 478 | if webidlIdentsConds.len > 0: 479 | var webidlIdentsCond = newNimNode(nnkBracket).add webidlIdentsConds 480 | webidlIdentsCond = nestList(ident"or", webidlIdentsCond) 481 | containsConds.add quote do: 482 | `n`.kind == Idents and `webidlIdentsCond` 483 | 484 | containsConds = nestList(ident"or", containsConds) 485 | 486 | var r = quote do: 487 | type `name` = object 488 | 489 | proc `procName`(`tContainer`: Node, `imports`: var HashSet[string]): NimUNode = 490 | assert `tContainer`.kind == Type 491 | let `t` = `tContainer`.sons[0] 492 | `ifNode` 493 | 494 | proc mapping(_: type `name`): auto = 495 | `procName` 496 | 497 | func `containsName`(`n`: Node): bool = 498 | `containsConds` 499 | 500 | func contains(_: type `name`): auto = 501 | `containsName` 502 | 503 | func usedNimIdents(_: type `name`): HashSet[string] = 504 | const result = toHashSet(`nimIdents`) 505 | result 506 | 507 | when defined(webidl2nim.debug): 508 | echo r.repr 509 | r 510 | -------------------------------------------------------------------------------- /src/webidl2nim/translator.nim: -------------------------------------------------------------------------------- 1 | import unode 2 | import ast 3 | import translate_types_dsl 4 | 5 | import sequtils 6 | import strformat 7 | import sets 8 | import sugar 9 | import std/[with, tables, options] 10 | import deps 11 | import "$nim"/compiler/renderer 12 | import object_signatures 13 | from std/algorithm import SortOrder 14 | 15 | type 16 | OptionalAttributePolicy* {.pure.} = enum 17 | MakeNoVariardic 18 | UseDefaultVal 19 | GenDeferredProcs 20 | 21 | ConstructorPolicy* {.pure.} = enum 22 | InitTypedesc 23 | InitName 24 | NewName 25 | 26 | Feature* {.pure.} = enum 27 | NamespaceJsFieldBinding 28 | InterfaceJsFieldBinding 29 | 30 | MethodCallSyntax 31 | 32 | ImportJsToImportCpp 33 | JsRootToRootObj 34 | 35 | ObjConstrRequired 36 | ReadonlyAttributes 37 | 38 | OnIdentProc* = (NimUNode, bool) -> NimUNode 39 | 40 | TranslatorSettings* = object 41 | onIdent*: OnIdentProc 42 | 43 | optionalAttributePolicy*: OptionalAttributePolicy 44 | constructorPolicy*: ConstructorPolicy 45 | 46 | features*: set[Feature] 47 | exportCode*: bool 48 | 49 | Translator* = ref object 50 | settings*: TranslatorSettings 51 | imports*: HashSet[string] 52 | 53 | symCache: SymCache # webidl syms 54 | deps: Table[string, DeclDeps[string]] 55 | 56 | typeMappings: seq[proc (t: Node, imports: var HashSet[string]): NimUNode] 57 | webidlContainsProcs: seq[proc (n: Node): bool {.noSideEffect.}] 58 | additionalNimIdents: HashSet[string] 59 | 60 | TranslatedDeclAssembly* = object 61 | decl: NimUNode 62 | declGenerated*: NimUNode 63 | declFields*: seq[NimUNode] 64 | 65 | bindRoutines*: seq[NimUNode] 66 | bindLets* : seq[NimUNode] = @[] 67 | 68 | VariardicArg = tuple[ 69 | val: NimUNode, 70 | isVariardic: bool 71 | ] 72 | 73 | OperationTranslationCtx* = object 74 | args: seq[VariardicArg] 75 | 76 | SymCache = object 77 | idTable: Table[int, Node] 78 | nextId: int 79 | 80 | symNames: Table[string, Sym] 81 | 82 | Sym = ref object 83 | id: int 84 | name: string 85 | kind: NodeKind 86 | # ast: Node 87 | 88 | proc assemble*(decls: openArray[TranslatedDeclAssembly], imports: HashSet[string]): NimUNode = 89 | let typeSect = unode(unkTypeSection) 90 | let letSect = unode(unkLetSection) 91 | var routines = seq[NimUNode].default 92 | var imports = unode(unkImportStmt).add imports.mapIt(ident(it)) 93 | 94 | for i in decls: 95 | if i.declGenerated != nil: 96 | typeSect.add i.declGenerated 97 | 98 | for j in i.bindLets: 99 | letSect.add j 100 | 101 | for j in i.bindRoutines: 102 | routines.add j 103 | 104 | result = unode(unkStmtList) 105 | with result: 106 | addIfNotEmpty imports 107 | addIfNotEmpty typeSect 108 | addIfNotEmpty letSect 109 | addIfNotEmpty routines 110 | 111 | proc getAst*(cache: SymCache, s: Sym): Node = 112 | cache.idTable[s.id] 113 | 114 | proc newSym*(self: Translator, name: string = ""; assembly = Node(kind: Empty)): Sym = 115 | result = Sym( 116 | id: self.symCache.nextId, 117 | name: name, 118 | kind: assembly.kind 119 | # ast: ast 120 | ) 121 | self.symCache.symNames[name] = result 122 | self.symCache.idTable[result.id] = assembly 123 | 124 | inc self.symCache.nextId 125 | 126 | proc findSym*(self: Translator; name: string): Sym = 127 | self.symCache.symNames[name] 128 | 129 | proc tryFindSym*(self: Translator; name: string): Option[Sym] = 130 | if name in self.symCache.symNames: 131 | some(self.findSym(name)) 132 | else: 133 | none(Sym) 134 | 135 | proc findSym*(self: Translator; name: Node): Sym = 136 | self.findSym(name.strVal) 137 | 138 | proc tryFindSym*(self: Translator; name: Node): Option[Sym] = 139 | self.tryFindSym(name.strVal) 140 | 141 | translateTypesDsl nimTypes: 142 | undefined -> void 143 | 144 | (ByteString, DOMString, USVString) -> cstring 145 | boolean -> bool 146 | 147 | byte -> int8 148 | short -> int16 149 | long -> int32#ident 150 | (long long) -> int64#idents 151 | 152 | octet -> byte 153 | (unsigned short) -> uint16 154 | (unsigned long) -> uint32#{usigned, short}, uint32 155 | (unsigned long long) -> uint64 156 | 157 | float -> float32 158 | double -> float64 159 | 160 | Int8Array -> seq[int8] 161 | Int16Array -> seq[int16] 162 | Int32Array -> seq[int32] 163 | BigInt64Array -> seq[int64] 164 | 165 | (Uint8Array, Uint8ClampedArray) -> seq[byte] 166 | Uint16Array -> seq[uint16] 167 | Uint32Array -> seq[uint32] 168 | Float32Array -> seq[float32] 169 | Float64Array -> seq[float64] 170 | BigUint64Array -> seq[uint64] 171 | 172 | ArrayBuffer: 173 | #`import` pkg/jsutils 174 | # ? maybe better to make switch 175 | `import` std/private/jsutils {.all.} 176 | -> ArrayBuffer 177 | 178 | Navigator: 179 | `import` std/dom 180 | -> Navigator 181 | 182 | sequence[_] -> seq[_] 183 | Promise[_]: 184 | `import` std/asyncjs 185 | -> Future[_] 186 | 187 | bigint: 188 | `import` std/jsbigints 189 | -> JsBigInt 190 | 191 | undefined: 192 | `import` std/jsffi 193 | -> JsObject # we can't use undefined 194 | 195 | object: 196 | `import` std/jsffi 197 | -> JsObject 198 | 199 | any: 200 | `import` std/jsffi 201 | -> JsObject 202 | 203 | record[_]: 204 | #not tested with ffi 205 | `import` std/jsffi 206 | -> JsObject 207 | 208 | EventTarget: 209 | `import` std/dom 210 | 211 | const sep = ", " 212 | 213 | proc genImportJsMethodPattern(argsCnt: int, methodName = "$1"): string = 214 | var args = newStringOfCap(sep.len * (argsCnt-2)) 215 | if argsCnt - 2 > 0: 216 | args.add "#" 217 | for i in 1..<(argsCnt - 2): 218 | args.add sep 219 | args.add "#" 220 | 221 | "#." & methodName & '(' & args & ')' 222 | 223 | using 224 | self: Translator 225 | assembly: var TranslatedDeclAssembly 226 | 227 | type TypeMapping = concept type T 228 | T.mapping# is proc (t: Node, imports: var HashSet[string]): NimUNode 229 | T.contains# is proc (n: Node): bool 230 | T.usedNimIdents is HashSet[string] 231 | 232 | template addMapping*(self; m: type TypeMapping)= 233 | self.typeMappings.add m.mapping 234 | self.webidlContainsProcs.add m.contains 235 | self.additionalNimIdents.incl m.usedNimIdents 236 | 237 | proc jsRoot(self): auto = 238 | if JsRootToRootObj in self.settings.features: 239 | ident"RootObj" 240 | else: 241 | ident"JsRoot" 242 | 243 | proc importJs(self): auto = 244 | if ImportJsToImportCpp in self.settings.features: 245 | ident"importcpp" 246 | else: 247 | ident"importjs" 248 | 249 | proc toNimType*(self; n: Node): NimUNode = 250 | let t = n.inner 251 | if t.kind == Union: 252 | return nestList( 253 | ident"or", 254 | t.sons.mapIt(self.toNimType(it)), 255 | unkInfix 256 | ) 257 | 258 | let 259 | mapping = nimTypes.mapping 260 | contains = nimTypes.contains 261 | 262 | if contains(t): 263 | return mapping(n, self.imports) 264 | 265 | for i in 0..self.typeMappings.high: 266 | let 267 | mapping = self.typeMappings[i] 268 | contains = self.webidlContainsProcs[i] 269 | 270 | if contains(t): 271 | return mapping(n, self.imports) 272 | 273 | if n.inner.kind == Ident: 274 | ident(n.inner.strVal) 275 | else: 276 | # TODO: make exception more informative 277 | raise newException(CatchableError, "Non ident defined type is not supported") 278 | 279 | proc translateIdentImpl(self; node: Node, capitalize: bool): auto = 280 | assert node.kind in {Ident, Empty} 281 | if node.isEmpty: 282 | return empty() 283 | 284 | self.settings.onIdent( 285 | ident node.strVal, 286 | capitalize 287 | ) 288 | 289 | proc translateIdent(self; node: Node): auto = 290 | translateIdentImpl(self, node, false) 291 | 292 | proc translateDeclIdent(self; node: Node): auto = 293 | translateIdentImpl(self, node, true) 294 | 295 | proc translateType*(self; node: Node, typeDefKind: NimUNodeKind = unkEmpty): auto = 296 | assert node.kind == Type 297 | 298 | var nimType = self.toNimType(node) 299 | 300 | if ( 301 | node.inner.kind != Ident or 302 | nimType.kind != unkIdent or 303 | nimType.strVal in (self.additionalNimIdents + static nimTypes.usedNimIdents) 304 | ): return nimType 305 | 306 | tryRemoveExportMarker self.settings.onIdent( 307 | nimType, 308 | true 309 | ) 310 | 311 | proc translateEmpty(node: Node): auto = 312 | assert node.kind == Empty 313 | empty() 314 | 315 | proc translateIntLit(node: Node): auto = 316 | assert node.kind == IntLit 317 | intLit(node.intVal) 318 | 319 | proc translateStrLit(node: Node): auto = 320 | assert node.kind == StrLit 321 | echo node.strVal 322 | strLit(node.strVal) 323 | 324 | proc translateFloatLit(node: Node): auto = 325 | assert node.kind == FloatLit 326 | floatLit(node.floatVal) 327 | 328 | proc translateBoolLit(node: Node): auto = 329 | assert node.kind == BoolLit 330 | 331 | case node.boolVal: 332 | of true: ident"true" 333 | of false: ident"false" 334 | 335 | proc translateVal(self; node: Node): auto = 336 | assert node.kind in {Empty, IntLit, FloatLit, BoolLit, StrLit} 337 | case node.kind: 338 | of Empty: 339 | node.translateEmpty() 340 | of IntLit: 341 | node.translateIntLit() 342 | of FloatLit: 343 | node.translateFloatLit() 344 | of BoolLit: 345 | node.translateBoolLit() 346 | of StrLit: 347 | node.translateStrLit() 348 | else: 349 | raise newException(CatchableError, "Invalid node for val") 350 | 351 | 352 | 353 | proc translateIdentDefs(self; node: Node): auto = 354 | assert node.kind == IdentDefs 355 | const anyStr = @["ByteString", "DOMString", "USVString"] 356 | 357 | let 358 | name = node[0] 359 | t = node[1] 360 | default = node[2] 361 | 362 | unode(unkIdentDefs).add( 363 | self.translateIdent name, 364 | self.translateType t, 365 | if default.kind == StrLit and t.inner.kind == Ident and t.inner.strVal notin anyStr: 366 | #? maybe need to raise exception if t is not enum ? 367 | tryRemoveExportMarker: 368 | self.translateDeclIdent Node(kind: Ident, strVal: default.strVal) 369 | elif ( 370 | default.kind in {IntLit, Ident} and 371 | t.inner.kind == Ident and 372 | t.inner.strVal notin (anyStr & @["long", "short"]) 373 | ): 374 | #? maybe need to raise exception if t is not distinct ? 375 | unode(unkCall).add( 376 | self.translateType t, 377 | self.translateVal default 378 | ) 379 | elif ( 380 | default.kind == IntLit and 381 | (let nimT = self.translateType(t); nimT).kind == unkIdent and 382 | nimT.strVal != "int" 383 | ): 384 | unode(unkDotExpr).add( 385 | self.translateVal default, 386 | nimT 387 | ) 388 | else: 389 | self.translateVal default 390 | ) 391 | 392 | proc translateAttribute(self; node: Node): auto = 393 | assert node.kind == Attribute 394 | 395 | let 396 | name = node[0] 397 | t = node[1] 398 | 399 | var identDefsName = self.translateIdent name 400 | 401 | unode(unkIdentDefs).add( 402 | identDefsName, 403 | self.translateType t, 404 | empty() 405 | ) 406 | 407 | using opCtx: var OperationTranslationCtx 408 | 409 | proc translateSimpleArgument(self, opCtx; node: Node) = 410 | assert node.kind == SimpleArgument 411 | 412 | var result = self.translateIdentDefs node[0] 413 | result[0] = tryRemoveExportMarker result[0] 414 | 415 | if node[1].kind == Ellipsis: 416 | result[1] = 417 | unode(unkBracketExpr) 418 | .add(ident"varargs") 419 | .add(result[1]) 420 | 421 | opCtx.args.add (result, false) 422 | 423 | # type IdentDefsVariants = object 424 | proc translateOptionalArgument(self, opCtx; node: Node) = 425 | assert node.kind == OptionalArgument 426 | let identDefs = node[0] 427 | 428 | 429 | var result = (self.translateIdentDefs identDefs, false).VariardicArg 430 | result[0][0] = tryRemoveExportMarker result[0][0] 431 | 432 | if identDefs[2].isEmpty: 433 | case self.settings.optionalAttributePolicy: 434 | of MakeNoVariardic: 435 | discard 436 | 437 | of UseDefaultVal: 438 | result[0][2] = 439 | unode(unkDotExpr) 440 | .add(self.translateType(identDefs[1], unkEmpty)) 441 | .add(ident"default") 442 | 443 | of GenDeferredProcs: 444 | result.isVariardic = true 445 | 446 | opCtx.args.add result 447 | 448 | proc translateArgument(self, opCtx; node: Node) = 449 | assert node.kind == Argument 450 | case node[0].kind: 451 | of SimpleArgument: 452 | self.translateSimpleArgument(opCtx, node[0]) 453 | of OptionalArgument: 454 | self.translateOptionalArgument(opCtx, node[0]) 455 | else: 456 | raise newException(CatchableError, "Invalid argument") 457 | 458 | proc translateArgumentList(self; node: Node): auto = 459 | # assert node.kind == ArgumentList 460 | var ctx: OperationTranslationCtx 461 | for i in node.sons: 462 | self.translateArgument(ctx, i) 463 | 464 | var argLists: seq[seq[NimUNode]] = @[@[]] 465 | for i in ctx.args: 466 | # [a, b, c], [a, b], [a] 467 | 468 | if i.isVariardic: 469 | argLists.add argLists[0] 470 | argLists[0].add i[0] 471 | 472 | argLists 473 | 474 | # import print 475 | 476 | proc translateRegularOperation*(self, assembly; node: Node) = 477 | assert node.kind == RegularOperation 478 | let argLists = self.translateArgumentList node[2] 479 | 480 | for argList in argLists: 481 | assembly.bindRoutines.add genRoutine( 482 | name = self.translateIdent node[0], 483 | returnType = self.translateType node[1], 484 | # pragmas = pragmas [pragma ident"importc"], 485 | params = argList 486 | ) 487 | 488 | proc translateOperation*(self, assembly; node: Node) = 489 | assert node.kind == Operation 490 | case node[0].kind: 491 | of RegularOperation: 492 | self.translateRegularOperation(assembly, node[0]) 493 | else: 494 | raise newException(CatchableError, "Invalid operation") 495 | 496 | proc setMethodBase*(m, base: NimUNode): auto = 497 | var params = unode(unkFormalParams) 498 | var p = m 499 | with params: 500 | add p[3][0] 501 | add base 502 | add p[3].sons[1..^1] 503 | 504 | p[3] = params 505 | p 506 | 507 | proc translateNamespaceMember*(self, assembly; node: Node) = 508 | assert node.kind == NamespaceMember 509 | 510 | if NamespaceJsFieldBinding in self.settings.features: 511 | # firstly trying to translate field as js 512 | if node[0].kind == ConstStmt: 513 | var identDefs = self.translateIdentDefs node[0][0] 514 | identDefs[0] = identDefs[0].withPragma: 515 | pragma unode(unkExprColonExpr).add( 516 | ident "importc", 517 | strLit node[0][0][0].strVal 518 | ) 519 | 520 | identDefs[2] = empty() 521 | assembly.declFields.add identDefs 522 | return 523 | 524 | elif node[0].kind == Operation and not node[0].isVariardic and MethodCallSyntax notin self.settings.features: 525 | self.translateOperation(assembly, node[0]) 526 | var op = assembly.bindRoutines.pop() 527 | op[0] = empty() # clear name if generated 528 | op[4] = empty() # clear pragmas if generated 529 | 530 | var name = self.translateIdent(node[0].name) 531 | name = name.withPragma pragma unode(unkExprColonExpr).add( 532 | ident "importc", 533 | strLit node[0].name.strVal 534 | ) 535 | 536 | assembly.declFields.add unode(unkIdentDefs).add( 537 | name, 538 | op, 539 | empty() 540 | ) 541 | return 542 | 543 | let selfNode = 544 | unode(unkIdentDefs).add( 545 | ident"_", 546 | unode(unkBracketExpr).add( 547 | ident"typedesc", 548 | assembly.decl 549 | ), 550 | empty() 551 | ) 552 | 553 | case node[0].kind: 554 | of Operation: 555 | var currentAssembly: TranslatedDeclAssembly 556 | self.translateOperation(currentAssembly, node[0]) 557 | for procDef in currentAssembly.bindRoutines: 558 | #TODO: method call syntax fix importjs 559 | var fixedProcDef = procDef.setMethodBase(selfNode) 560 | if MethodCallSyntax in self.settings.features: 561 | fixedProcDef[4] = fixedProcDef[4].withPragma pragma unode(unkExprColonExpr).add( 562 | self.importJs, 563 | strLit( 564 | if node.name.strVal == fixedProcDef[0].tryRemoveExportMarker.strVal: 565 | genImportJsMethodPattern(procDef[3].sons.len) 566 | else: 567 | genImportJsMethodPattern(procDef[3].sons.len, node.name.strVal) 568 | ) 569 | ) 570 | else: 571 | fixedProcDef[4] = fixedProcDef[4].withPragma pragma ident"importc" 572 | 573 | assembly.bindRoutines.add fixedProcDef 574 | 575 | of ConstStmt: 576 | var identDefs = node[0][0] 577 | 578 | assembly.bindRoutines.add genRoutine( 579 | name = self.translateIdent identDefs[0], 580 | returnType = self.translateType identDefs[1], 581 | params = [selfNode], 582 | body = self.translateVal identDefs[2], 583 | routineType = unkTemplateDef 584 | ) 585 | 586 | of Readonly: 587 | #TODO: maybe fix readonly ? 588 | assert node[0][0].kind == Attribute 589 | var attribute = node[0][0] 590 | 591 | assembly.bindRoutines.add genRoutine( 592 | name = self.translateIdent attribute[0], 593 | returnType = self.translateType attribute[1], 594 | params = [selfNode], 595 | routineType = unkProcDef, 596 | # pragmas = pragmas [pragma ident"importc"], 597 | ) 598 | 599 | else: 600 | raise newException(CatchableError, "Invalid namespace member") 601 | 602 | proc translateNamespace*(self; node: Node): TranslatedDeclAssembly = 603 | # var result = TranslatedDeclAssembly.default 604 | let t = self.translateDeclIdent(node[0]) 605 | 606 | if NamespaceJsFieldBinding in self.settings.features: 607 | let jsTypeName = ident tryRemoveExportMarker(t).strVal & "Impl" 608 | result.decl = jsTypeName 609 | 610 | for i in node.sons[1..^1]: 611 | self.translateNamespaceMember(result, i) 612 | 613 | let jsType = genAlias( 614 | jsTypeName, 615 | unode(unkRefTy).add unode(unkObjectTy).add( 616 | empty(), 617 | unode(unkOfInherit).add(self.jsRoot), 618 | unode(unkRecList).add(result.declFields) 619 | ) 620 | ) 621 | result.declGenerated = jsType 622 | result.bindLets.add: 623 | unode(unkIdentDefs).add( 624 | t.withPragma pragma( 625 | ident"importc", 626 | ident"nodecl" 627 | ), 628 | jsTypeName, 629 | empty(), 630 | ) 631 | 632 | else: 633 | result.decl = t 634 | 635 | for i in node.sons[1..^1]: 636 | self.translateNamespaceMember(result, i) 637 | 638 | result.declGenerated = genDistinct( 639 | t, 640 | ident"void" 641 | ) 642 | 643 | # import ast_repr 644 | 645 | proc translatePartialDictionary(self; node: Node): TranslatedDeclAssembly = 646 | # !Note: dictionary and partial dictionary is same 647 | # ! (only difference is inference) 648 | 649 | assert: 650 | node.kind == Dictionary# and 651 | # node[1].isEmpty # partial dictionary not support inference 652 | 653 | for i in node.sons[2..^1]: 654 | let n = i.inner 655 | var fieldPragmas = pragma(ident"importc") 656 | if ( 657 | ObjConstrRequired in self.settings.features and 658 | n.kind == Required 659 | ): fieldPragmas.add(ident"requiresInit") 660 | 661 | var identDefs = self.translateIdentDefs( 662 | n.skipNodes({Required}) 663 | ) 664 | identDefs[0] = identDefs[0].withPragma fieldPragmas 665 | result.declFields.add identDefs 666 | 667 | result.decl = self.translateDeclIdent(node[0]) 668 | result.declGenerated = genAlias( 669 | result.decl, 670 | unode(unkRefTy).add unode(unkObjectTy).add( 671 | empty(), 672 | unode(unkOfInherit).add(self.jsRoot), 673 | unode(unkRecList).add(result.declFields) 674 | ) 675 | ) 676 | 677 | proc translatePartialInterface*(self; node: Node): TranslatedDeclAssembly = 678 | result.decl = self.translateDeclIdent(node[0]) 679 | 680 | template makeField(attribute, node; hidden = false) = 681 | var field = self.translateAttribute(attribute) 682 | assert field.kind == unkIdentDefs 683 | # TODO: make hidden works >_< 684 | # if hidden: field[0] = tryRemoveExportMarker field[0] 685 | field[0] = field[0].withPragma: 686 | pragma unode(unkExprColonExpr).add( 687 | ident "importc", 688 | strLit node[0].strVal 689 | ) 690 | result.declFields.add field 691 | 692 | template makeField(attribute; hidden = false) = 693 | makeField(attribute, attribute, hidden) 694 | 695 | template genAttribute(n) = 696 | var attribute = n.skipNodes({Readonly}) 697 | if ReadonlyAttributes in self.settings.features and n.kind == Readonly: 698 | # type T = ref object of JsRoot 699 | # attrHidden: TT 700 | # proc attr*(self: T) = self.attrHidden 701 | assert attribute[0].kind == Ident 702 | 703 | let procName = self.translateIdent attribute[0] 704 | attribute.sons[0].strVal = 705 | attribute[0].strVal & "_hidden" 706 | 707 | makeField(attribute, n.inner, true) 708 | result.bindRoutines.add genRoutine( 709 | name = procName, 710 | returnType = tryRemoveExportMarker( 711 | self.translateType attribute[1] 712 | ), 713 | params = [selfNode], 714 | body = unode(unkDotExpr).add( 715 | ident"self", 716 | tryRemoveExportMarker self.translateIdent(attribute[0]), 717 | ), 718 | routineType = unkProcDef 719 | ) 720 | else: makeField(attribute) 721 | 722 | template `*`(n: NimUNode): NimUNode = 723 | if self.settings.exportCode: 724 | unode(unkPostfix).add(ident"*", n) 725 | else: n 726 | 727 | let selfTypedescNode = 728 | unode(unkIdentDefs).add( 729 | ident"_", 730 | unode(unkBracketExpr).add( 731 | ident"typedesc", 732 | tryRemoveExportMarker result.decl 733 | ), 734 | empty() 735 | ) 736 | let selfNode = 737 | unode(unkIdentDefs).add( 738 | ident"self", 739 | tryRemoveExportMarker result.decl, 740 | empty() 741 | ) 742 | 743 | for i in node.sons[2..^1]: 744 | let n = i.inner 745 | case n.kind: 746 | of ConstStmt: 747 | #TODO: add js way 748 | var identDefs = n.inner 749 | 750 | result.bindRoutines.add genRoutine( 751 | name = self.translateIdent identDefs[0], 752 | returnType = self.translateType identDefs[1], 753 | params = [selfTypedescNode], 754 | body = self.translateVal identDefs[2], 755 | routineType = unkTemplateDef 756 | ) 757 | 758 | of Readonly: 759 | case n.inner.kind: 760 | of Setlike: 761 | for i in readonlySetlike( 762 | self.settings.exportCode, 763 | tryRemoveExportMarker result.decl, 764 | self.translateType n.inner[1] 765 | ): result.bindRoutines.add i 766 | 767 | 768 | of Maplike: 769 | for i in readonlyMaplike( 770 | self.settings.exportCode, 771 | tryRemoveExportMarker result.decl, 772 | self.translateType n.inner[1] 773 | ): result.bindRoutines.add i 774 | 775 | of Attribute: 776 | genAttribute(n) 777 | else: 778 | raise newException(CatchableError, "Invalid readonly") 779 | 780 | of Attribute: genAttribute(n) 781 | 782 | of Operation: 783 | var currentAssembly: TranslatedDeclAssembly 784 | self.translateOperation(currentAssembly, n) 785 | for procDef in currentAssembly.bindRoutines: 786 | #TODO: method call syntax fix importjs 787 | var fixedProcDef = procDef.setMethodBase(selfNode) 788 | if MethodCallSyntax in self.settings.features: 789 | fixedProcDef[4] = fixedProcDef[4].withPragma pragma unode(unkExprColonExpr).add( 790 | self.importJs, 791 | strLit( 792 | if n.name.strVal == fixedProcDef[0].tryRemoveExportMarker.skipNodes({unkAccQuoted}).strVal: 793 | genImportJsMethodPattern(procDef[3].sons.len) 794 | else: 795 | genImportJsMethodPattern(procDef[3].sons.len, n.name.strVal) 796 | ) 797 | ) 798 | else: 799 | fixedProcDef[4] = fixedProcDef[4].withPragma pragma ident"importc" 800 | 801 | result.bindRoutines.add fixedProcDef 802 | 803 | of Setlike: 804 | for i in setlike( 805 | self.settings.exportCode, 806 | tryRemoveExportMarker result.decl, 807 | self.translateType n[1] 808 | ): result.bindRoutines.add i 809 | 810 | of Maplike: 811 | for i in maplike( 812 | self.settings.exportCode, 813 | tryRemoveExportMarker result.decl, 814 | self.translateType n.inner[1] 815 | ): result.bindRoutines.add i 816 | 817 | of Constructor: 818 | # we maybe in not partial interface 819 | for argList in self.translateArgumentList(n.inner): 820 | let pragma = pragma unode(unkExprColonExpr).add( 821 | self.importJs, 822 | strLit do: 823 | "(new " & node[0].strVal & "(@))" 824 | ) 825 | result.bindRoutines.add: 826 | case self.settings.constructorPolicy: 827 | of InitTypedesc: 828 | genRoutine( 829 | name = *ident"init", 830 | returnType = tryRemoveExportMarker result.decl, 831 | pragmas = pragma, 832 | params = selfTypedescNode & argList 833 | ) 834 | of InitName, NewName: 835 | genRoutine( 836 | name = *ident( 837 | ( 838 | if self.settings.constructorPolicy == InitName: 839 | "init" 840 | else: 841 | "new" 842 | ) & (tryRemoveExportMarker result.decl).strVal 843 | ), 844 | returnType = tryRemoveExportMarker result.decl, 845 | pragmas = pragma, 846 | params = selfTypedescNode & argList 847 | ) 848 | 849 | of Stringifier: 850 | if n.sons.len > 0 and n.inner.kind in {Attribute, Readonly}: 851 | genAttribute(n.inner) 852 | result.bindRoutines.add genRoutine( 853 | name = *unode(unkAccQuoted).add(ident"$"), 854 | returnType = ident"string", 855 | params = [selfNode], 856 | body = unode(unkPrefix).add( 857 | ident"$", 858 | unode(unkDotExpr).add( 859 | ident"self", 860 | tryRemoveExportMarker( 861 | self.translateIdent n.inner.skipNodes({Readonly})[0] 862 | ) 863 | ) 864 | ) 865 | ) 866 | else: 867 | result.bindRoutines.add genRoutine( 868 | name = *unode(unkAccQuoted).add(ident"$"), 869 | returnType = ident"string", 870 | params = [selfNode], 871 | pragmas = pragma unode(unkExprColonExpr).add( 872 | self.importJs, 873 | strLit"#.toString()" 874 | ) 875 | ) 876 | of Static: 877 | case n.inner.kind: 878 | of RegularOperation: 879 | var tmpAssembly = TranslatedDeclAssembly.default 880 | self.translateRegularOperation(tmpAssembly, n.inner) 881 | for i in tmpAssembly.bindRoutines: 882 | var formalParams = unode(unkFormalParams) 883 | with formalParams: 884 | add i[3][0] 885 | add selfTypedescNode 886 | add i[3].sons[1..^1] 887 | 888 | var procDef = i 889 | procDef[3] = formalParams 890 | result.bindRoutines.add procDef 891 | of Readonly, Attribute: 892 | # Same as const, but not defined in webidl 893 | let attribute = n.inner.skipNodes({Readonly}) 894 | assert attribute.kind == Attribute 895 | 896 | result.bindRoutines.add genRoutine( 897 | name = self.translateIdent attribute[0], 898 | returnType = self.translateType attribute[1], 899 | params = [selfTypedescNode], 900 | pragmas = pragma unode(unkExprColonExpr).add( 901 | self.importJs, 902 | strLit(node[0].strVal & "." & attribute[0].strVal) 903 | ), 904 | routineType = unkProcDef 905 | ) 906 | else: 907 | raise newException(CatchableError, "Strange node of kind " & $n.inner.kind & " in static member") 908 | of Iterable: 909 | proc genIteratorImpl( 910 | methodName: string, 911 | outType: NimUNode, 912 | nimName = 913 | self.translateIdent Node(kind: Ident, strVal: methodName) 914 | ): auto = 915 | let 916 | methodNameS = strLit('.' & methodName & "()") 917 | selfName = selfNode[0] 918 | 919 | let body = ugenAst(methodNameS, outType, selfName): 920 | {.emit: ["var it = ", selfName, methodNameS].} 921 | var it {.importc, nodecl.}: JsObject 922 | while true: 923 | {.emit: "let next = it.next();".} 924 | let next {.importc, nodecl.}: JsObject 925 | 926 | if next.done.to(bool): 927 | break 928 | 929 | yield next.value.to(outType) 930 | 931 | 932 | genRoutine( 933 | name = nimName, 934 | returnType = outType, 935 | params = [selfNode], 936 | pragmas = pragma unode(unkExprColonExpr).add( 937 | self.importJs, 938 | strLit(node[0].strVal & "." & methodName & "()") 939 | ), 940 | body = body, 941 | routineType = unkIteratorDef 942 | ) 943 | 944 | 945 | template genIterator(methodName: string, outType: NimUNode)= 946 | result.bindRoutines.add genIteratorImpl(methodName, outType) 947 | template genIterator(methodName: string, outType: NimUNode, nimName: NimUNode)= 948 | result.bindRoutines.add genIteratorImpl(methodName, outType, nimName) 949 | 950 | self.imports.incl "std/jsffi" 951 | var v = self.translateType n[1] 952 | genIterator("values", v) 953 | if n[0].kind != Empty: 954 | let k = self.translateType n[0] 955 | genIterator("keys", k) 956 | genIterator( 957 | "entries", 958 | unode(unkTupleConstr).add(k, v), 959 | *ident"pairs" 960 | ) 961 | 962 | else: 963 | discard 964 | 965 | let recList = unode(unkRecList).add(result.declFields) 966 | 967 | result.declGenerated = genAlias( 968 | result.decl, 969 | unode(unkRefTy).add unode(unkObjectTy).add( 970 | empty(), 971 | unode(unkOfInherit).add(self.jsRoot), 972 | recList 973 | ) 974 | ) 975 | 976 | proc translateInterface*(self; node: Node): TranslatedDeclAssembly = 977 | assert node.kind == Interface 978 | var n = node 979 | let deps = self.deps[node.name.strVal] 980 | 981 | if deps.inheritance.isSome: 982 | let inheritanceSym = self.findSym(deps.inheritance.get()) 983 | assert inheritanceSym.kind == Interface 984 | var node = self.symCache.getAst(inheritanceSym) 985 | 986 | # let inheritanceDeps 987 | for i in node.sons[2..^1]:# & self.deps[deps.inheritance.get]: 988 | n.add i 989 | 990 | for i in deps.partialMembers: 991 | n.add i 992 | 993 | for i in deps.includes: 994 | let mixinDeps = self.deps[i] 995 | for j in (mixinDeps.mixinMembers & mixinDeps.partialMembers): 996 | n.add j 997 | 998 | self.translatePartialInterface(n) 999 | 1000 | proc translateTypedef(self; node: Node): TranslatedDeclAssembly = 1001 | result.decl = self.translateDeclIdent node[0] 1002 | result.declGenerated = genDistinct( 1003 | result.decl, 1004 | self.translateType node[1] 1005 | ) 1006 | 1007 | proc translateEnum*(self; node: Node): TranslatedDeclAssembly = 1008 | result.decl = self.translateDeclIdent node[0] 1009 | result.declFields = node.sons[1..^1].mapIt: 1010 | tryRemoveExportMarker( 1011 | self.translateDeclIdent Node(kind: Ident, strVal: it.strVal) 1012 | ) 1013 | 1014 | result.declGenerated = genAlias( 1015 | result.decl, 1016 | unode(unkEnumTy).add unode(unkStmtList).add(result.declFields) 1017 | ) 1018 | proc translateCallbackFunction*(self; node, name: Node): TranslatedDeclAssembly = 1019 | var curAssembly = TranslatedDeclAssembly.default 1020 | self.translateOperation(curAssembly, node) 1021 | var procs: seq[NimUNode] = @[] 1022 | for i in curAssembly.bindRoutines: 1023 | procs.add i.toLambda 1024 | 1025 | result.decl = self.translateDeclIdent name 1026 | result.declGenerated = genAlias( 1027 | result.decl, 1028 | nestList(ident"or", procs, unkInfix) 1029 | ) 1030 | 1031 | proc translateCallback*(self; node: Node): TranslatedDeclAssembly = 1032 | assert node.kind == Callback 1033 | 1034 | case (let i = node[1]; i).kind: 1035 | of Operation: 1036 | result = self.translateCallbackFunction(i, node.name) 1037 | else: 1038 | raise newException(CatchableError): 1039 | "Invalid callback" 1040 | 1041 | proc translateWithExtendInterface(self; node: Node): TranslatedDeclAssembly = 1042 | let selfNode = 1043 | unode(unkIdentDefs).add( 1044 | ident"self", 1045 | self.translateType Node( 1046 | kind: Type, 1047 | sons: @[node.name] 1048 | ), 1049 | empty() 1050 | ) 1051 | 1052 | template `*`(n: NimUNode): NimUNode = 1053 | if self.settings.exportCode: 1054 | unode(unkPostfix).add(ident"*", n) 1055 | else: n 1056 | 1057 | 1058 | for i in node.sons[2..^1]: 1059 | case (let i = i.inner; i).kind: 1060 | of Readonly: 1061 | case (let i = i.inner; i).kind: 1062 | of Attribute: 1063 | var attribute = i 1064 | result.bindRoutines.add genRoutine( 1065 | name = self.translateIdent attribute[0], 1066 | returnType = self.translateType attribute[1], 1067 | params = [selfNode], 1068 | pragmas = pragma unode(unkExprColonExpr).add( 1069 | self.importJs, 1070 | strLit("#" & "." & attribute[0].strVal) 1071 | ), 1072 | routineType = unkProcDef 1073 | ) 1074 | else: 1075 | raise newException(CatchableError): 1076 | "maplike or setlike can't be extend existing types" 1077 | of Attribute: 1078 | var attribute = i 1079 | let 1080 | attributeNimName = self.translateIdent(attribute[0]) 1081 | attributeWebidlName = attribute[0] 1082 | attributeType = self.translateType attribute[1] 1083 | 1084 | result.bindRoutines.add genRoutine( 1085 | name = attributeNimName, 1086 | returnType = attributeType, 1087 | params = [selfNode], 1088 | pragmas = pragma unode(unkExprColonExpr).add( 1089 | self.importJs, 1090 | strLit("#" & "." & attributeWebidlName.strVal) 1091 | ), 1092 | routineType = unkProcDef 1093 | ) 1094 | 1095 | result.bindRoutines.add genRoutine( 1096 | name = *unode(unkAccQuoted).add( 1097 | ident(attributeNimName.tryRemoveExportMarker.strVal & "=") 1098 | ), 1099 | params = [ 1100 | selfNode, 1101 | unode(unkIdentDefs).add( 1102 | ident"val", 1103 | attributeType, 1104 | empty() 1105 | ) 1106 | ], 1107 | pragmas = pragma unode(unkExprColonExpr).add( 1108 | self.importJs, 1109 | strLit("#" & "." & attributeWebidlName.strVal & " = #") 1110 | ), 1111 | routineType = unkProcDef 1112 | ) 1113 | else: 1114 | discard 1115 | 1116 | proc translateWithExtend(self; node: Node): TranslatedDeclAssembly = 1117 | assert node.kind in {Partial, Mixin} 1118 | 1119 | case node.kind: 1120 | of Partial: 1121 | let node = node.inner 1122 | case node.kind: 1123 | of Interface: 1124 | result = self.translateWithExtendInterface(node) 1125 | else: 1126 | discard 1127 | else: 1128 | discard 1129 | 1130 | proc translate*(self; node: Node): TranslatedDeclAssembly = 1131 | result = case node.kind: 1132 | of Interface: 1133 | self.translateInterface(node) 1134 | of Dictionary: 1135 | self.translatePartialDictionary(node) 1136 | of Namespace: 1137 | self.translateNamespace(node) 1138 | of Typedef: 1139 | self.translateTypedef(node) 1140 | of Enum: 1141 | self.translateEnum(node) 1142 | of Callback: 1143 | self.translateCallback(node) 1144 | else: 1145 | raise newException(CatchableError, "Invalid decl: " & $node) 1146 | 1147 | discard self.newSym(node.name.strVal, node) 1148 | 1149 | proc getInheritanceOrder*(nodes: seq[Node]; finder: var DepsFinder): seq[string] = 1150 | for i in nodes: 1151 | finder.findDeps(i) 1152 | 1153 | var ct = initCountTable[string]() 1154 | for i in finder.deps.keys: 1155 | finder.countDeps(i, ct) 1156 | ct.sort(Ascending) 1157 | ct.keys.toSeq() 1158 | 1159 | proc genDeclTable(nodes: seq[Node]): Table[string, Node] = 1160 | for i in nodes: 1161 | # partial, includes adds to decl only if not found normal interface 1162 | if i.kind in {Includes, Partial}: 1163 | continue 1164 | 1165 | if i.kind == Empty: 1166 | # dirty hack. Empty must be not in nodes 1167 | continue 1168 | 1169 | result[i.name.strVal] = i 1170 | 1171 | for i in nodes: 1172 | if i.kind in {Includes, Partial} and i.name.strVal notin result: 1173 | result[i.name.strVal] = i 1174 | 1175 | proc translate*(self; nodes: seq[Node], allowUndeclared = false): seq[TranslatedDeclAssembly] = 1176 | var table = nodes.genDeclTable() 1177 | var finder = DepsFinder.init( 1178 | allowUndeclared, 1179 | self.webidlContainsProcs & nimTypes.contains 1180 | ) 1181 | let order = getInheritanceOrder(nodes, finder) 1182 | self.deps = finder.deps 1183 | 1184 | for i in order: 1185 | if ( 1186 | table[i].kind in {Includes, Partial} and 1187 | finder.containsProcs.anyIt( 1188 | it Node(kind: Ident, strVal: i) 1189 | ) 1190 | ): 1191 | # type is already declared in nim so we need to add routines 1192 | # to complete webidl signature 1193 | result.add self.translateWithExtend(table[i]) 1194 | 1195 | if table[i].kind in {Includes, Mixin, Partial}: 1196 | # no need to translate because it only changes exists defs 1197 | continue 1198 | result.add self.translate(table[i]) 1199 | -------------------------------------------------------------------------------- /src/webidl2nim/unode.nim: -------------------------------------------------------------------------------- 1 | import std/[sugar, sequtils] 2 | 3 | type 4 | NimUNodeKind* = enum 5 | unkNone, unkEmpty, unkIdent, unkSym, unkType, unkCharLit, unkIntLit, 6 | unkInt8Lit, unkInt16Lit, unkInt32Lit, unkInt64Lit, unkUIntLit, unkUInt8Lit, 7 | unkUInt16Lit, unkUInt32Lit, unkUInt64Lit, unkFloatLit, unkFloat32Lit, 8 | unkFloat64Lit, unkFloat128Lit, unkStrLit, unkRStrLit, unkTripleStrLit, 9 | unkNilLit, unkComesFrom, unkDotCall, unkCommand, unkCall, unkCallStrLit, 10 | unkInfix, unkPrefix, unkPostfix, unkHiddenCallConv, unkExprEqExpr, 11 | unkExprColonExpr, unkIdentDefs, unkVarTuple, unkPar, unkObjConstr, unkCurly, 12 | unkCurlyExpr, unkBracket, unkBracketExpr, unkPragmaExpr, unkRange, unkDotExpr, 13 | unkCheckedFieldExpr, unkDerefExpr, unkIfExpr, unkElifExpr, unkElseExpr, 14 | unkLambda, unkDo, unkAccQuoted, unkTableConstr, unkBind, unkClosedSymChoice, 15 | unkOpenSymChoice, unkHiddenStdConv, unkHiddenSubConv, unkConv, unkCast, 16 | unkStaticExpr, unkAddr, unkHiddenAddr, unkHiddenDeref, unkObjDownConv, 17 | unkObjUpConv, unkChckRangeF, unkChckRange64, unkChckRange, unkStringToCString, 18 | unkCStringToString, unkAsgn, unkFastAsgn, unkGenericParams, unkFormalParams, 19 | unkOfInherit, unkImportAs, unkProcDef, unkMethodDef, unkConverterDef, 20 | unkMacroDef, unkTemplateDef, unkIteratorDef, unkOfBranch, unkElifBranch, 21 | unkExceptBranch, unkElse, unkAsmStmt, unkPragma, unkPragmaBlock, unkIfStmt, 22 | unkWhenStmt, unkForStmt, unkParForStmt, unkWhileStmt, unkCaseStmt, 23 | unkTypeSection, unkVarSection, unkLetSection, unkConstSection, unkConstDef, 24 | unkTypeDef, unkYieldStmt, unkDefer, unkTryStmt, unkFinally, unkRaiseStmt, 25 | unkReturnStmt, unkBreakStmt, unkContinueStmt, unkBlockStmt, unkStaticStmt, 26 | unkDiscardStmt, unkStmtList, unkImportStmt, unkImportExceptStmt, 27 | unkExportStmt, unkExportExceptStmt, unkFromStmt, unkIncludeStmt, unkBindStmt, 28 | unkMixinStmt, unkUsingStmt, unkCommentStmt, unkStmtListExpr, unkBlockExpr, 29 | unkStmtListType, unkBlockType, unkWith, unkWithout, unkTypeOfExpr, 30 | unkObjectTy, unkTupleTy, unkTupleClassTy, unkTypeClassTy, unkStaticTy, 31 | unkRecList, unkRecCase, unkRecWhen, unkRefTy, unkPtrTy, unkVarTy, unkConstTy, 32 | unkMutableTy, unkDistinctTy, unkProcTy, unkIteratorTy, unkSharedTy, unkEnumTy, 33 | unkEnumFieldDef, unkArgList, unkPattern, unkHiddenTryStmt, unkClosure, 34 | unkGotoState, unkState, unkBreakState, unkFuncDef, unkTupleConstr, unkError ## erroneous AST node 35 | 36 | NimUNode* = ref NimUNodeObj 37 | NimUNodeObj = object 38 | case kind*: NimUNodeKind 39 | of unkNone, unkEmpty, unkNilLit: 40 | discard 41 | of unkCharLit..unkUInt64Lit: 42 | intVal*: BiggestInt 43 | of unkFloatLit..unkFloat64Lit: 44 | floatVal*: BiggestFloat 45 | of unkStrLit..unkTripleStrLit, unkCommentStmt, unkIdent, unkSym: 46 | strVal*: string 47 | else: 48 | sons*: seq[NimUNode] 49 | 50 | const nimKeywords = [ 51 | "addr", "and", "as", "asm", "bind", "block", 52 | "break", "case", "cast", "concept", "const", 53 | "continue", "converter", "defer", "discard", 54 | "distinct", "div", "do", "elif", "else", "end", 55 | "enum", "except", "export", "finally", "for", 56 | "from", "func", "if", "import", "in", "include", 57 | "interface", "is", "isnot", "iterator", "let", "macro", 58 | "method", "mixin", "mod", "nil", "not", "notin", "object", 59 | "of", "or", "out", "proc", "ptr", "raise", "ref", "return", 60 | "shl", "shr", "static", "template", "try", "tuple", 61 | "type", "using", "var", "when", "while", "xor", "yield" 62 | ] 63 | 64 | {.push inline.} 65 | 66 | func len*(node: NimUNode): int = node.sons.len 67 | 68 | func unode*(kind: NimUNodeKind): NimUNode= 69 | NimUNode(kind: kind) 70 | 71 | proc skipNodes*(n: NimUNode, kinds: set[NimUNodeKind]): NimUNode = 72 | result = n 73 | while result.kind in kinds: result = result.sons[0] 74 | 75 | proc add*(self, son: NimUNode): NimUNode {.discardable.} = 76 | self.sons.add(son) 77 | self 78 | 79 | proc add*(father: NimUNode, children: varargs[NimUNode]): NimUNode {.discardable.} = 80 | father.sons.add(children) 81 | result = father 82 | 83 | proc addIfNotEmpty*(self, son: NimUNode): NimUNode {.discardable.} = 84 | if son.kind == unkEmpty or 85 | (son.kind notin {unkCharLit..unkNilLit, unkIdent} and son.sons.len == 0): return self 86 | self.add son 87 | 88 | proc addIfNotEmpty*(self: NimUNode, sons: openArray[NimUNode]): NimUNode {.discardable.} = 89 | if self.kind == unkEmpty: return self 90 | self.add sons 91 | 92 | func `[]`*(self: NimUNode, i: int): var NimUNode = 93 | self.sons[i] 94 | 95 | func `[]=`*(self: var NimUNode, i: int, val: NimUNode) = 96 | self.sons[i] = val 97 | 98 | func empty*(): NimUNode = 99 | NimUNode(kind: unkEmpty) 100 | 101 | func ident*(s: string): NimUNode = 102 | NimUNode(kind: unkIdent, strVal: s) 103 | 104 | func strLit*(s: string): NimUNode = 105 | NimUNode(kind: unkStrLit, strVal: s) 106 | 107 | func intLit*(i: BiggestInt): NimUNode = 108 | NimUNode(kind: unkIntLit, intVal: i) 109 | 110 | func floatLit*(f: BiggestFloat): NimUNode = 111 | NimUNode(kind: unkFloatLit, floatVal: f) 112 | 113 | func makePublic*(n: NimUNode): NimUNode {.discardable.} = 114 | let n = 115 | unode(unkPostfix) 116 | .add(ident("*")) 117 | .add(n) 118 | n 119 | 120 | import std/strutils 121 | 122 | {.pop.} 123 | func nep1Rename(name: string, capitalize: bool): string {.discardable, inline.} = 124 | result = name 125 | 126 | if result.all(x => x.isUpperAscii or x in {'_', '-'}): 127 | result = result.toLower 128 | 129 | if result[0] == '-': 130 | result = result[1..^1] 131 | elif result[0] in {'0'..'9'}: 132 | result = "n_" & result 133 | 134 | if capitalize: 135 | result[0] = result[0].toUpperAscii 136 | 137 | var res: string 138 | for i in 0.. 0 or returnType.kind != unkEmpty: 245 | unode(unkFormalParams).add(returnType).add(params) 246 | else: 247 | empty(), 248 | 249 | pragmas, 250 | empty(), 251 | body) 252 | 253 | # error("Expected one of " & $RoutineNodes & ", got " & $procType) 254 | 255 | func genAlias*(name, base: NimUNode): NimUNode = 256 | # var typeDefName = name 257 | 258 | unode(unkTypeDef).add(name, empty(), base) 259 | 260 | func genDistinct*(name, base: NimUNode): NimUNode = 261 | # var typeDefName = name 262 | let 263 | distinctBody = unode(unkDistinctTy).add(base) 264 | typeDef = unode(unkTypeDef).add(name, empty(), distinctBody) 265 | 266 | typeDef 267 | 268 | func genEnum*( 269 | name: NimUNode, fields: openArray[NimUNode], 270 | pure: bool = true 271 | ): NimUNode = 272 | let enumBody = 273 | unode(unkEnumTy) 274 | .add(empty()) 275 | .add(fields) 276 | 277 | var typeDefArgs = [name, empty(), enumBody] 278 | 279 | if pure: 280 | typeDefArgs[0] = 281 | unode(unkPragmaExpr) 282 | .add(typeDefArgs[0]) 283 | .add(pragma ident("pure")) 284 | 285 | let 286 | typeDef = add(unode(unkTypeDef), typeDefArgs) 287 | 288 | typeDef 289 | 290 | proc nestList*(op: NimUNode; pack: openArray[NimUNode], kind: NimUNodeKind = unkCall): NimUNode = 291 | ## Nests the list `pack` into a tree of call expressions: 292 | ## `[a, b, c]` is transformed into `op(a, op(c, d))`. 293 | ## This is also known as fold expression. 294 | # if pack.len < 1: 295 | # error("`nestList` expects a node with at least 1 child") 296 | result = pack[^1] 297 | for i in countdown(pack.len - 2, 0): 298 | result = 299 | unode(kind) 300 | .add(op) 301 | .add(pack[i], result) 302 | 303 | proc isSimpleOrdinal*(node: NimUNode): bool = 304 | # ?maybe better run nim interpreter via nimscript 305 | assert node.kind == unkIdent 306 | if node.strVal in [ 307 | "byte", 308 | "uint8", 309 | "int8", 310 | "int16", 311 | "uint16", 312 | "int32", 313 | "uint32", 314 | "int64", 315 | "uint64", 316 | "char", 317 | "cint", 318 | "cuint", 319 | "clong", 320 | "culong", 321 | "clonglong", 322 | "culonglong", 323 | "cchar", 324 | "cuchar" 325 | ]: true 326 | else: false 327 | 328 | proc replaceIdentBy*(n: var NimUNode, id, by: NimUNode)= 329 | case n.kind: 330 | of unkIdent: 331 | if n.strVal == id.strVal: 332 | n = by 333 | of unkCharLit..unkUInt64Lit, 334 | unkFloatLit..unkFloat64Lit, 335 | unkStrLit..unkTripleStrLit, 336 | unkCommentStmt, unkSym: discard 337 | else: 338 | for i in 0..= 1.9.1" 15 | requires "regex" 16 | requires "npeg" 17 | requires "cligen" 18 | --------------------------------------------------------------------------------