@helios-lang/contract-utils 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/hl2ts.js CHANGED
@@ -1,13 +1,170 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { promises, readFileSync } from "node:fs"
4
+ import { dirname, join, resolve } from "node:path"
5
+ import { readHeader, translateImportPaths } from "@helios-lang/compiler-utils"
6
+ import { TypescriptWriter } from "./codegen/index.js"
3
7
  import { loadLibrary } from "./lib/index.js"
4
8
 
9
+ /**
10
+ * @typedef {import("./lib/Lib.js").Lib} Lib
11
+ */
12
+
13
+ /**
14
+ * @typedef {import("./lib/Lib.js").SourceDetails} SourceDetails
15
+ */
16
+
17
+ /**
18
+ * @typedef {import("./lib/Lib.js").ModuleDetails} ModuleDetails
19
+ */
20
+
21
+ /**
22
+ * @typedef {import("./lib/Lib.js").ValidatorDetails} ValidatorDetails
23
+ */
24
+
5
25
  async function main() {
6
26
  console.log("hl2ts")
7
27
 
8
28
  const lib = await loadLibrary()
29
+ const filePaths = await listFiles(process.cwd(), ".hl")
30
+ const files = readFiles(filePaths)
31
+ const sources = preparseFiles(files)
32
+ const { modules, validators } = typeCheck(lib, sources)
33
+
34
+ const w = new TypescriptWriter()
35
+ console.log(modules, validators)
36
+ w.writeModules(modules)
37
+ w.writeValidators(validators)
38
+ const [js, dts] = w.finalize()
39
+
40
+ console.log(js)
41
+ console.log(dts)
42
+ }
43
+
44
+ main()
9
45
 
10
- console.log(lib.version)
46
+ /**
47
+ * @param {string} dir
48
+ * @param {string} ext
49
+ * @returns {Promise<string[]>}
50
+ */
51
+ async function listFiles(dir, ext) {
52
+ const entries = await promises.readdir(dir, { withFileTypes: true })
53
+
54
+ const files = await Promise.all(
55
+ entries.map((entry) => {
56
+ const res = resolve(dir, entry.name)
57
+
58
+ if (entry.isDirectory()) {
59
+ if (entry.name.endsWith("node_modules")) {
60
+ return []
61
+ } else {
62
+ return listFiles(res, ext)
63
+ }
64
+ } else {
65
+ return res
66
+ }
67
+ })
68
+ )
69
+
70
+ return files.flat().filter((name) => name.endsWith(ext))
71
+ }
72
+
73
+ /**
74
+ * @param {string[]} filePaths
75
+ * @returns {{[path: string]: string}}
76
+ */
77
+ function readFiles(filePaths) {
78
+ return Object.fromEntries(
79
+ filePaths.map((f) => {
80
+ return [f, readFileSync(f).toString()]
81
+ })
82
+ )
11
83
  }
12
84
 
