idlize
358 строк · 15.9 Кб
1/*
2* Copyright (c) 2024 Huawei Device Co., Ltd.
3* Licensed under the Apache License, Version 2.0 (the "License");
4* you may not use this file except in compliance with the License.
5* You may obtain a copy of the License at
6*
7* http://www.apache.org/licenses/LICENSE-2.0
8*
9* Unless required by applicable law or agreed to in writing, software
10* distributed under the License is distributed on an "AS IS" BASIS,
11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12* See the License for the specific language governing permissions and
13* limitations under the License.
14*/
15
16import * as path from "path"
17import { IndentedPrinter } from "../../IndentedPrinter";
18import { EnumEntity, PeerFile } from "../PeerFile";
19import { PeerLibrary } from "../PeerLibrary";
20import { Language, isStatic, renameDtsToPeer, throwException } from "../../util";
21import { ImportsCollector } from "../ImportsCollector";
22import { PeerClass, PeerClassBase } from "../PeerClass";
23import { InheritanceRole, determineParentRole, isHeir, isRoot, isStandalone } from "../inheritance";
24import { PeerMethod } from "../PeerMethod";
25import {
26LanguageExpression,
27LanguageWriter,
28Method,
29MethodModifier,
30MethodSignature,
31NamedMethodSignature,
32Type,
33createLanguageWriter
34} from "../LanguageWriters";
35import { MaterializedMethod } from "../Materialized";
36import { collectDtsImports } from "../DtsImportsGenerator";
37import { tsCopyrightAndWarning } from "../FileGenerators";
38
39export function componentToPeerClass(component: string) {
40return `Ark${component}Peer`
41}
42
43function componentToAttributesClass(component: string) {
44return `Ark${component}Attributes`
45}
46
47class PeerFileVisitor {
48readonly printer: LanguageWriter = createLanguageWriter(this.file.declarationTable.language)
49//TODO: Ignore until bugs are fixed in https://rnd-gitlab-msc.huawei.com/rus-os-team/virtual-machines-and-tools/panda/-/issues/17850
50private static readonly ArkTsIgnoredMethods = ["testTupleNumberStringEnum", "testTupleOptional", "testTupleUnion"]
51
52// Temporary, until other languages supported.
53private isTs = this.file.declarationTable.language == Language.TS
54private isArkTs = this.file.declarationTable.language == Language.ARKTS
55
56constructor(
57private readonly library: PeerLibrary,
58private readonly file: PeerFile,
59private readonly dumpSerialized: boolean,
60) { }
61
62get targetBasename() {
63return renameDtsToPeer(path.basename(this.file.originalFilename), this.file.declarationTable.language)
64}
65
66private generatePeerParentName(peer: PeerClass): string {
67if (!peer.originalClassName)
68throw new Error(`${peer.componentName} is not supported, use 'uselessConstructorInterfaces' for now`)
69const parentRole = determineParentRole(peer.originalClassName, peer.parentComponentName)
70if ([InheritanceRole.Finalizable, InheritanceRole.PeerNode].includes(parentRole)) {
71return InheritanceRole[parentRole]
72}
73const parent = peer.parentComponentName ?? throwException(`Expected component to have parent`)
74return componentToPeerClass(parent)
75}
76
77private generateAttributesParentClass(peer: PeerClass): string | undefined {
78if (!isHeir(peer.originalClassName!)) return undefined
79return componentToAttributesClass(peer.parentComponentName!)
80}
81
82private printImports(): void {
83this.getDefaultPeerImports(this.file.declarationTable.language)!.forEach(it => this.printer.print(it))
84if (this.file.declarationTable.language == Language.JAVA) {
85return
86}
87
88const imports = new ImportsCollector()
89imports.addFilterByBasename(this.targetBasename)
90this.file.peersToGenerate.forEach(peer => {
91if (determineParentRole(peer.originalClassName, peer.parentComponentName) === InheritanceRole.PeerNode)
92imports.addFeatureByBasename('PeerNode', 'PeerNode')
93if (peer.originalParentFilename) {
94const parentBasename = renameDtsToPeer(path.basename(peer.originalParentFilename), this.file.declarationTable.language)
95imports.addFeatureByBasename(this.generatePeerParentName(peer), parentBasename)
96const parentAttributesClass = this.generateAttributesParentClass(peer)
97if (parentAttributesClass)
98imports.addFeatureByBasename(parentAttributesClass, parentBasename)
99}
100})
101if (this.file.declarationTable.language === Language.TS
102|| this.file.declarationTable.language === Language.ARKTS) {
103this.file.importFeatures.forEach(it => imports.addFeature(it.feature, it.module))
104this.file.serializeImportFeatures.forEach(it => imports.addFeature(it.feature, it.module))
105imports.addFeature('GestureName', './generated-utils')
106imports.addFeature('GestureComponent', './generated-utils')
107}
108imports.addFeature("unsafeCast", "./generated-utils")
109imports.addFeature("registerCallback", "./SerializerBase")
110Array.from(this.library.builderClasses.keys())
111.forEach((className) => imports.addFeature(className, `./Ark${className}Builder`))
112imports.print(this.printer)
113}
114
115private printAttributes(peer: PeerClass) {
116if (!(this.isTs||this.isArkTs)) return
117for (const attributeType of peer.attributesTypes)
118this.printer.print(attributeType.content)
119
120const parent = this.generateAttributesParentClass(peer)
121this.printer.writeInterface(componentToAttributesClass(peer.componentName), (writer) => {
122for (const field of peer.attributesFields)
123writer.print(field)
124}, parent ? [parent] : undefined)
125}
126
127private printPeerConstructor(peer: PeerClass): void {
128// TODO: fully switch to writer!
129const printer = this.printer
130const parentRole = determineParentRole(peer.originalClassName, peer.originalParentName)
131const isNode = parentRole !== InheritanceRole.Finalizable
132const signature = new NamedMethodSignature(
133Type.Void,
134[new Type('ArkUINodeType', !isNode), new Type('ComponentBase', true), new Type('int32')],
135['type', 'component', 'flags'],
136[undefined, undefined, '0'])
137
138printer.writeConstructorImplementation(componentToPeerClass(peer.componentName), signature, (writer) => {
139if (parentRole === InheritanceRole.PeerNode) {
140writer.writeSuperCall([`type`, 'flags'])
141writer.writeMethodCall('component', 'setPeer', ['this'], true)
142} else if (parentRole === InheritanceRole.Heir || parentRole === InheritanceRole.Root) {
143writer.writeSuperCall([`type`, 'component', 'flags'])
144} else {
145throwException(`Unexpected parent inheritance role: ${parentRole}`)
146}
147})
148}
149
150private printPeerMethod(method: PeerMethod) {
151this.library.declarationTable.setCurrentContext(`${method.originalParentName}.${method.overloadedName}`)
152writePeerMethod(this.printer, method, this.dumpSerialized, "Attribute", "this.peer.ptr")
153this.library.declarationTable.setCurrentContext(undefined)
154}
155
156private printApplyMethod(peer: PeerClass) {
157if (!(this.isTs||this.isArkTs)) return
158const name = peer.originalClassName!
159const typeParam = componentToAttributesClass(peer.componentName)
160if (isRoot(name)) {
161this.printer.print(`applyAttributes(attributes: ${typeParam}): void {}`)
162return
163}
164
165this.printer.print(`applyAttributes<T extends ${typeParam}>(attributes: T): void {`)
166this.printer.pushIndent()
167this.printer.print(`super.applyAttributes(attributes)`)
168this.printer.popIndent()
169this.printer.print(`}`)
170}
171
172private printPeer(peer: PeerClass) {
173this.printer.writeClass(componentToPeerClass(peer.componentName), (writer) => {
174this.printPeerConstructor(peer)
175peer.methods.filter((method) =>
176writer.language == Language.ARKTS ? !PeerFileVisitor.ArkTsIgnoredMethods.includes(method.overloadedName) : true
177).forEach((method) => this.printPeerMethod(method))
178this.printApplyMethod(peer)
179}, this.generatePeerParentName(peer))
180}
181
182printFile(): void {
183this.printImports()
184this.file.peersToGenerate.forEach(peer => {
185this.printPeer(peer)
186this.printAttributes(peer)
187})
188}
189
190private getDefaultPeerImports(lang: Language) {
191switch(lang) {
192case Language.TS: {
193return [
194`import { int32 } from "@koalaui/common"`,
195`import { nullptr, KPointer } from "@koalaui/interop"`,
196`import { isPixelMap, isResource, isInstanceOf, runtimeType, RuntimeType, SerializerBase } from "./SerializerBase"`,
197`import { createSerializer, Serializer } from "./Serializer"`,
198`import { nativeModule } from "@koalaui/arkoala"`,
199`import { ArkUINodeType } from "./ArkUINodeType"`,
200`import { ComponentBase } from "./ComponentBase"`,
201]
202}
203case Language.ARKTS: {
204return [
205`import { int32 } from "@koalaui/common"`,
206`import { nullptr, KPointer } from "@koalaui/interop"`,
207`import { isPixelMap, isResource, isInstanceOf, runtimeType, RuntimeType, SerializerBase } from "./SerializerBase"`,
208`import { createSerializer, Serializer } from "./Serializer"`,
209`import { ArkUINodeType } from "./ArkUINodeType"`,
210`import { ComponentBase } from "./ComponentBase"`,
211`import { NativeModule } from "./NativeModule"`,
212`${collectDtsImports().trim()}`
213]
214}
215case Language.JAVA: {
216return [
217"import org.koalaui.arkoala.*;"
218]
219}
220}
221}
222}
223
224class PeersVisitor {
225readonly peers: Map<string, string[]> = new Map()
226
227constructor(
228private readonly library: PeerLibrary,
229private readonly dumpSerialized: boolean,
230) { }
231
232printPeers(): void {
233for (const file of this.library.files.values()) {
234if (file.peersToGenerate.length) {
235const visitor = new PeerFileVisitor(this.library, file, this.dumpSerialized)
236visitor.printFile()
237this.peers.set(visitor.targetBasename, visitor.printer.getOutput())
238}
239}
240}
241}
242
243const returnValName = "retval" // make sure this doesn't collide with parameter names!
244
245export function printPeers(peerLibrary: PeerLibrary, dumpSerialized: boolean): Map<string, string> {
246const visitor = new PeersVisitor(peerLibrary, dumpSerialized)
247visitor.printPeers()
248const result = new Map<string, string>()
249for (const [key, content] of visitor.peers) {
250if (content.length === 0) continue
251const text = tsCopyrightAndWarning(content.join('\n'))
252result.set(key, text)
253}
254return result
255}
256
257export function printPeerFinalizer(peerClassBase: PeerClassBase, writer: LanguageWriter): void {
258const className = peerClassBase.getComponentName()
259const finalizer = new Method(
260"getFinalizer",
261new MethodSignature(Type.Pointer, []),
262// TODO: private static getFinalizer() method conflicts with its implementation in the parent class
263[MethodModifier.STATIC])
264writer.writeMethodImplementation(finalizer, writer => {
265writer.writeStatement(
266writer.makeReturn(
267writer.makeMethodCall("nativeModule()", `_${className}_getFinalizer`, [])))
268})
269}
270
271export function writePeerMethod(printer: LanguageWriter, method: PeerMethod, dumpSerialized: boolean,
272methodPostfix: string, ptr: string, returnType: Type = Type.Void, generics?: string[]) {
273// Not yet!
274if (printer.language != Language.TS && printer.language != Language.ARKTS) return
275const signature = method.method.signature as NamedMethodSignature
276let peerMethod = new Method(
277`${method.overloadedName}${methodPostfix}`,
278new NamedMethodSignature(returnType, signature.args, signature.argsNames),
279method.method.modifiers, method.method.generics)
280printer.writeMethodImplementation(peerMethod, (writer) => {
281let scopes = method.argConvertors.filter(it => it.isScoped)
282scopes.forEach(it => {
283writer.pushIndent()
284writer.print(it.scopeStart?.(it.param, printer.language))
285})
286let serializerCreated = false
287method.argConvertors.forEach((it, index) => {
288if (it.useArray) {
289if (!serializerCreated) {
290writer.writeStatement(
291writer.makeAssign(`thisSerializer`, new Type('Serializer'),
292writer.makeMethodCall('SerializerBase', 'get', [
293writer.makeString('createSerializer'), writer.makeString(index.toString())
294]), true)
295)
296serializerCreated = true
297}
298it.convertorSerialize(`this`, it.param, writer)
299}
300})
301// Enable to see serialized data.
302if (dumpSerialized) {
303let arrayNum = 0
304method.argConvertors.forEach((it, index) => {
305if (it.useArray) {
306writer.writePrintLog(`"${it.param}:", thisSerializer.asArray(), thisSerializer.length())`)
307}
308})
309}
310let params: LanguageExpression[] = []
311if (method.hasReceiver()) {
312params.push(writer.makeString(ptr))
313}
314let serializerPushed = false
315method.argConvertors.forEach(it => {
316if (it.useArray) {
317if (!serializerPushed) {
318params.push(writer.makeMethodCall(`thisSerializer`, 'asArray', []))
319params.push(writer.makeMethodCall(`thisSerializer`, 'length', []))
320serializerPushed = true
321}
322} else {
323params.push(writer.makeString(it.convertorArg(it.param, writer)))
324}
325})
326let call = writer.makeNativeCall(
327`_${method.originalParentName}_${method.overloadedName}`,
328params)
329if (returnType != Type.Void) {
330writer.writeStatement(writer.makeAssign(returnValName, undefined, call, true))
331} else {
332writer.writeStatement(writer.makeStatement(call))
333}
334scopes.reverse().forEach(it => {
335writer.popIndent()
336writer.print(it.scopeEnd!(it.param, writer.language))
337})
338// TODO: refactor
339if (returnType != Type.Void) {
340let result = returnValName
341if (method.hasReceiver() && returnType === Type.This) {
342result = `this`
343} else if (method instanceof MaterializedMethod && method.peerMethodName !== "ctor") {
344const isStatic = method.method.modifiers?.includes(MethodModifier.STATIC)
345if (!method.hasReceiver()) {
346const retType = signature.returnType
347const obj = `new ${retType.name}(${signature.argsNames.map(it => "undefined").join(", ")})`
348writer.writeStatement(writer.makeAssign("obj", retType, writer.makeString(obj), true))
349writer.writeStatement(
350writer.makeAssign("obj.peer", new Type("Finalizable"),
351writer.makeString(`new Finalizable(${returnValName}, ${method.originalParentName}.getFinalizer())`), false))
352result = "obj"
353}
354}
355writer.writeStatement(writer.makeReturn(writer.makeString(result)))
356}
357})
358}