├── .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 | 
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------