13
- main()
85
+ /**
86
+ *
87
+ * @param {{[path: string]: string}} files
88
+ * @returns {{[name: string]: SourceDetails}}
89
+ */
90
+ function preparseFiles(files) {
91
+ const partialSources = {}
92
+
93
+ for (let path in files) {
94
+ const [purpose, name] = readHeader(files[path])
95
+
96
+ partialSources[path] = {
97
+ name: name,
98
+ purpose: purpose,
99
+ sourceCode: files[path]
100
+ }
101
+ }
102
+
103
+ /**
104
+ * @type {{[name: string]: SourceDetails}}
105
+ */
106
+ const sources = {}
107
+
108
+ for (let path in files) {
109
+ const sourceCode = translateImportPaths(files[path], (relPath) => {
110
+ const absPath = resolve(join(dirname(path), relPath))
111
+
112
+ const d = partialSources[absPath]
113
+
114
+ if (!d) {
115
+ throw new Error(`'${relPath}' not found`)
116
+ }
117
+
118
+ return d.name
119
+ })
120
+
121
+ sources[partialSources[path].name] = {
122
+ ...partialSources[path],
123
+ sourceCode: sourceCode
124
+ }
125
+ }
126
+
127
+ return sources
128
+ }
129
+
130
+ /**
131
+ * @param {{[name: string]: SourceDetails}} sources
132
+ * @returns {[{[name: string]: SourceDetails}, {[name: string]: SourceDetails}]}
133
+ */
134
+ function splitValidatorsAndModules(sources) {
135
+ /**
136
+ * @type {{[name: string]: SourceDetails}}
137
+ */
138
+ const validators = {}
139
+
140
+ /**
141
+ * @type {{[name: string]: SourceDetails}}
142
+ */
143
+ const modules = {}
144
+
145
+ for (let key in sources) {
146
+ const v = sources[key]
147
+ if (v.purpose == "module") {
148
+ modules[key] = v
149
+ } else if (
150
+ v.purpose == "spending" ||
151
+ v.purpose == "minting" ||
152
+ v.purpose == "staking"
153
+ ) {
154
+ validators[key] = v
155
+ }
156
+ }
157
+
158
+ return [validators, modules]
159
+ }
160
+
161
+ /**
162
+ * @param {Lib} lib
163
+ * @param {{[name: string]: SourceDetails}} sources
164
+ * @returns {{modules: {[name: string]: ModuleDetails}, validators: {[name: string]: ValidatorDetails}}}
165
+ */
166
+ function typeCheck(lib, sources) {
167
+ const [validators, modules] = splitValidatorsAndModules(sources)
168
+
169
+ return lib.typeCheck(validators, modules)
170
+ }
package/src/lib/Lib.js CHANGED
@@ -1,13 +1,29 @@
1
1
  /**
2
2
  * @typedef {{
3
3
  * version: string
4
+ * typeCheck: (validators: {[name: string]: SourceDetails}, modules: {[name: string]: SourceDetails}) => ({
5
+ * modules: {[name: string]: ModuleDetails}, validators: {[name: string]: ValidatorDetails}})
4
6
  * }} Lib
5
7
  */
6
8
 
9
+ /**
10
+ * @typedef {import("../codegen/index.js").TypeSchema} InternalTypeDetails
11
+ * @typedef {import("../codegen/index.js").Module} ModuleDetails
12
+ * @typedef {import("../codegen/index.js").Validator} ValidatorDetails
13
+ */
14
+
15
+ /**
16
+ * @typedef {{
17
+ * name: string
18
+ * purpose: string
19
+ * sourceCode: string
20
+ * }} SourceDetails
21
+ */
22
+
7
23
  /**
8
24
  * @param {{VERSION: string}} lib
9
25
  * @returns {number[]}
10
26
  */
