diff --git a/arkoala-arkts/arkui/arktsconfig-pure-sts.json b/arkoala-arkts/arkui/arktsconfig-pure-sts.json new file mode 100644 index 0000000000000000000000000000000000000000..52c609f78cac46ac855da487b123bb430f98654c --- /dev/null +++ b/arkoala-arkts/arkui/arktsconfig-pure-sts.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "package": "@ohos.arkui", + "outDir": "build/sts/abc", + "baseUrl": "./src/sts" + }, + "files": [ + "./src/sts/arkui.sts" + ] +} diff --git a/arkoala-arkts/arkui/package.json b/arkoala-arkts/arkui/package.json index 60d32802b7093beab5511ad862a922b6664ce6cf..2654aa0db48d4d7adeed9d719dbad7a3c6ed97e5 100644 --- a/arkoala-arkts/arkui/package.json +++ b/arkoala-arkts/arkui/package.json @@ -9,6 +9,7 @@ "clean:arkui-no-common": "npm run clean", "build:arkui-no-common:inc": "npm run unmemoize && fast-arktsc --input-files ./arktsconfig-unmemoized.json --output-dir ./build --compiler ../../incremental/tools/panda/arkts/arktsc --link-name arkui-no-common && ninja ${NINJA_OPTIONS} -f build/build.ninja", "build:arkui-no-common": "npm run unmemoize && fast-arktsc --input-files ./arktsconfig-unmemoized.json --output-dir ./build --compiler ../../incremental/tools/panda/arkts/arktsc --link-name arkui-no-common && ninja ${NINJA_OPTIONS} -f build/build.ninja", - "unmemoize": "ets-tsc -b ./tsconfig-unmemoize.json && cp -r ./src/generated/arkts ./build/unmemoized/src/generated" + "unmemoize": "ets-tsc -b ./tsconfig-unmemoize.json && cp -r ./src/generated/arkts ./build/unmemoized/src/generated", + "build:arkui:pure-sts": "../../incremental/tools/panda/arkts/arktsc --arktsconfig arktsconfig-pure-sts.json" } } diff --git a/arkoala-arkts/arkui/src/sts/arkui.sts b/arkoala-arkts/arkui/src/sts/arkui.sts index e076a007961999eda7a81513006f2a041f22c9f3..06e96582ceba2387cb9fed2d7be6e0e83f046420 100644 --- a/arkoala-arkts/arkui/src/sts/arkui.sts +++ b/arkoala-arkts/arkui/src/sts/arkui.sts @@ -10,81 +10,131 @@ export @interface State {} export @interface memo {} -// Imitation of CommonMethod -export interface CommonMethod { - width(value: number) -} - -export class StructBase { +export abstract class StructBase { @BuilderLambda("instantiateImpl") static $_instantiate, OptionsS> ( factory: () => S, - options: OptionsS + options?: OptionsS, + content?: () => void ): S { - const instance = factory() - return instance + throw new Error("This method should only be called through a @BuilderLambda redirect") } static instantiateImpl, OptionsS>( - builder: (instance: S)=>S, + builder: ((instance: S)=>S)|undefined, factory: () => S, - options: OptionsS + options?: OptionsS, + content?: () => void ) { + console.log("Struct instantiate redirected") const instance = factory() - builder(instance) + if (builder !== undefined) builder(instance) + instance.build() } + abstract build() } export enum Color { White, -Red + Red +} + +export interface ColumnOptions { + space: number } -export interface ColumnOptions {} -export class ArkColumnComponent {} +export class CommonMethod { + onClick(callback: () => void): this { + console.log("registered Button.onClick()") + return this + } + width(value: number): this { + console.log(`.width(${value})`) + return this + } +} // An attempt to put down a component -export class Column { +export class Column extends CommonMethod { + @BuilderLambda("instantiateImpl") static $_instantiate( - factory: () => Column, - options?: ColumnOptions, - content?: () => void - ): ArkColumnComponent { - instance = factory() + factory: () => Column, + options: ColumnOptions, + content: () => void + ): Column { + throw new Error("This method should only be called through a @BuilderLambda redirect") + } + + static instantiateImpl( + style: (instance: Column) => Column, + factory: () => Column, + options: ColumnOptions, + content: () => void + ): void { + console.log("Column({space:", options.space, "})") + const instance = factory() + style(instance) content() - return instance } } + export interface ButtonOptions {} -export class ArkButtonComponent {} // An attempt to put down a component -export class Button { +export class Button extends CommonMethod { + // TODO: the second argument will be gone after hte plugin is improved + @BuilderLambda("instantiateImpl") static $_instantiate( - factory: () => Button, - options?: ButtonOptions, - content?: () => void - ): ArkButtonComponent { - instance = factory() - content() - return instance + factory: () => Button, + options?: ButtonOptions|string, + //content?: () => void + ): Button { + throw new Error("This method should only be called through a @BuilderLambda redirect") + } + + static instantiateImpl( + style: (instance: Button) => Button, + factory: () => Button, + options?: ButtonOptions|string, + //content?: () => void + ): void { + console.log("Button(", options, ")") + const instance = factory() + style(instance) + //content() + } } export interface TextOptions {} -export class ArkTextComponent {} // An attempt to put down a component -export class Text { +export class Text extends CommonMethod { + @BuilderLambda("instantiateImpl") static $_instantiate( - factory: () => Text, - options?: TextOptions, - content?: () => void - ): ArkTextComponent { - instance = factory() - content() - return instance + factory: () => Text, + options?: TextOptions|string, + //content?: () => void + ): Text { + throw new Error("This method should only be called through a @BuilderLambda redirect") } -} + // TODO: the second argument will be gone after hte plugin is improved + static instantiateImpl( + style: (instance: Text) => Text, + factory: () => Text, + options?: TextOptions|string, + //content?: () => void + ): void { + console.log("Text(", options, ")") + const instance = factory() + style(instance) + //content() + } + + fontColor(value: Color): this { + console.log(value) + return this + } +} diff --git a/arkoala-arkts/arkui/src/sts/index.sts b/arkoala-arkts/arkui/src/sts/index.sts new file mode 100644 index 0000000000000000000000000000000000000000..0cef4eb4d11cf59dd26c55d08cb00320297354a4 --- /dev/null +++ b/arkoala-arkts/arkui/src/sts/index.sts @@ -0,0 +1,2 @@ +export * from "./arkui" + diff --git a/arkoala-arkts/libarkts/plugins/input/main.sts b/arkoala-arkts/libarkts/plugins/input/main.sts index ef366ef8bcd8641ea9d493d263ffc338d581ce93..bff61711a10b1179cc5d2bb790e60205e1c1d937 100644 --- a/arkoala-arkts/libarkts/plugins/input/main.sts +++ b/arkoala-arkts/libarkts/plugins/input/main.sts @@ -18,6 +18,7 @@ struct MyComponent { } width(value: number): MyComponent { return this } + build() {} } diff --git a/arkoala-arkts/libarkts/plugins/src/builder-lambda-transformer.ts b/arkoala-arkts/libarkts/plugins/src/builder-lambda-transformer.ts index c2cc52f6bb7502153db9100370863ba69eb9c361..6be93bccdffd798db15b5b2012d3d0f0fb0c7cc7 100644 --- a/arkoala-arkts/libarkts/plugins/src/builder-lambda-transformer.ts +++ b/arkoala-arkts/libarkts/plugins/src/builder-lambda-transformer.ts @@ -18,7 +18,7 @@ import { AbstractVisitor } from "./AbstractVisitor"; const builderLambdaInstanceName = "instance" -function getLambdaArg(lambdaBody: arkts.AstNode): arkts.ArrowFunctionExpression { +function getLambdaArg(lambdaBody: arkts.AstNode, typeName: string|undefined): arkts.ArrowFunctionExpression { const body = arkts.factory.createBlock( [ arkts.factory.createReturnStatement( @@ -31,11 +31,11 @@ function getLambdaArg(lambdaBody: arkts.AstNode): arkts.ArrowFunctionExpression arkts.factory.createIdentifier( builderLambdaInstanceName, // TODO: it should be the return type of the function annotated with the @BuilderLambda - arkts.factory.createTypeReference( - arkts.factory.createIdentifier( - 'MyComponent' - ) - ) + typeName ? arkts.factory.createTypeReference( + arkts.factory.createIdentifier( + typeName + ) + ) : undefined, ), undefined ) @@ -44,7 +44,12 @@ function getLambdaArg(lambdaBody: arkts.AstNode): arkts.ArrowFunctionExpression [ param ], - undefined + // TODO: it should be the return type of the function annotated with the @BuilderLambda + typeName ? arkts.factory.createTypeReference( + arkts.factory.createIdentifier( + typeName + ) + ) : undefined ) const func = arkts.factory.createScriptFunction( @@ -61,22 +66,51 @@ function getLambdaArg(lambdaBody: arkts.AstNode): arkts.ArrowFunctionExpression ) } -function builderLambdaFunctionName(node: arkts.CallExpression): undefined | string { +function isBuilderLambdaAnnotation(annotation: arkts.AnnotationUsageIr): boolean { + if (!arkts.isIdentifier(annotation.expr)) { + return false + } + return annotation.expr.name !== "BuilderLambda" +} + +function builderLambdaArgumentName(annotation: arkts.AnnotationUsageIr): string | undefined { + if (!isBuilderLambdaAnnotation(annotation)) return undefined + + if (!annotation.properties[0].value || + !arkts.isStringLiteral(annotation.properties[0].value) + ) { + return undefined + } + return annotation.properties[0].value.str +} +/* +function builderLambdaTypeName(annotation: arkts.AnnotationUsageIr): string | undefined { + if (!isBuilderLambdaAnnotation(annotation)) return undefined + + if (!annotation.properties[1].value || + !arkts.isStringLiteral(annotation.properties[1].value) + ) { + return undefined + } + return annotation.properties[1].value.str +} +*/ +function findBuilderLambdaAnnotation(node: arkts.CallExpression): arkts.AnnotationUsageIr|undefined { + let decl: arkts.AstNode|undefined = undefined if (arkts.isIdentifier(node.expression)) { decl = arkts.getDecl(node.expression) } else if (arkts.isMemberExpression(node.expression)) { // TODO: getDecl doesn't work for members. // decl = arkts.getDecl(node.expression) - if (arkts.isIdentifier(node.expression.property) && node.expression.property.name == "$_instantiate") { - return `instantiateImpl` - } + return undefined } else { return undefined } if (decl === undefined) { return undefined } + // TODO: why? We need to support functions. if (!arkts.isMethodDefinition(decl)) { return undefined } @@ -85,29 +119,53 @@ function builderLambdaFunctionName(node: arkts.CallExpression): undefined | stri if (declAnnotations.length === 0) { return undefined } - // TODO: check all annotations - const annotation = declAnnotations[0] - if (!arkts.isIdentifier(annotation.expr)) { - return undefined + + return declAnnotations.find(it => isBuilderLambdaAnnotation(it)) +} + + +function builderLambdaFunctionName(node: arkts.CallExpression): string | undefined { + // TODO: remove this special handling when getDecl is capable. + if (arkts.isMemberExpression(node.expression)) { + if (arkts.isIdentifier(node.expression.property) && node.expression.property.name == "$_instantiate") { + return `instantiateImpl` + } } - if (annotation.expr.name !== "BuilderLambda") { - return undefined + + const annotation = findBuilderLambdaAnnotation(node) + if (!annotation) return undefined + return builderLambdaArgumentName(annotation) +} + +function builderLambdaTypeName(node: arkts.CallExpression): string | undefined { + + // TODO: remove this special handling when getDecl is capable. + // this is the only way to know the type + if (arkts.isMemberExpression(node.expression)) { + if (arkts.isIdentifier(node.expression.object)) { + return node.expression.object.name + } } - if (!annotation.properties[0].value || - !arkts.isStringLiteral(annotation.properties[0].value) - ) { - return undefined + + const annotation = findBuilderLambdaAnnotation(node) + if (!annotation) return undefined + if (arkts.isIdentifier(node.expression)) { + return builderLambdaArgumentName(annotation) } - return annotation.properties[0].value.str + return undefined +} + + +function callIsGoodForBuilderLambda(leaf: arkts.CallExpression): boolean { + const node = leaf.expression + return arkts.isIdentifier(node) || arkts.isMemberExpression(node) } function builderLambdaReplace(leaf: arkts.CallExpression): arkts.Identifier|arkts.MemberExpression|undefined { + if (!callIsGoodForBuilderLambda(leaf)) return undefined const node = leaf.expression - if (!arkts.isIdentifier(node) && - !arkts.isMemberExpression(node)) { - return undefined - } + const funcName = builderLambdaFunctionName(leaf) if (funcName === undefined) { return undefined @@ -132,7 +190,6 @@ function builderLambdaReplace(leaf: arkts.CallExpression): arkts.Identifier|arkt return undefined } - export class BuilderLambdaTransformer extends AbstractVisitor { visitor(beforeChildren: arkts.AstNode): arkts.AstNode { const node = this.visitEachChild(beforeChildren) @@ -191,7 +248,8 @@ export class BuilderLambdaTransformer extends AbstractVisitor { ) }) - const lambdaArg = getLambdaArg(lambdaBody) + const typeName = builderLambdaTypeName(leaf) + const lambdaArg = getLambdaArg(lambdaBody, typeName) return arkts.factory.updateCallExpression( node, diff --git a/arkoala-arkts/libarkts/src/arkts-api/utilities/public.ts b/arkoala-arkts/libarkts/src/arkts-api/utilities/public.ts index 2d8ba52e993cc3827d29fcbbc5f3425b793e5e02..ec5c19bfa5e4ffb4ddaad4114e1bf88a715772a0 100644 --- a/arkoala-arkts/libarkts/src/arkts-api/utilities/public.ts +++ b/arkoala-arkts/libarkts/src/arkts-api/utilities/public.ts @@ -55,7 +55,7 @@ export function rebindSubtree(node: AstNode): void { export function getDecl(node: AstNode): AstNode | undefined { let decl: KNativePointer = node.peer - + decl = global.es2panda._AstNodeVariableConst(global.context, decl) if (decl === nullptr) { return undefined diff --git a/arkoala-arkts/libarkts/src/es2panda.ts b/arkoala-arkts/libarkts/src/es2panda.ts index 23d5a91f3bc88f9ab820847fef9ac976e097d0c2..6dd1435ca21c2633e6b3601c0d2d514d64ecfbfd 100644 --- a/arkoala-arkts/libarkts/src/es2panda.ts +++ b/arkoala-arkts/libarkts/src/es2panda.ts @@ -17,7 +17,7 @@ import * as fs from "node:fs" import * as path from "node:path" import { global } from "./arkts-api/static/global" import { Command } from "commander" -import { isNumber, throwError, withWarning } from "./utils" +import { isNumber, throwError, withWarning, filterSource } from "./utils" import { Es2pandaContextState } from "./generated/Es2pandaEnums" import { AstNode, Config, Context, EtsScript, proceedToState } from "./arkts-api" @@ -57,7 +57,7 @@ function insertPlugin(pluginsByState: Map(value: T, message: string): T { export function isNumber(value: any): value is number { return typeof value === `number` } + +// TODO: the lowerings insert %% and other special symbols +// into names of temporary variables. +// Until we keep feeding ast dumps back to the parser +// this function is needed. +export function filterSource(text: string): string { + return text + .replaceAll(/%/g, "_") + .replaceAll(/#/g, "_") + .replaceAll("", "_cctor_") +} + diff --git a/arkoala-arkts/user/arktsconfig-pure-sts.json b/arkoala-arkts/user/arktsconfig-pure-sts.json index 18c1a88e0194e82d602893ba79007b2bc72b7e6d..c6c229b254e72094ce8456824a1142c6d9686e4b 100644 --- a/arkoala-arkts/user/arktsconfig-pure-sts.json +++ b/arkoala-arkts/user/arktsconfig-pure-sts.json @@ -4,11 +4,7 @@ "outDir": "build/sts/abc", "baseUrl": ".", "paths": { - "@ohos.arkui": ["../arkui/src/sts/arkui.sts"], - "@ohos.arkui.Button": ["../arkui/src/sts/arkui.sts"], - "@ohos.arkui.Column": ["../arkui/src/sts/arkui.sts"], - "@ohos.arkui.Color": ["../arkui/src/sts/arkui.sts"], - "@ohos.arkui.Text": ["../arkui/src/sts/arkui.sts"] + "@ohos.arkui": ["../arkui/src/sts"] }, "plugins": [ { @@ -18,6 +14,10 @@ { "transform": "@koalaui/libarkts/plugins/parsed-stage-plugin", "stage": "parsed" + }, + { + "transform": "@koalaui/libarkts/plugins/checked-stage-plugin", + "stage": "checked" } ] }, diff --git a/arkoala-arkts/user/package.json b/arkoala-arkts/user/package.json index e11160f60080ac9ae29578d83500a702c705136a..77272971b5c5049ef7e1abce64a1404e8e3b853d 100644 --- a/arkoala-arkts/user/package.json +++ b/arkoala-arkts/user/package.json @@ -13,7 +13,8 @@ "unmemoize:all": "npm run unmemoize:runtime && npm run unmemoize:arkui-no-common && npm run unmemoize:arkui-common && npm run unmemoize", "build:user": "npm run unmemoize:all && npm run build:user:inc", "build:user:inc": "fast-arktsc --input-files ./arktsconfig-run-unmemoized.json --output-dir ./build --compiler ../../incremental/tools/panda/arkts/arktsc --link-name user && ninja ${NINJA_OPTIONS} -f build/build.ninja", - "build:user:pure-sts": "../../incremental/tools/panda/arkts/arktsc-capi --file src/sts/hello.sts --arktsconfig arktsconfig-pure-sts.json --output build/sts/abc/hello.abc --dump-plugin-ast", + "build:user:pure-sts": "npm run build:arkui:pure-sts --prefix ../arkui && ../../incremental/tools/panda/arkts/arktsc-capi --file src/sts/hello.sts --arktsconfig arktsconfig-pure-sts.json --output build/sts/abc/hello.abc --dump-plugin-ast", + "run:user:pure-sts": "npm run build:user:pure-sts && ../../incremental/tools/panda/arkts/ark build/sts/abc/hello.abc --ark-boot-files ../arkui/build/sts/abc/src/sts/arkui.abc:build/sts/abc/hello.abc --ark-entry-point @ohos.example.src.sts.hello.ArkUIEntry::run", "pack": "npm run cli-tools:check && cd app && DEVECO_SDK_HOME=../../../arkoala/ohos-sdk/ohos-sdk ../command-line-tools/hvigor/bin/hvigorw --no-daemon --mode module -p product=default -p module=user@default assembleHar", "har-arm32": "npm run build:user && npm run --prefix ../../arkoala/ohos-sdk download && node scripts/build-har.mjs --name user --arch arm32 && npm run pack", "har-arm64": "npm run build:user && npm run --prefix ../../arkoala/ohos-sdk download && node scripts/build-har.mjs --name user --arch arm64 && npm run pack", diff --git a/arkoala-arkts/user/src/sts/hello.sts b/arkoala-arkts/user/src/sts/hello.sts index 009e718088d80db99f1166c08cfc7f97c67e34a2..6a41db84cc9c85e5bf7f3151d3dfd55b7dd99cd7 100644 --- a/arkoala-arkts/user/src/sts/hello.sts +++ b/arkoala-arkts/user/src/sts/hello.sts @@ -1,25 +1,38 @@ -import { Text } from "@ohos.arkui.Text" -import { Column, ColumnOptions } from "@ohos.arkui.Column" -import { Button } from "@ohos.arkui.Button" +import { Text } from "@ohos.arkui" +import { Column, ColumnOptions } from "@ohos.arkui" +import { Button } from "@ohos.arkui" import { Component, State, Entry } from "@ohos.arkui" -import { Color } from "@ohos.arkui.Color" +import { Color } from "@ohos.arkui" @Entry @Component struct MyStateSample { @State color: Color = Color.White build() { - Column({} as ColumnOptions) { -/* - Text("") {} + Column({ space: 20 } as ColumnOptions, () => { + Text("Hello World!") .fontColor(this.color) Button('change') .onClick(() => { this.color = Color.Red }) -*/ - } + + }).width(100) } } - +export class ArkUIEntry { + static mandatory: boolean = ArkUIEntry.run() + static run(): boolean { + console.log("About to invoke the struct") + // The proper call fails because of a compiler frontend bug. + // MyStateSample({} as __Options_MyStateSample) + MyStateSample.instantiateImpl( + undefined, + (): MyStateSample => new MyStateSample(), + {} as __Options_MyStateSample, + undefined + ) + return true + } +}