* 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 { Hs, D, DD, E, IdentityTransformer, lw, Md, Op, std, T, Ts, utils } from "../../ost";
import { throwError } from "../engine/utils";
import { generatorConfiguration, zipStrip } from "@idlizer/core";
import { mergeStructs } from "./postprocess";
import { Builders } from "../../ost/builders";
import { cApiName, implName } from "../producers/common";
export function postprocess(decls: lw.LWDeclaration[]): Map<string, lw.LWDeclaration[]> {
decls = mergeStructs(decls)
decls = introduceOptionalTypes(decls)
decls = specializeGenerics(decls)
decls = makeApis(decls)
decls = makeForwardDeclarations(decls)
return aliasTypes(decls)
}
class MakeOptional extends IdentityTransformer {
override goStructureDeclaration(decl: lw.StructureDeclaration): lw.StructureDeclaration {
decl.members.forEach(field => {
if (field.modifiers?.includes(Md.optional()))
field.type = T.c('idlize.Opt', field.type)
})
return decl
}
go(decls: lw.LWDeclaration[]) {
return decls.map(decl => this.goDeclaration(decl))
}
}
function introduceOptionalTypes(decls: lw.LWDeclaration[]): lw.LWDeclaration[] {
return new MakeOptional().go(decls)
}
class MakeInstance extends IdentityTransformer {
constructor(
private subst: Map<string, lw.LWType>
) { super() }
goValueType(type: lw.ValueType): lw.LWType {
if (type.args.length) {
return super.goValueType(type)
}
if (this.subst.has(type.name)) {
return this.subst.get(type.name)!
}
return type
}
goDeclaration(decl: lw.LWDeclaration): lw.LWDeclaration {
const processed = super.goDeclaration(decl)
if (processed.kind === lw.LWKind.NamespaceDeclaration) {
return processed
}
processed.generics = []
return processed
}
}
class MakeMono extends IdentityTransformer {
private index = new Map<string, lw.LWDeclaration>()
private newDecls: lw.LWDeclaration[] = []
constructor(
decls: lw.LWDeclaration[]
) {
super()
decls.forEach(decl => {
this.index.set(decl.name, decl)
})
this.index.set(
'idlize.Array',
DD({ generics: [{ name: 'T' }] }).struct('synthetic.mono.Array', [
{ name: 'length', type: Ts.prim.i32 },
{ name: 'value', type: Ts.ptr(T.c('T')) }
])
)
this.index.set(
'idlize.Map',
DD({ generics: [{ name: 'K' }, { name: 'V' }] }).struct('synthetic.mono.Map', [
{ name: 'length', type: Ts.prim.i32 },
{ name: 'keys', type: Ts.ptr(T.c('K')) },
{ name: 'values', type: Ts.ptr(T.c('V')) },
])
)
this.index.set(
'idlize.Opt',
DD({ generics: [{ name: 'T' }] }).struct('synthetic.mono.Optional', [
{ name: 'tag', type: Ts.prim.tag },
{ name: 'value', type: T.c('T') },
])
)
}
private makeSpecializedArgName(type: lw.LWType): string {
switch (type.kind) {
case lw.LWKind.ValueType: return type.name.split('.').pop()!
case lw.LWKind.FunctionalType: throw new Error("")
}
}
private makeSpecializedName(name: string, args: lw.LWType[]): string {
return `synthetic.mono.instance.${name}_${args.map(it => this.makeSpecializedArgName(it)).join('_')}`
}
private specialize(name: string, args: lw.LWType[]): lw.LWType {
const decl = this.index.get(name) ?? throwError(`Not found! "${name}"`)
switch (decl.kind) {
case lw.LWKind.StructureDeclaration: {
const specialName = this.makeSpecializedName(name, args)
if (!this.index.has(specialName)) {
const subst = new Map<string, lw.LWType>()
zipStrip(args, decl.generics).forEach(([arg, gen]) => {
subst.set(gen.name, arg)
})
const instance = this.goDeclaration(
new MakeInstance(subst).goStructureDeclaration(decl)
)
instance.name = specialName
this.newDecls.push(instance)
this.index.set(specialName, instance)
}
return T.c(specialName)
}
}
throw new Error(`Unsupported generic declaration "${name}" of kind "${lw.LWKind[decl.kind]}"`)
}
goValueType(type: lw.ValueType): lw.LWType {
const processed = super.goValueType(type) as lw.ValueType
if (processed.name.startsWith('@') || processed.name.startsWith('#')) {
return processed
}
if (processed.args.length === 0) {
return processed
}
return this.specialize(processed.name, processed.args)
}
go(decls:lw.LWDeclaration[]): lw.LWDeclaration[] {
return decls.map(decl => this.goDeclaration(decl))
.concat(this.newDecls)
}
}
function specializeGenerics(decls: lw.LWDeclaration[]): lw.LWDeclaration[] {
return new MakeMono(decls).go(decls)
}
function makeApis(decls: lw.LWDeclaration[]): lw.LWDeclaration[] {
const apiStructName = cApiName('modifier.API')
const apiStruct = Builders.struct(apiStructName)
.field('version').type(Ts.prim.i32).$()
const modifiers = decls
.filter(it => it.name.startsWith('capi.modifier'))
.map(it => it as lw.StructureDeclaration)
const modifierImpls: lw.FunctionDeclaration[] = []
const apiImpls: lw.LWExpression[] = []
modifiers.forEach(decl => {
const className = decl.name.split('.').pop()!.replace(/Modifier$/, '');
const modifierImplName = implName(decl.name + 'Impl');
apiStruct.field(className)
.funcType().returns(Ts.const(Ts.ptr(T.c(decl.name)))).$().$()
const modifierImpl = Builders.func(modifierImplName)
.returns(Ts.const(Ts.ptr(T.c(decl.name))))
.block()
.decl('instance', T.c(decl.name)).static().value()
.ctor().asStruct().args(
decl.members.map(it => E.unary(Op.ref, E.v(it.name + 'Impl')))).$().$().$()
.return().valueExpr(E.unary(Op.ref, E.v('instance'))).$().$().$()
modifierImpls.push(modifierImpl)
apiImpls.push(E.unary(Op.ref, E.v(modifierImplName, [Hs.isType()])))
})
const apiImpl = Builders.func(implName(`Get${generatorConfiguration().moduleName.toUpperCase()}APIImpl`))
.returns(Ts.const(Ts.ptr(T.c(apiStructName))))
.param('version').type(Ts.prim.i32).$()
.block()
.decl('api', T.c(apiStructName)).static().value()
.ctor().asStruct().args([E.c(1), ...apiImpls]).$().$().$()
.if()
.cond().binary(Op.ne).leftStr('version').right().access(E.v('api')).member('version').$().$().$().$()
.then().return().valueStr('nullptr').$().$().$()
.return().valueExpr(E.unary(Op.ref, E.v('api'))).$().$().$()
return [...decls, apiStruct.$(), ...modifierImpls, apiImpl]
}
function makeForwardDeclarations(decls: lw.LWDeclaration[]): lw.LWDeclaration[] {
const [forward, typedefs, structs] = decls.reduce<[lw.LWDeclaration[], lw.LWDeclaration[], lw.LWDeclaration[]]>(
([fwd, tdef, str], decl) => {
if (decl.kind == lw.LWKind.StructureDeclaration) {
str.push(decl)
fwd.push(D.type(decl.name, Ts.struct(T.c(decl.name))))
} else {
tdef.push(decl)
}
return [fwd, tdef, str]
}, [[], [], []])
return [...forward, ...typedefs, ...structs]
}
class TypeAliasing extends IdentityTransformer {
private readonly ShortPrefix = generatorConfiguration().TypePrefix
private readonly LongPrefix = this.ShortPrefix + generatorConfiguration().moduleName.toUpperCase() + '_'
private conflicts: Set<string> = new Set()
private goTypeName(name: string): string {
const p = (type: string) => this.ShortPrefix + type
switch (name) {
case std.names.types.auto: return 'auto'
case std.names.types.bigint: return p('Int64')
case std.names.types.boolean: return p('Boolean')
case std.names.types.buffer: return p('Buffer')
case std.names.types.f32: return p('Float32')
case std.names.types.f64: return p('Float64')
case std.names.types.i8: return p('Int8')
case std.names.types.i32: return p('Int32')
case std.names.types.i64: return p('Int64')
case std.names.types.number: return p('Number')
case std.names.types.nativePointer: return p('NativePointer')
case std.names.types.object: return p('Object')
case std.names.types.returnBuffer: return 'KInteropReturnBuffer'
case std.names.types.serializerBuffer: return 'KSerializerBuffer'
case std.names.types.string: return p('String')
case std.names.types.u8: return p('Int8')
case std.names.types.u32: return p('UInt32')
case std.names.types.u64: return p('UInt64')
case std.names.types.tag: return p('Tag')
case std.names.types.void: return 'void'
default:
if (name.startsWith('@'))
throw new Error('Unhandled builtin type: ' + name)
}
const path = name.split('.')
if (path.length === 1)
return name
const prefix = path.shift()
const typeName = this.conflicts.has(name)
? path.join('_')
: path[path.length - 1]
return prefix === 'capi' ? this.LongPrefix + typeName : typeName
}
override goValueType(type: lw.ValueType): lw.LWType {
return type.args.length === 0 ? T.c(this.goTypeName(type.name)) : super.goValueType(type)
}
override goEnumDeclaration(decl: lw.EnumDeclaration): lw.EnumDeclaration {
decl = super.goEnumDeclaration(decl)
decl.name = this.goTypeName(decl.name)
return decl
}
override goUnionDeclaration(decl: lw.UnionDeclaration): lw.UnionDeclaration {
decl = super.goUnionDeclaration(decl)
decl.name = this.goTypeName(decl.name)
return decl
}
override goStructureDeclaration(decl: lw.StructureDeclaration): lw.StructureDeclaration {
decl = super.goStructureDeclaration(decl)
decl.name = this.goTypeName(decl.name)
return decl
}
override goClassDeclaration(decl: lw.ClassDeclaration): lw.ClassDeclaration {
decl = super.goClassDeclaration(decl)
decl.name = this.goTypeName(decl.name)
return decl
}
override goTypedefDeclaration(decl: lw.TypedefDeclaration): lw.TypedefDeclaration {
decl = super.goTypedefDeclaration(decl)
decl.name = this.goTypeName(decl.name)
return decl
}
override goFunctionDeclaration(decl: lw.FunctionDeclaration): lw.FunctionDeclaration {
decl = super.goFunctionDeclaration(decl)
decl.name = this.goTypeName(decl.name)
return decl
}
override goVariableExpression(expr: lw.VariableExpression): lw.VariableExpression {
expr = super.goVariableExpression(expr) as lw.VariableExpression
expr.name = utils.hasHint(expr, std.names.hints.isType)
? this.goTypeName(expr.name)
: expr.name
return expr
}
go(decls: lw.LWDeclaration[]) {
const seenNames: Map<string, string[]> = new Map()
decls.forEach(decl => {
const path = decl.name.split('.')
let name = path[path.length - 1]
const conflictingNames = seenNames.get(name)
if (conflictingNames) {
if (!conflictingNames.includes(decl.name))
conflictingNames.push(decl.name)
} else {
seenNames.set(name, [decl.name])
}
})
this.conflicts = new Set(
Array.from(seenNames.entries())
.filter(([_, names]) => names.length > 1)
.flatMap(([_, names]) => names))
const files: Map<string, lw.LWDeclaration[]> = new Map()
decls.forEach(decl => {
const file = decl.name.split('.').shift()!
const content = files.get(file)
const image = this.goDeclaration(decl)
if (!content)
files.set(file, [image])
else
content.push(image)
})
return files
}
}
function aliasTypes(decls: lw.LWDeclaration[]): Map<string, lw.LWDeclaration[]> {
return new TypeAliasing().go(decls)
}