11
27
  export function getVersion(lib) {
12
- return lib.VERSION.split(".").map(v => Number(v))
13
- }
28
+ return lib.VERSION.split(".").map((v) => Number(v))
29
+ }
@@ -1,5 +1,110 @@
1
1
  /**
2
2
  * @typedef {import("./Lib.js").Lib} Lib
3
+ * @typedef {import("./Lib.js").SourceDetails} SourceDetails
4
+ */
5
+
6
+ /**
7
+ * @typedef {import("../codegen/index.js").Module} ModuleDetails
8
+ * @typedef {import("../codegen/index.js").Validator} ValidatorDetails
9
+ * @typedef {import("../codegen/index.js").TypeSchema} InternalTypeDetails
10
+ */
11
+
12
+ /**
13
+ * @typedef {{
14
+ * [name: string]: string[]
15
+ * }} DagDependencies
16
+ */
17
+
18
+ /**
19
+ * @typedef {{
20
+ * }} HashType
21
+ */
22
+
23
+ /**
24
+ * @typedef {{
25
+ * includes: (m: string) => boolean
26
+ * }} IR
27
+ */
28
+
29
+ /**
30
+ * @typedef {{
31
+ * name: string
32
+ * }} EnumStatement
33
+ */
34
+
35
+ /**
36
+ * @typedef {{
37
+ * name: string
38
+ * }} StructStatement
39
+ */
40
+
41
+ /**
42
+ * @typedef {StructStatement | EnumStatement | any} Statement
43
+ */
44
+
45
+ /**
46
+ * @typedef {{
47
+ * name: {value: string}
48
+ * filterDependencies: (all: Module[]) => Module[]
49
+ * statements: Statement[]
50
+ * }} Module
51
+ */
52
+ /**
53
+ * @typedef {{
54
+ * name: string
55
+ * toIR: (ctx: any, extra: Map<string, IR>) => IR
56
+ * types: UserTypes
57
+ * mainImportedModules: Module[]
58
+ * mainModule: Module
59
+ * mainArgTypes: DataType[]
60
+ * }} Program
61
+ */
62
+
63
+ /**
64
+ * @typedef {{
65
+ * typeDetails?: TypeDetails
66
+ * }} DataType
67
+ */
68
+
69
+ /**
70
+ * @typedef {{
71
+ * type: string
72
+ * } | {
73
+ * type: "List"
74
+ * itemType: TypeSchema
75
+ * } | {
76
+ * type: "Map"
77
+ * keyType: TypeSchema
78
+ * valueType: TypeSchema
79
+ * } | {
80
+ * type: "Option"
81
+ * someType: TypeSchema
82
+ * } | {
83
+ * type: "Struct"
84
+ * fieldTypes: NamedTypeSchema[]
85
+ * } | {
86
+ * type: "Enum"
87
+ * variantTypes: {name: string, fieldTypes: NamedTypeSchema[]}[]
88
+ * }} TypeSchema
89
+ */
90
+
91
+ /**
92
+ * @typedef {{
93
+ * name: string
94
+ * } & TypeSchema} NamedTypeSchema
95
+ */
96
+
97
+ /**
98
+ * @typedef {{
99
+ * inputType: string
100
+ * outputType: string
101
+ * internalType: TypeSchema
102
+ * }} TypeDetails
103
+ */
104
+
105
+ /**
106
+ * @typedef {{
107
+ * }} UserTypes
3
108
  */
4
109
 
5
110
  /**
@@ -7,7 +112,7 @@
7
112
  */
