* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Builders } from "../../ost/builders";
import { D, IdentityTransformer, lw, std, T, utils } from "../../ost";
import { ImportsCollector } from "../../peer-generation/ImportsCollector";
import { mapFileName, moduleName, nativeModuleName } from "../engine/utils";
import { managedName } from "../producers/common";
import { mergeStructs } from "./postprocess";
export function postprocess(decls: lw.LWDeclaration[]): lw.LWDeclaration[] {
decls = mergeNamespaces(decls)
decls = mergeStructs(decls)
decls = introduceTypeChecker(decls)
decls = loadNativeModule(decls)
return decls
}
function introduceTypeChecker(decls: lw.LWDeclaration[]): lw.LWDeclaration[] {
return decls.concat(Builders.class(managedName('engine.TypeChecker')).$())
}
function loadNativeModule(decls: lw.LWDeclaration[]): lw.LWDeclaration[] {
const name = nativeModuleName();
const nativeModule = decls.find(it => it.name == name) as lw.ClassDeclaration
nativeModule.methods.unshift(
Builders.func('').static().block()
.call().functionName('loadNativeModuleLibrary').arg(`"${moduleName('NativeModule')}"`).$().$().$().$())
return decls
}
function mergeNamespaces(decls: lw.LWDeclaration[]): lw.LWDeclaration[] {
const index = new Map<string, lw.NamespaceDeclaration[]>()
const others: lw.LWDeclaration[] = []
decls.forEach(decl => {
if (decl.kind !== lw.LWKind.NamespaceDeclaration) {
others.push(decl)
return
}
if (!index.has(decl.name)) {
index.set(decl.name, [])
}
index.get(decl.name)?.push(decl)
})
const result: lw.LWDeclaration[] = others
index.forEach((records, name) => {
if (records.length === 0) {
return
}
if (records.length === 1) {
result.push(records[0])
return
}
result.push(D.ns(name, mergeNamespaces(records.map(r => r.members).flat())))
})
return result
}
interface ResultFile {
moduleLikeImports: ImportsCollector
body: lw.LWDeclaration[]
}
class RefSearcher extends IdentityTransformer {
private seenNames: Map<string, string[]>
constructor(
private decls: lw.LWDeclaration[],
private fileName: string,
private registry: Map<string, string>,
private imports: ImportsCollector
) {
super()
this.seenNames = new Map(decls.map(it => [it.name, ['.']]))
}
private nsStack: string[] = []
private trimNs(name:string): string {
const prefix = this.nsStack.join('.') + '.'
if (name.startsWith(prefix)) {
return name.substring(prefix.length)
}
return name
}
goNamespaceDeclaration(decl: lw.NamespaceDeclaration): lw.NamespaceDeclaration {
this.nsStack.push(decl.name)
const r = super.goNamespaceDeclaration(decl)
this.nsStack.pop()
return r
}
private getBase(name:string) {
return name.split('.').at(0)!
}
private goTypeName(name: string): string {
if (name.startsWith('@'))
throw new Error('Unhandled builtin type: ' + name)
const record = this.registry.get(name)
if (record) {
let val = name
if (val.startsWith(record)) {
val = val.substring(record.length)
while (val.startsWith('.')) {
val = val.substring(1)
}
}
if (record === this.fileName)
return this.trimNs(val)
const baseName = this.getBase(val);
const source = mapFileName(record)
const conflictingNames = this.seenNames.get(baseName)
if (conflictingNames) {
const alias = source + '_' + baseName
if (!conflictingNames.includes(source)) {
conflictingNames.push(source)
this.imports.addFeature(baseName, source, alias)
}
return this.trimNs(conflictingNames[0] === source ? val : alias)
} else {
this.seenNames.set(baseName, [source])
this.imports.addFeature(baseName, source)
return this.trimNs(val)
}
}
return this.trimNs(name)
}
override goValueType(type: lw.ValueType): lw.LWType {
return type.name.startsWith('@')
? super.goValueType(type)
: T.c(this.goTypeName(type.name), ...type.args.map(t => this.goType(t)))
}
override goConstructorExpression(expr: lw.ConstructorExpression): lw.ConstructorExpression {
expr = super.goConstructorExpression(expr)
expr.name = this.goTypeName(expr.name)
return expr
}
override goVariableExpression(expr: lw.VariableExpression): lw.VariableExpression {
expr = super.goVariableExpression(expr) as lw.VariableExpression
if (utils.hasHint(expr, std.names.hints.isType))
expr.name = this.goTypeName(expr.name)
return expr
}
go(): lw.LWDeclaration[] {
return this.decls.map(it => this.goDeclaration(it))
}
}
function putToNs(declarations:lw.LWDeclaration[]): lw.LWDeclaration[] {
const index = new Map<string, lw.LWDeclaration[]>()
const result: lw.LWDeclaration[] = []
declarations.forEach(decl => {
const clause = decl.name.split('.')
if (clause.length === 1) {
result.push(decl)
return
}
const [base, ...rest] = clause
if (!index.has(base)) {
index.set(base, [])
}
decl.name = rest.join('.')
index.get(base)?.push(decl)
})
index.forEach((decls, name) => {
result.push(D.ns(name, putToNs(decls)))
})
return result
}
export function formFiles(knownPackages: Set<string>, declarations: lw.LWDeclaration[]): Map<string, ResultFile> {
const files = new Map<string, lw.LWDeclaration[]>()
const refIndex = new Map<string, string>()
declarations.forEach(decl => {
const chunks = decl.name.split('.')
const clause: string[] = []
while (chunks.length) {
clause.push(chunks.shift()!)
const prefix = clause.join('.')
if (knownPackages.has(prefix)) {
if (!files.has(prefix)) {
files.set(prefix, [])
}
refIndex.set(decl.name, prefix)
decl.name = chunks.join('.')
files.get(prefix)?.push(decl)
return
}
}
if (!files.has('other')) {
files.set('other', [])
}
files.get('other')?.push(decl)
})
const nsFiles = new Map<string, ResultFile>()
files.forEach((decls, fileName) => {
const imports = defaultImports()
const nsDecls = new RefSearcher(putToNs(decls), fileName, refIndex, imports).go()
nsFiles.set(fileName, {
moduleLikeImports: imports,
body: nsDecls
})
})
return nsFiles
}
function defaultImports(): ImportsCollector {
const imports = new ImportsCollector()
imports.addFeatures([
'SerializerBase', 'DeserializerBase',
'MaterializedBase', 'Finalizable', 'KPointer', 'toPeerPtr',
'loadNativeModuleLibrary',
], '@koalaui/interop')
return imports
}