YyalixieRevert
be574699创建于 3月12日历史提交
/*
 * Copyright (c) 2024 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 * as idl from '../../idl'
import { isOptionalType } from '../../idl'
import { Language } from '../../Language'
import { IndentedPrinter } from "../../IndentedPrinter";
import {
    ACCESS_MODIFIERS_SET,
    AssignStatement,
    BlockStatement,
    DelegationCall,
    DelegationType,
    ExpressionStatement,
    FieldModifier,
    IfStatement,
    LambdaExpression,
    LanguageExpression,
    LanguageStatement,
    LanguageWriter,
    MakeAssignOptions,
    MakeCastOptions,
    Method,
    MethodModifier,
    MethodSignature,
    NamedMethodSignature,
    NamespaceOptions,
    NaryOpExpression,
    ObjectArgs,
    ReturnStatement,
    StringExpression
} from "../LanguageWriter"
import { ArgConvertor } from "../ArgConvertors"
import { IdlNameConvertor } from "../nameConvertor"
import { RuntimeType } from "../common";
import { rightmostIndexOf } from "../../util"
import { ReferenceResolver } from "../../peer-generation/ReferenceResolver";
import { TSKeywords } from '../../languageSpecificKeywords';

////////////////////////////////////////////////////////////////
//                        EXPRESSIONS                         //
////////////////////////////////////////////////////////////////

export class TSLambdaExpression extends LambdaExpression {
    constructor(
        writer: LanguageWriter,
        private convertor: IdlNameConvertor,
        signature: MethodSignature,
        resolver: ReferenceResolver,
        body?: LanguageStatement[]) {
        super(writer, signature, resolver, body)
    }
    protected get statementHasSemicolon(): boolean {
        return false
    }
    asString(): string {
        const params = this.signature.args.map((it, i) => {
            const maybeOptional = idl.isOptionalType(it) ? "?" : ""
            return `${this.signature.argName(i)}${maybeOptional}: ${this.convertor.convert(it)}`
        })

        return `(${params.join(", ")}): ${this.convertor.convert(this.signature.returnType)} =>${this.bodyAsString(true)}`
    }
}

export class TSCastExpression implements LanguageExpression {
    constructor(public value: LanguageExpression, public type: string, private unsafe = false) {}
    asString(): string {
        return this.unsafe
            ? `unsafeCast<${this.type}>(${this.value.asString()})`
            : `(${this.value.asString()} as ${this.type})`
    }
}

class TSUnwrapOptionalExpression implements LanguageExpression {
    constructor(public value: LanguageExpression) {}
    asString(): string {
        return `(${this.value.asString()})!`
    }
}


////////////////////////////////////////////////////////////////
//                         STATEMENTS                         //
////////////////////////////////////////////////////////////////

class TSThrowErrorStatement implements LanguageStatement {
    constructor(public message: string) { }
    write(writer: LanguageWriter): void {
        writer.print(`throw new Error('${this.message}')`)
    }
}
export class TSReturnStatement extends ReturnStatement {
    constructor(public expression: LanguageExpression) { super(expression) }
}

class TSLoopStatement implements LanguageStatement {
    constructor(private counter: string, private limit: string, private statement: LanguageStatement | undefined) {}
    write(writer: LanguageWriter): void {
        writer.print(`for (let ${this.counter} = 0; ${this.counter} < ${this.limit}; ${this.counter}++) {`)
        if (this.statement) {
            writer.pushIndent()
            this.statement.write(writer)
            writer.popIndent()
            writer.print("}")
        }
    }
}

class TSSetForEachStatement implements LanguageStatement {
    constructor(
        private setAccessor: string,
        private elementName: string,
        private body: LanguageStatement[],
    ) {}

    write(writer: LanguageWriter): void {
        writer.print(`for (let ${this.elementName} of ${this.setAccessor}) {`)
        writer.pushIndent()
        this.body.forEach(statement => {
            statement.write(writer)
        })
        writer.popIndent()
        writer.print('}')
    }
}

class TSMapForEachStatement implements LanguageStatement {
    constructor(private map: string, private key: string, private value: string, private body: LanguageStatement[]) {}
    write(writer: LanguageWriter): void {
        writer.print(`for (const [${this.key}, ${this.value}] of ${this.map}) {`)
        writer.pushIndent()
        writer.writeStatement(new BlockStatement(this.body, false))
        writer.popIndent()
        writer.print(`}`)
    }
}

export class TsTupleAllocStatement implements LanguageStatement {
    constructor(private tuple: string) {}
    write(writer: LanguageWriter): void {
        writer.writeStatement(writer.makeAssign(this.tuple, undefined, writer.makeString("[]"), false, false))
    }
}

////////////////////////////////////////////////////////////////
//                           WRITER                           //
////////////////////////////////////////////////////////////////

export class TSLanguageWriter extends LanguageWriter {
    protected typeConvertor: IdlNameConvertor

    constructor(printer: IndentedPrinter,
                resolver: ReferenceResolver,
                typeConvertor: IdlNameConvertor,
                language: Language = Language.TS) {
        super(printer, resolver, language)
        this.typeConvertor = typeConvertor
    }

    maybeSemicolon() { return "" }

    pushNamespace(namespace: string, options: NamespaceOptions): void {
        this.namespaceStack.push(namespace)
        const declaredPrefix = options.isDeclared ? "declare " : ""
        if (options.isDefault) {
            this.print(`export default ${namespace}`)
        }
        this.print(`export ${declaredPrefix}namespace ${namespace} {`)
        if (options.ident) this.pushIndent()
    }

    fork(options?: { resolver?: ReferenceResolver }): LanguageWriter {
        return new TSLanguageWriter(new IndentedPrinter([], this.indentDepth()), options?.resolver ?? this.resolver, this.typeConvertor, this.language)
    }

    getNodeName(type: idl.IDLNode): string {
        // another stub. Bad one.
        // I hope that I will rewrite LWs soon
        if (idl.isType(type) && idl.isReferenceType(type)) {
            if (type.name.startsWith('%TEXT%:')) {
                return type.name.substring(7)
            }
        }
       return this.typeConvertor.convert(type)
    }

    override get interopModule(): string {
        return "@koalaui/interop"
    }

    writeClass(
        name: string,
        op: (writer: this) => void,
        superClass?: string,
        interfaces?: string[],
        generics?: string[],
        isDeclared?: boolean,
        isAbstract?: boolean
    ): void {
        let extendsClause = superClass ? ` extends ${superClass}` : ''
        let implementsClause = interfaces ? ` implements ${interfaces.join(",")}` : ''
        let genericsClause = generics?.length ? `<${generics.join(", ")}>` : ''
        let declaredClause = isDeclared ? ` declare` : ''
        let abstractClause = isAbstract ? ` abstract` : ''
        this.printer.print(`export${declaredClause}${abstractClause} class ${name}${genericsClause}${extendsClause}${implementsClause} {`)
        this.pushIndent()
        this.classOp(() => op(this))
        this.popIndent()
        this.printer.print(`}`)
    }
    override writeInterface(name: string, op: (writer: this) => void, superInterfaces?: string[], generics?: string[], isDeclared?: boolean): void {
        const genericsClause = generics?.length ? `<${generics.join(", ")}>` : ''
        let extendsClause = superInterfaces?.length ? ` extends ${superInterfaces.join(",")}` : ''
        this.printer.print(`export ${isDeclared ? "declare " : ""}interface ${name}${genericsClause}${extendsClause} {`)
        this.pushIndent()
        op(this)
        this.popIndent()
        this.printer.print(`}`)
    }
    writeFunctionDeclaration(name: string, signature: MethodSignature, generics?:string[]): void {
        this.printer.print(this.generateFunctionDeclaration(name, signature, generics))
    }
    writeFunctionImplementation(name: string, signature: MethodSignature, op: (writer: this) => void, generics?:string[]): void {
        this.printer.print(`${this.generateFunctionDeclaration(name, signature, generics)} {`)
        this.printer.pushIndent()
        op(this)
        this.printer.popIndent()
        this.printer.print('}')
    }
    private generateFunctionDeclaration(name: string, signature: MethodSignature, generics?:string[]): string {
        const rightmostRegularParameterIndex = rightmostIndexOf(signature.args, it => !isOptionalType(it))
        const args = signature.args.map((it, index) => {
            const optionalToken = idl.isOptionalType(it) && index > rightmostRegularParameterIndex ? '?' : ''
            return `${signature.argName(index)}${optionalToken}: ${this.getNodeName(it)}`
        })
        const returnType = this.getNodeName(signature.returnType)
        const typeParams = generics && generics.length ? '<' + generics?.join(', ') + '>' : ''
        return `export function ${name}${typeParams}(${args.join(", ")}): ${returnType}`
    }
    writeEnum(name: string, members: { name: string, alias?: string | undefined, stringId: string | undefined, numberId: number }[], options: { isDeclare?: boolean, isExport: boolean }): void {
        this.printer.print(`${options.isExport ? "export " : ""}${options.isDeclare ? "declare " : ""}enum ${name} {`)
        this.printer.pushIndent()
        for (const [index, member] of members.entries()) {
            let value
            if (member.alias !== undefined) {
                value = member.alias
            } else {
                value = `${member.stringId != undefined ? `'${member.stringId}'` : `${member.numberId}`}`
            }
            const maybeComma = index < members.length - 1 ? "," : ""
            this.printer.print(`${member.name} = ${value}${maybeComma}`)
        }
        this.printer.popIndent()
        this.printer.print("}")
    }
    writeFieldDeclaration(name: string, type: idl.IDLType, modifiers: FieldModifier[]|undefined, optional: boolean, initExpr?: LanguageExpression): void {
        if (this.writingClassBody && !modifiers?.some(m => ACCESS_MODIFIERS_SET.has(m))) {
            modifiers = modifiers?.slice() ?? []
            modifiers.unshift(FieldModifier.PUBLIC)
        }
        let prefix = this.makeFieldModifiersList(modifiers)
        if (prefix) prefix += " "
        const typeName = this.getNodeName(type)
        const isGetter = modifiers?.includes(FieldModifier.GET)
        const isSetter = modifiers?.includes(FieldModifier.SET)
        if (isGetter) {
            this.printer.print(`${prefix}get ${name}(): ${typeName}`)
        }
        if (isSetter) {
            this.printer.print(`${prefix}set ${name}(value: ${typeName})`)
        }
        if (isGetter || isSetter) return
        const init = initExpr != undefined ? ` = ${initExpr.asString()}` : ``
        this.printer.print(`${prefix}${name}${optional ? "?"  : ""}: ${typeName}${init}`)
    }
    writeNativeMethodDeclaration(method: Method): void {
        let name = method.name
        let signature = method.signature
        this.writeMethodImplementation(new Method(name, signature, [MethodModifier.STATIC]), writer => {
            const selfCallExpression = writer.makeFunctionCall(
                `this.${name}`,
                signature.args.map((_, i) => writer.makeString(this.escapeKeyword(signature.argName(i))))
            )
            writer.writeStatement(new IfStatement(
                new NaryOpExpression("==", [writer.makeFunctionCall("this._LoadOnce", []), writer.makeString("true")]),
                new BlockStatement([
                    writer.makeReturn(selfCallExpression)
                ]),
                undefined, undefined, undefined
            ))
            writer.writeStatement(writer.makeThrowError("Not implemented"))
        })
    }
    writeMethodDeclaration(name: string, signature: MethodSignature, modifiers?: MethodModifier[]): void {
        this.writeDeclaration(name, signature, true, false, modifiers)
    }
    writeConstructorImplementation(className: string, signature: MethodSignature, op: (writer: this) => void, delegationCall?: DelegationCall, modifiers?: MethodModifier[]) {
        this.writeDeclaration(`${modifiers ? modifiers.map((it) => MethodModifier[it].toLowerCase()).join(' ') + ' ' : ''}constructor`, signature, false, true)
        this.pushIndent()
        if (delegationCall) {
            const delegationType = (delegationCall?.delegationType == DelegationType.THIS) ? "this" : "super"
            this.print(`${delegationType}(${delegationCall.delegationArgs?.map(it => it.asString()).join(", ")})`)
        }
        op(this)
        this.popIndent()
        this.printer.print(`}`)
    }
    writeMethodImplementation(method: Method, op: (writer: this) => void) {
        this.writeDeclaration(method.name, method.signature, true, true, method.modifiers, method.generics)
        this.pushIndent()
        op(this)
        this.popIndent()
        this.printer.print(`}`)
    }
    writeProperty(propName: string, propType: idl.IDLType, modifiers: FieldModifier[], getter?: { method: Method, op: () => void }, setter?: { method: Method, op: () => void }, initExpr?: LanguageExpression): void {
        let isStatic = modifiers.includes(FieldModifier.STATIC)
        let containerName = propName.concat("_container")
        if (getter) {
            if(!getter!.op) {
                this.print(`private var ${this.getNodeName(propType)} ${containerName}`)
            }
            this.writeGetterImplementation(
                new Method(propName, new MethodSignature(propType, []), isStatic ? [MethodModifier.STATIC] : []),
                getter ? getter!.op :
                (writer) => {
                    writer.print(`return ${containerName}`)
                }
            )
        }
        if (setter) {
            const setSignature = new NamedMethodSignature(idl.IDLVoidType, [propType], [propName])
            this.writeSetterImplementation(
                new Method(propName, setSignature, isStatic ? [MethodModifier.STATIC] : []),
                setter ? setter!.op :
                    (writer) => {
                        writer.print(`${containerName} = ${propName}`)
                    }
            )
        }
        if (getter || setter) return
        this.writeFieldDeclaration(propName, propType, modifiers, idl.isOptionalType(propType), initExpr)
    }
    override writeTypeDeclaration(decl: idl.IDLTypedef): void {
        const type = this.getNodeName(decl.type)
        const typeParams = decl.typeParameters?.length ? `<${decl.typeParameters.join(",").replace("[]", "")}>` : ""
        this.print(`export type ${decl.name}${typeParams} = ${type};`)
    }
    writeConstant(constName: string, constType: idl.IDLType, constVal?: string): void {
        this.print(`export const ${constName}: ${this.getNodeName(constType)}${constVal ? ' = ' + constVal : ''}`)
    }
    override writeImports(moduleName: string, importedFeatures: string[], aliases: string[]): void {
        if (importedFeatures.length !== aliases.length) {
            throw new Error(`Inconsistent imports from ${moduleName}`)
        }
        const importNodes: string[] = []
        for (let i = 0; i < importedFeatures.length; i++) {
            importNodes.push(importedFeatures[i] + (aliases[i] ? ` as ${aliases[i]}` : ``))
        }
        this.writeExpressionStatement(this.makeString(`import { ${importNodes.join(', ')} } from '${moduleName}'`))
    }
    private writeDeclaration(name: string, signature: MethodSignature, needReturn: boolean, needBracket: boolean, modifiers?: MethodModifier[], generics?: string[]) {
        let prefix = !modifiers ? undefined : this.supportedModifiers
            .filter(it => modifiers.includes(it))
            .map(it => this.mapMethodModifier(it)).join(" ")
        if (modifiers?.includes(MethodModifier.GETTER)) {
            prefix = `${prefix} get`
        } else if (modifiers?.includes(MethodModifier.SETTER)) {
            prefix = `${prefix} set`
            needReturn = false
        } else if (modifiers?.includes(MethodModifier.FREE)) {
            prefix = `${needBracket ? "" : "declare "}function ${prefix}`
        }
        prefix = prefix ? prefix.trim() + " " : ""
        const typeParams = generics?.length ? `<${generics.join(", ")}>` : ""
        const normalizedArgs = signature.args.map((it, i) =>
            idl.isOptionalType(it) && signature.isArgOptional(i) ? idl.maybeUnwrapOptionalType(it) : it
        )
        this.printer.print(`${prefix}${name}${typeParams}(${normalizedArgs.map((it, index) => `${this.escapeKeyword(signature.argName(index))}${signature.isArgOptional(index) ? "?" : ``}: ${this.getNodeName(it)}${signature.argDefault(index) ? ' = ' + signature.argDefault(index) : ""}`).join(", ")})${needReturn ? ": " + this.getNodeName(signature.returnType) : ""}${needBracket ? " {" : ""}`)
    }
    makeNull(type?: idl.IDLOptionalType): LanguageExpression {
        if (type && idl.hasExtAttribute(type, idl.IDLExtendedAttributes.UnionOnlyNull))
            return new StringExpression("null")
        return new StringExpression("undefined")
    }
    makeAssign(variableName: string, type: idl.IDLType | undefined, expr: LanguageExpression | undefined, isDeclared: boolean = true, isConst: boolean = true, options?:MakeAssignOptions): LanguageStatement {
        return new AssignStatement(variableName, type, expr, isDeclared, isConst, options)
    }
    makeLambda(signature: MethodSignature, body?: LanguageStatement[]): LanguageExpression {
        return new TSLambdaExpression(this, this.typeConvertor, signature, this.resolver, body)
    }
    makeThrowError(message: string): LanguageStatement {
        return new TSThrowErrorStatement(message)
    }
    makeReturn(expr: LanguageExpression): LanguageStatement {
        return new TSReturnStatement(expr)
    }
    makeStatement(expr: LanguageExpression): LanguageStatement {
        return new ExpressionStatement(expr)
    }
    makeLoop(counter: string, limit: string, statement?: LanguageStatement): LanguageStatement {
        return new TSLoopStatement(counter, limit, statement)
    }
    makeMapForEach(map: string, key: string, value: string, body: LanguageStatement[]): LanguageStatement {
        return new TSMapForEachStatement(map, key, value, body)
    }
    writePrintLog(message: string): void {
        this.print(`console.log("${message}")`)
    }
    makeCast(value: LanguageExpression, node: idl.IDLNode, options?: MakeCastOptions): LanguageExpression {
        return new TSCastExpression(value, this.getNodeName(node), options?.unsafe ?? false)
    }
    override instanceOf(value: string, type: idl.IDLType): LanguageExpression {
        return idl.IDLContainerUtils.isSequence(type)
            ? this.makeString(`Array.isArray(${value})`)
            : super.instanceOf(value, type)

    }
    override typeInstanceOf(type: idl.IDLEntry, value: string, members?: string[]): LanguageExpression {

        if (idl.isInterface(type)) {
            if (idl.isInterfaceSubkind(type)) {
                if (!members) {
                    throw new Error("Members must be defined for interface type recognition!")
                }
                return this.makeString(
                    members!.map(it => `${value}.hasOwnProperty("${it}")`).join("&&")
                )
            }
            if (idl.isClassSubkind(type)) {
                return super.typeInstanceOf(type, value, members)
            }
        }
        throw new Error(`typeInstanceOf fails: not class or interface: ${this.getNodeName(type)}`)
    }
    getObjectAccessor(convertor: ArgConvertor, value: string, args?: ObjectArgs): string {
        if (convertor.useArray && args?.index != undefined) {
            return `${value}[${args.index}]`
        }
        return `${value}`
    }
    makeUndefined(): LanguageExpression {
        return this.makeString("undefined")
    }
    makeRuntimeType(rt: RuntimeType): LanguageExpression {
        return this.makeString(`RuntimeType.${RuntimeType[rt]}`)
    }
    makeDefinedCheck(value: string, type?: idl.IDLOptionalType): LanguageExpression {
        if (type) {
            if (idl.hasExtAttribute(type, idl.IDLExtendedAttributes.UnionWithNull)) {
                return this.makeString(`${value} !== undefined && ${value} !== null`)
            } else if (idl.hasExtAttribute(type, idl.IDLExtendedAttributes.UnionOnlyNull)) {
                return this.makeString(`${value} !== null`)
            }
        }
        return this.makeString(`${value} !== undefined`)
    }
    makeTupleAlloc(option: string): LanguageStatement {
        return new TsTupleAllocStatement(option)
    }
    makeArrayInit(type: idl.IDLContainerType, size?:number): LanguageExpression {
        return this.makeString(`new Array<${this.getNodeName(type.elementType[0])}>(${size?.toString() ?? ''})`)
    }
    makeClassInit(type: idl.IDLType, paramenters: LanguageExpression[]): LanguageExpression {
        return this.makeString(`new ${this.getNodeName(type)}(${paramenters.map(it => it.asString()).join(", ")})`)
    }
    makeSetInit(type: idl.IDLType): LanguageExpression {
        return this.makeString(`new Set<${this.getNodeName(type)}>()`)
    }
    makeSetSize(setAccessor: string): LanguageExpression {
        return this.makeFieldAccess(setAccessor, 'size')
    }
    makeSetAdd(setAccessor: string, element: LanguageExpression): LanguageStatement {
        return this.makeStatement(this.makeMethodCall(setAccessor, 'add', [element]))
    }
    makeSetForEach(set: string, element: string, body: LanguageStatement[]): LanguageStatement {
        return new TSSetForEachStatement(set, element, body)
    }
    makeMapInit(type: idl.IDLType): LanguageExpression {
        return this.makeString(`new ${this.getNodeName(type)}()`)
    }
    makeMapInsert(keyAccessor: string, key: string, valueAccessor: string, value: string): LanguageStatement {
        // keyAccessor and valueAccessor are equal in TS
        return this.makeStatement(this.makeMethodCall(keyAccessor, "set", [this.makeString(key), this.makeString(value)]))
    }
    makeUnwrapOptional(expression: LanguageExpression): LanguageExpression {
        return new TSUnwrapOptionalExpression(expression)
    }

    getTagType(): idl.IDLType {
        return idl.createReferenceType("Tags")
    }
    getRuntimeType(): idl.IDLType {
        return idl.IDLI32Type
    }
    makeTupleAssign(receiver: string, fields: string[]): LanguageStatement {
        return this.makeAssign(receiver, undefined,
            this.makeString(`[${fields.map(it=> `${it}!`).join(",")}]`), false)
    }
    get supportedModifiers(): MethodModifier[] {
        return [MethodModifier.PUBLIC, MethodModifier.PRIVATE, MethodModifier.PROTECTED, MethodModifier.STATIC]
    }
    get supportedFieldModifiers(): FieldModifier[] {
        return [FieldModifier.PUBLIC, FieldModifier.PRIVATE, FieldModifier.PROTECTED, FieldModifier.READONLY, FieldModifier.STATIC]
    }
    enumFromI32(value: LanguageExpression, enumEntry: idl.IDLEnum): LanguageExpression {
        const enumName = enumEntry.name
        const ordinal = value.asString()
        return idl.isStringEnum(enumEntry)
            ? this.makeString(`Object.values(${enumName})[${ordinal}]`)
            : this.makeString(ordinal)
    }
    override i32FromEnum(value: LanguageExpression, enumEntry: idl.IDLEnum): LanguageExpression {
        const enumName = this.getNodeName(enumEntry)
        if (idl.isEnum(enumEntry) && idl.isStringEnum(enumEntry)) {
            return this.makeString(`Object.values(${enumName}).indexOf(${value.asString()})`)
        }
        return this.makeString(`${value.asString()}.valueOf()`)
    }
    override castToBoolean(value: string): string { return `!!${value}` }
    override makeCallIsObject(value: string): LanguageExpression {
        return this.makeString(`${value} instanceof Object`)
    }

    override escapeKeyword(keyword: string): string {
        return TSKeywords.has(keyword) ? keyword + "_" : keyword
    }
    override discriminate(value: string, index: number, type: idl.IDLType, runtimeTypes: RuntimeType[]): string {
        const runtimeTypeList = runtimeTypes.map(ty => "RuntimeType." + RuntimeType[ty]).join(", ")
        return `[${runtimeTypeList}].includes(runtimeType(${value}))`
    }

    private writingClassBody = false
    classOp(op:() => void): void {
        const old = this.writingClassBody
        this.writingClassBody = true
        op()
        this.writingClassBody = old
    }
}