8
113
  export class Lib_v0_16 {
9
114
  /**
10
- * @param {any} lib
115
+ * @param {any} lib
11
116
  */
12
117
  constructor(lib) {
13
118
  this.lib = lib
@@ -19,4 +124,211 @@ export class Lib_v0_16 {
19
124
  get version() {
20
125
  return this.lib.VERSION
21
126
  }
22
- }
127
+
128
+ /**
129
+ * @private
130
+ * @param {string} purpose
131
+ * @returns {HashType}
132
+ */
133
+ getValidatorType(purpose) {
134
+ switch (purpose) {
135
+ case "spending":
136
+ return this.lib.ValidatorHashType
137
+ case "minting":
138
+ return this.lib.MintingPolicyHashType
139
+ case "staking":
140
+ return this.lib.StakingValidatorHashType
141
+ default:
142
+ throw new Error("unhandled validator type")
143
+ }
144
+ }
145
+
146
+ /**
147
+ * @private
148
+ * @param {SourceDetails[]} validators
149
+ * @param {SourceDetails[]} modules
150
+ * @param {{[name: string]: HashType}} validatorTypes
151
+ * @returns {Program[]}
152
+ */
153
+ createPrograms(validators, modules, validatorTypes) {
154
+ const moduleSrcs = modules.map((m) => m.sourceCode)
155
+
156
+ return validators.map((v) =>
157
+ this.lib.Program.newInternal(
158
+ v.sourceCode,
159
+ moduleSrcs,
160
+ validatorTypes,
161
+ {
162
+ allowPosParams: false,
163
+ invertEntryPoint: true
164
+ }
165
+ )
166
+ )
167
+ }
168
+
169
+ /**
170
+ * @private
171
+ * @param {Program} program
172
+ * @param {{[name: string]: HashType}} validatorTypes
173
+ * @returns {IR}
174
+ */
175
+ toTestIR(program, validatorTypes) {
176
+ const extra = new Map()
177
+
178
+ for (let validatorName in validatorTypes) {
179
+ extra.set(
180
+ `__helios__scripts__${validatorName}`,
181
+ new this.lib.IR(`#`)
182
+ )
183
+ }
184
+
185
+ return program.toIR(new this.lib.ToIRContext(false), extra)
186
+ }
187
+
188
+ /**
189
+ * @private
190
+ * @param {Program[]} validators
191
+ * @param {{[name: string]: HashType}} validatorTypes
192
+ * @returns {DagDependencies}
193
+ */
194
+ buildDagDependencies(validators, validatorTypes) {
195
+ /**
196
+ * @type {DagDependencies}
197
+ */
198
+ const dag = {}
199
+
200
+ validators.forEach((v) => {
201
+ const ir = this.toTestIR(v, validatorTypes)
202
+
203
+ dag[v.name] = Object.keys(validatorTypes).filter((name) =>
204
+ ir.includes(`__helios__scripts__${name}`)
205
+ )
206
+ })
207
+
208
+ return dag
209
+ }
210
+
211
+ /**
212
+ * @param {{[name: string]: SourceDetails}} validators
213
+ * @param {{[name: string]: SourceDetails}} modules
214
+ * @returns {{modules: {[name: string]: ModuleDetails}, validators: {[name: string]: ValidatorDetails}}}
215
+ */
216
+ typeCheck(validators, modules) {
217
+ const validatorTypes = Object.fromEntries(
218
+ Object.values(validators).map((v) => [
219
+ v.name,
220
+ this.getValidatorType(v.purpose)
221
+ ])
222
+ )
223
+
224
+ // create validator programs
225
+ let validatorPrograms = this.createPrograms(
226
+ Object.values(validators),
227
+ Object.values(modules),
228
+ validatorTypes
229
+ )
230
+
231
+ // build dag
232
+ const dag = this.buildDagDependencies(validatorPrograms, validatorTypes)
233
+
234
+ // sort the validators according to the dag, a valid order should exist
235
+ // validatorPrograms = this.sortValidators(validatorPrograms, dag)
236
+
237
+ /**
238
+ * @type {{[name: string]: ValidatorDetails}}
239
+ */
240
+ const validatorDetails = {}
241
+
242
+ /**
243
+ * @type {{[name: string]: ModuleDetails}}
244
+ */
245
+ const moduleDetails = {}
246
+
247
+ for (let v of validatorPrograms) {
248
+ const hashDependencies = dag[v.name]
249
+ const allModules = v.mainImportedModules
250
+ const moduleDepedencies = v.mainModule
251
+ .filterDependencies(allModules)
252
+ .map((m) => m.name.value)
253
+ const purpose = validators[v.name].purpose
254
+
255
+ validatorDetails[v.name] = {
256
+ name: v.name,
257
+ purpose: validators[v.name].purpose,
258
+ sourceCode: validators[v.name].sourceCode,
259
+ hashDependencies: hashDependencies,
260
+ moduleDepedencies: moduleDepedencies,
261
+ types: {},
262
+ Redeemer: this.getInternalTypeDetails(
263
+ v.mainArgTypes[purpose == "spending" ? 1 : 0]
264
+ ),
265
+ Datum:
266
+ purpose == "spending"
267
+ ? this.getInternalTypeDetails(v.mainArgTypes[0])
268
+ : undefined
269
+ }
270
+
271
+ for (let m of v.mainImportedModules) {
272
+ if (!(m.name.value in moduleDetails)) {
273
+ moduleDetails[m.name.value] = {
274
+ name: m.name.value,
275
+ purpose: "module",
276
+ sourceCode: modules[m.name.value].sourceCode,
277
+ moduleDepedencies: m
278
+ .filterDependencies(allModules)
279
+ .map((m) => m.name.value),
280
+ types: {} // not yet exported
281
+ }
282
+ }
283
+ }
284
+ }
285
+
286
+ return { modules: moduleDetails, validators: validatorDetails }
287
+ }
288
+
289
+ /**
290
+ * @param {TypeSchema} it
291
+ * @returns {InternalTypeDetails}
292
+ */
293
+ convertInternalType(it) {
294
+ if ("itemType" in it) {
295
+ return { listItemType: this.convertInternalType(it.itemType) }
296
+ } else if ("someType" in it) {
297
+ return { optionSomeType: this.convertInternalType(it.someType) }
298
+ } else if ("fieldTypes" in it) {
299
+ return {
300
+ structFieldTypes: it.fieldTypes.map((ft) => ({
301
+ name: ft.name,
302
+ type: this.convertInternalType(ft)
303
+ }))
304
+ }
305
+ } else if ("keyType" in it) {
306
+ return {
307
+ mapKeyType: this.convertInternalType(it.keyType),
308
+ mapValueType: this.convertInternalType(it.valueType)
309
+ }
310
+ } else if ("variantTypes" in it) {
311
+ return {
312
+ enumVariantTypes: it.variantTypes.map((vt) => ({
313
+ name: vt.name,
314
+ fieldTypes: vt.fieldTypes.map((ft) => ({
315
+ name: ft.name,
316
+ type: this.convertInternalType(ft)
317
+ }))
318
+ }))
319
+ }
320
+ } else {
321
+ return { primitiveType: it.type }
322
+ }
323
+ }
324
+
325
+ /**
326
+ * @param {DataType} dt
327
+ * @returns {InternalTypeDetails}
328
+ */
329
+ getInternalTypeDetails(dt) {
330
+ const it = dt?.typeDetails?.internalType ?? { type: "Any" }
331
+
332
+ return this.convertInternalType(it)
333
+ }
334
+ }
package/src/lib/index.js CHANGED
@@ -1 +1 @@
1
- export { loadLibrary } from "./load.js"
1
+ export { loadLibrary } from "./load.js"
package/src/lib/load.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { existsSync } from "node:fs"
2
+ import { dirname, join } from "node:path"
1
3
  import { getVersion } from "./Lib.js"
2
4
  import { Lib_v0_16 } from "./Libg_v0_16.js"
3
5
 
@@ -5,33 +7,55 @@ import { Lib_v0_16 } from "./Libg_v0_16.js"
5
7
  * @typedef {import("./Lib.js").Lib} Lib
6
8
  */
7
9
 
8
- const packageNames = [
9
- "@hyperionbt/helios"
10
- ]
10
+ /**
11
+ * @returns {string}
12
+ */
13
+ function findRootDir() {
14
+ let dir = process.cwd()
15
+
16
+ while (dir != "/") {
17
+ if (existsSync(join(dir, "package.json"))) {
18
+ return dir
19
+ } else {
20
+ dir = dirname(dir)
21
+ }
22
+ }
23
+
24
+ throw new Error("package.json not found")
25
+ }
11
26
 
12
27
  /**
13
28
  * @returns {Promise<Lib>}
14
29
  */
15
30
  export async function loadLibrary() {
31
+ const rootDir = findRootDir()
32
+
33
+ const packageNames = ["@hyperionbt/helios/helios.js"]
34
+
16
35
  for (let packageName of packageNames) {
17
36
  try {
18
- const lib = await eval(`import("${packageName}")`)
19
-
37
+ const lib = await eval(
38
+ `import("${rootDir}/node_modules/${packageName}")`
39
+ )
40
+
20
41
  const [major, minor] = getVersion(lib)
21
-
22
- switch(major) {
42
+
43
+ switch (major) {
23
44
  case 0:
24
45
  switch (minor) {
25
46
  case 16:
26
47
  return new Lib_v0_16(lib)
27
48
  }
28
49
  default:
29
- throw new Error(`compiler version ${lib.VERSION} not supported`)
50
+ throw new Error(
51
+ `compiler version ${lib.VERSION} not supported`
52
+ )
30
53
  }
31
- } catch(_e) {
54
+ } catch (_e) {
55
+ console.error(_e)
32
56
  continue
33
57
  }
34
58
  }
35
-
59
+
36
60
  throw new Error("compiler not installed")
37
- }
61
+ }