From 1a9fc7077523429944b0d2fc22b147e1e80ea39c Mon Sep 17 00:00:00 2001 From: katsumiok <50365735+katsumiok@users.noreply.github.com> Date: Sun, 30 Jul 2023 20:06:49 -0400 Subject: [PATCH 1/5] Add .prettierrc.json --- .prettierrc.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .prettierrc.json diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..1eab64e --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "singleQuote": true +} From 706c64272fbb988d1ee7984594fb9c8c543b613e Mon Sep 17 00:00:00 2001 From: katsumiok <50365735+katsumiok@users.noreply.github.com> Date: Sat, 5 Aug 2023 19:19:19 -0400 Subject: [PATCH 2/5] Fix tsconfig.json in README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 76cc19a..1e6bc6f 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,7 @@ Add the following snippet to your `tsconfig.json`: ```json "compilerOptions": { - "plugins": [ - { "transform": "ts-askit" } - ] + "plugins": [{ "transform": "ts-askit/transform" }] } ``` From 7fa3810e3737fc1e185eacced8040b62b997b01f Mon Sep 17 00:00:00 2001 From: katsumiok <50365735+katsumiok@users.noreply.github.com> Date: Wed, 9 Aug 2023 08:29:49 -0400 Subject: [PATCH 3/5] Use the same prompt with pyaskit --- src/gpt.ts | 81 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 18 deletions(-) diff --git a/src/gpt.ts b/src/gpt.ts index 8adf7b0..74295c8 100644 --- a/src/gpt.ts +++ b/src/gpt.ts @@ -107,6 +107,7 @@ function makeMessages( content: question + '\n', }, ]; + // console.log(messages.map((message) => message.content).join('\n')); return messages; } @@ -128,16 +129,21 @@ function askString( } function makeSystemMessage(type: string) { - return `You are a helpful assistant that generates responses in JSON format enclosed with \`\`\`json\n...\n\`\`\` like: + let message = `You are a helpful assistant that generates responses in JSON format enclosed with \`\`\`json and \`\`\` like: \`\`\`json -{ "reason": "Reason for the answer", "answer": "Final answer or result" } +{ "reason": "Step-by-step reason for the answer", "answer": "Final answer or result" } \`\`\` -The answer inside the JSON code block should be given in the type defined as follows: +The response in the JSON code block should be given in the type defined as follows: \`\`\`ts { reason: string; answer: ${type} } \`\`\` -`; +Explain your answer step-by-step in the 'reason' field.`; + if (type === 'string') { + message += + "\nNo additional text should be part of the value in the 'answer' field."; + } + return message; } function askCoding(question: string): Array { @@ -205,21 +211,50 @@ export function extractJson(result: string): any | null { } } -export async function chat( - task: string, - varMap: { [key: string]: any }, +function makeRetryMessage(returnType: t.Type): string { + const type = printType(returnType); + return `Generates responses again in JSON format enclosed with \`\`\`json and \`\`\` like: +\`\`\`json +{ "reason": "Reason for the answer", "answer": "Final answer or result" } +\`\`\` +The response in the JSON code block should be given in the type defined as follows: +\`\`\`ts +{ reason: string; answer: ${type} } +\`\`\` +`; +} + +async function askAndParse( returnType: t.Type, - trainingExamples: ExamplesType -): Promise { - const messages = makeMessages(task, varMap, returnType, trainingExamples); + messages: ChatCompletionRequestMessage[] +): Promise<[T, string, string[], any]> { + let retry = false; + const errors: string[] = []; for (let i = 0; i < 10; i++) { // console.log(messages); const completion = await chatWithRetry('gpt-3.5-turbo-16k', messages); - const result = completion.data.choices[0].message.content; + const content = completion.data.choices[0].message.content; try { - const value = parse(result, returnType); - return value; - } catch (error) { + const [data, reason] = parse(content, returnType); + return [data, reason, errors, completion]; + } catch (error: any) { + errors.push(error.message); + if (retry) { + // Remove the last two element from the messages + messages.splice(-2); + retry = false; + } else { + const s = makeRetryMessage(returnType); + messages.push({ + role: ChatCompletionRequestMessageRoleEnum.Assistant, + content: content, + }); + messages.push({ + role: ChatCompletionRequestMessageRoleEnum.System, + content: s, + }); + retry = true; + } // console.log('failed to parse as JSON: ', result); // console.log('error: ', error); // console.log('retrying...'); @@ -228,10 +263,20 @@ export async function chat( throw new Error('Failed to parse JSON after multiple attempts'); } -function parse(text: string, returnType: t.Type): T | never { +export async function chat( + task: string, + varMap: { [key: string]: any }, + returnType: t.Type, + trainingExamples: ExamplesType +): Promise<[T, string, string[], any]> { + const messages = makeMessages(task, varMap, returnType, trainingExamples); + return askAndParse(returnType, messages); +} + +function parse(text: string, returnType: t.Type): [T, string] | never { const data = extractJson(text); if (data === null) { - throw new Error('Answer is not in a single JSON block'); + throw new Error('Answer is not in a JSON block'); } if (!(data instanceof Object)) { throw new Error('JSON must be an object'); @@ -243,7 +288,7 @@ function parse(text: string, returnType: t.Type): T | never { if (!validate(returnType, value)) { throw new Error(`Output must be of type ${printType(returnType)}`); } - return value; + return [value, 'reason' in data ? data['reason'] : '']; } export async function askCode(message: string): Promise { @@ -258,7 +303,7 @@ function makeQuestion(task: string, varMap: { [key: string]: any }): string { return question; } const varList = Object.entries(varMap).map( - ([key, value]) => ` ${key} = ${JSON.stringify(value)}` + ([key, value]) => ` '${key}' = ${JSON.stringify(value)}` ); return question + '\n where\n' + varList.join('\n'); } From 174743c350be194ddbee39e4848eb64c4daa280d Mon Sep 17 00:00:00 2001 From: katsumiok <50365735+katsumiok@users.noreply.github.com> Date: Wed, 9 Aug 2023 08:32:30 -0400 Subject: [PATCH 4/5] Update for evaluation --- examples/src/top50.ts | 14 ++----------- src/askit.ts | 20 ++++++++++++++++++- src/cli/askgen.ts | 46 ++++++++++++++++++++++++++++++++----------- src/function.ts | 29 +++++++++++++++++---------- src/index.ts | 8 ++++---- 5 files changed, 78 insertions(+), 39 deletions(-) diff --git a/examples/src/top50.ts b/examples/src/top50.ts index f8722b3..098f726 100644 --- a/examples/src/top50.ts +++ b/examples/src/top50.ts @@ -97,7 +97,6 @@ function generateFibonacci(n: number) { const example: Example[] = [{ input: { n: 5 }, output: [0, 1, 1, 2, 3] }]; return ask('Generate the Fibonacci sequence up to {{n}}.', example); } -// ... Previous function definitions are correct ... // Write a function that finds the greatest common divisor of two numbers. function findGreatestCommonDivisor(a: number, b: number) { @@ -163,7 +162,7 @@ function formatDate(d: Date) { ]; return ask( 'Format the date {{d}} into a specific string format.' - // example + /* example */ ); } @@ -177,7 +176,7 @@ function findDateDifference(d1: Date, d2: Date) { ]; return ask( 'Find the difference between the dates {{d1}} and {{d2}}.' - // example + /* example */ ); } @@ -382,12 +381,3 @@ function findMode(ns: number[]) { const example: Example[] = [{ input: { ns: [1, 2, 3, 1, 2, 3] }, output: 1 }]; return ask('Find the mode of the array {{ns}}.', example); } - -// Write a function that checks if an array is sorted in ascending order. -function isSortedAscending(ns: number[]) { - const example: Example[] = [{ input: { ns: [1, 2, 3] }, output: true }]; - return ask( - 'Check if the array {{ns}} is sorted in ascending order.', - example - ); -} diff --git a/src/askit.ts b/src/askit.ts index ed2b742..956dace 100644 --- a/src/askit.ts +++ b/src/askit.ts @@ -3,6 +3,20 @@ import { ExamplesType } from './example'; import { define } from './function'; import * as t from './types'; +let fReason = ''; +let fErrors: string[] = []; +let fCompletion: any = { data: null }; + +export function getReason(): string { + return fReason; +} +export function getErrors(): string[] { + return fErrors; +} +export function getCompletion(): any { + return fCompletion; +} + export async function llm( template: string, examples: ExamplesType = [] @@ -33,7 +47,11 @@ export async function ask(...args: unknown[]): Promise { examples = args[2] as ExamplesType; const argsMap = args[3] as { [key: string]: any }; const f = define(type, template, examples); - const result = f(argsMap); + const result = await f(argsMap); + fReason = f.reason; + fErrors = f.errors; + fCompletion = f.completion; + return result; } throw new Error('Invalid number of arguments'); diff --git a/src/cli/askgen.ts b/src/cli/askgen.ts index 09a7daa..3ba876f 100644 --- a/src/cli/askgen.ts +++ b/src/cli/askgen.ts @@ -242,6 +242,7 @@ function generateFiles(filename: string) { // generate source code const modules = new Set(); info.forEach(async (i: Info) => { + const start = process.hrtime.bigint(); const { modulePath } = makeModuleName(sourceFilePath, i.name); modules.add(i.name); if (fs.existsSync(modulePath)) { @@ -252,6 +253,7 @@ function generateFiles(filename: string) { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } + let testFailedCount = 0; for (let count = 0; count < 10; count++) { const s = await sendChatRequest(make_message(i.desc, i.params)); const m = s.match(/\[([0-9])\]/); @@ -288,11 +290,28 @@ function generateFiles(filename: string) { const jsCode = output.outputText; // console.log('ts code: ', code); // console.log('js code: ', jsCode); - let ok = testFunction(jsCode, i); + const generatedFunc = getFunction(jsCode, i.name); + if (!generatedFunc) { + continue; + } + let ok = testFunction(generatedFunc, i); if (ok) { - writeFileSync(modulePath, code); + const program = `// Recompilation count: ${testFailedCount}}\n${code}`; + writeFileSync(modulePath, program); + const end = process.hrtime.bigint(); + const elapsed = Number(end - start) / 1000000000; console.log('Generated: ', modulePath, count + 1); + writeFileSync( + filename + '.log', + JSON.stringify({ + name: i.name, + elapsed, + count, + }) + ); break; + } else { + testFailedCount++; } } else { console.log(diag); @@ -317,31 +336,34 @@ function generateFiles(filename: string) { // }); } -function testFunction(jsCode: string, i: Info) { - let ok = true; +function getFunction(jsCode: string, functionName: string) { try { const generatedFunc = eval(jsCode); + if (generatedFunc.name !== functionName) { + return false; + } + return generatedFunc; + } catch (e) { + return false; + } +} + +function testFunction(generatedFunc: any, i: Info) { + let ok = true; + try { i.examples.forEach(({ input, output }) => { const args = i.params.map(([_, name]) => input[name]); - // const args: string[] = []; - // for (let name in inputs) { - // args.push(inputs[name]); - // } - //console.log('args: ', args); const actualOutput = generatedFunc(...args); if ( !_.isEqual(actualOutput, output) && !almostEqual(actualOutput, output) // XXX: for float ) { console.log('Failed: ', input, output, actualOutput, i.desc); - console.log('Code: ', jsCode); console.log(JSON.stringify(args)); ok = false; } }); } catch (e) { - console.log(e); - console.log(jsCode); ok = false; } return ok; diff --git a/src/function.ts b/src/function.ts index dd963fa..4047f90 100644 --- a/src/function.ts +++ b/src/function.ts @@ -2,7 +2,6 @@ import { ExamplesType, checkExamples } from './example'; import { generateUniqueFunctionName } from './function-name'; import { chat } from './gpt'; import { convertTemplate, extractVariables } from './template'; -import { printType } from './type-printer'; import * as t from './types'; export class Function { @@ -37,7 +36,7 @@ export class Function { }); } - call(args: { [key: string]: any } = {}): Promise { + async call(args: { [key: string]: any } = {}) { this.checkArgs(args); const convertedTemplate = convertTemplate(this.template); return chat( @@ -55,26 +54,36 @@ export class Function { } } +type DefinedFunctionType = { + (args: { [key: string]: any }): Promise; + reason: string; + errors: string[]; + completion: any; +}; + export function define( type: t.Type, template: string, trainingExamples?: ExamplesType -): (args: { [key: string]: any }) => Promise; +): DefinedFunctionType; export function define( template: string, trainingExamples?: ExamplesType -): (args: { [key: string]: any }) => Promise; -export function define( - ...args: unknown[] -): (args: { [key: string]: any }) => Promise { +): DefinedFunctionType; +export function define(...args: unknown[]): DefinedFunctionType { if (typeof args[0] != 'string') { const type = args[0] as t.Type; const template = args[1] as string; const trainingExamples = (args[2] as ExamplesType) || []; const f = new Function(type, template, trainingExamples); - return function (args: { [key: string]: any } = []) { - return f.call(args); - }; + const g = async function (args: { [key: string]: any } = []) { + const [answer, reason, errors, completion] = await f.call(args); + g.reason = reason; + g.errors = errors; + g.completion = completion; + return answer; + } as DefinedFunctionType; + return g; } throw new Error('defined should be transpiled by AskIt'); } diff --git a/src/index.ts b/src/index.ts index 85ebe4d..0ab3686 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { llm, ask } from './askit'; -export { define } from './function'; -export { ExampleType as Example } from './example'; -export { configure, Config } from './config'; +export { llm, ask, getCompletion, getErrors, getReason } from "./askit"; +export { define } from "./function"; +export { ExampleType as Example } from "./example"; +export { configure, Config } from "./config"; From 3a514d960643beecc919882897dba22af57c7155 Mon Sep 17 00:00:00 2001 From: katsumiok <50365735+katsumiok@users.noreply.github.com> Date: Thu, 17 Aug 2023 22:51:25 -0400 Subject: [PATCH 5/5] Support code generation with 'define' --- examples/src/pbe.ts | 25 ++--- src/askit.ts | 19 ++-- src/cli/askgen.ts | 52 +++++++--- src/function.ts | 34 ++++--- src/info.ts | 3 +- src/transform/transformer.ts | 187 ++++++++++++++++++++++++++++++++--- 6 files changed, 265 insertions(+), 55 deletions(-) diff --git a/examples/src/pbe.ts b/examples/src/pbe.ts index 2d9fde7..053d805 100644 --- a/examples/src/pbe.ts +++ b/examples/src/pbe.ts @@ -1,18 +1,19 @@ -import { ask, Example } from 'ts-askit'; +import { define, Example } from 'ts-askit'; -function addInBase2(x: string, y: string) { - const example: Example[] = [ - { input: { x: '1', y: '0' }, output: '1' }, - { input: { x: '1', y: '1' }, output: '10' }, - { input: { x: '101', y: '11' }, output: '1000' }, - { input: { x: '1001', y: '110' }, output: '1111' }, - { input: { x: '1111', y: '1' }, output: '10000' }, - ]; - return ask('Add {{x}} and {{y}}', example); -} +const example: Example[] = [ + { input: { x: '1', y: '0' }, output: '1' }, + { input: { x: '1', y: '1' }, output: '10' }, + { input: { x: '101', y: '11' }, output: '1000' }, + { input: { x: '1001', y: '110' }, output: '1111' }, + { input: { x: '1111', y: '1' }, output: '10000' }, +]; +const addInBase2 = define( + 'Add {{x}} and {{y}}', + example +); async function doit() { - console.log(await addInBase2('101', '11')); + console.log(await addInBase2({ x: '101', y: '11' })); } doit(); diff --git a/src/askit.ts b/src/askit.ts index 956dace..ab2c803 100644 --- a/src/askit.ts +++ b/src/askit.ts @@ -19,34 +19,39 @@ export function getCompletion(): any { export async function llm( template: string, - examples: ExamplesType = [] + trainingExamples: ExamplesType = [], + testExamples: ExamplesType = [] ): Promise { - return ask(template, examples); + return ask(template, testExamples, trainingExamples); } export async function ask( template: string, - examples?: ExamplesType + trainingExamplesExamples?: ExamplesType, + testExamples?: ExamplesType ): Promise; export async function ask( type: t.Type, template: string, - examples?: ExamplesType, + trainingExamplesExamples?: ExamplesType, + testExamples?: ExamplesType, args?: { [key: string]: any } ): Promise; export async function ask(...args: unknown[]): Promise { let type: t.Type; let template: string; - let examples: ExamplesType; + let trainingExamples: ExamplesType; + let testExamples: ExamplesType; if (typeof args[0] === 'string') { throw new Error('Transpilation with AskIt is needed!'); } else if (args[0] instanceof t.Type) { type = args[0] as t.Type; template = args[1] as string; - examples = args[2] as ExamplesType; + trainingExamples = args[2] as ExamplesType; + testExamples = args[3] as ExamplesType; const argsMap = args[3] as { [key: string]: any }; - const f = define(type, template, examples); + const f = define(type, template, trainingExamples, testExamples); const result = await f(argsMap); fReason = f.reason; fErrors = f.errors; diff --git a/src/cli/askgen.ts b/src/cli/askgen.ts index 3ba876f..fe78c2f 100644 --- a/src/cli/askgen.ts +++ b/src/cli/askgen.ts @@ -232,7 +232,7 @@ if (args.length !== 1) { process.exit(1); } -function generateFiles(filename: string) { +async function generateFiles(filename: string) { const text = readFileSync(filename, 'utf-8'); const lines = text.split('\n'); const info: Info[] = lines.map((line: string) => JSON.parse(line)); @@ -241,7 +241,7 @@ function generateFiles(filename: string) { const sourceFilePath = path.join(dirName, sourceFileName); // generate source code const modules = new Set(); - info.forEach(async (i: Info) => { + for (const i of info) { const start = process.hrtime.bigint(); const { modulePath } = makeModuleName(sourceFilePath, i.name); modules.add(i.name); @@ -294,9 +294,9 @@ function generateFiles(filename: string) { if (!generatedFunc) { continue; } - let ok = testFunction(generatedFunc, i); + let ok = await testFunction(generatedFunc, i); if (ok) { - const program = `// Recompilation count: ${testFailedCount}}\n${code}`; + const program = `// Recompilation count: ${testFailedCount}\n${code}`; writeFileSync(modulePath, program); const end = process.hrtime.bigint(); const elapsed = Number(end - start) / 1000000000; @@ -319,7 +319,7 @@ function generateFiles(filename: string) { } } } - }); + } // delete unused files const baseName = path.basename(filename, '.ts.jsonl'); @@ -348,21 +348,47 @@ function getFunction(jsCode: string, functionName: string) { } } -function testFunction(generatedFunc: any, i: Info) { +function executeWithTimeout( + func: any, + args: any[], + timeout: number +): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + reject(new Error('Timeout')); + }, timeout); + try { + const result = func(...args); + clearTimeout(timer); + resolve(result); + } catch (e) { + clearTimeout(timer); + reject(e); + } + }); +} + +async function testFunction(generatedFunc: any, i: Info) { let ok = true; try { - i.examples.forEach(({ input, output }) => { - const args = i.params.map(([_, name]) => input[name]); - const actualOutput = generatedFunc(...args); + for (const example of i.testExamples) { + const args = i.params.map(([_, name]) => example.input[name]); + const actualOutput = await executeWithTimeout(generatedFunc, args, 60000); if ( - !_.isEqual(actualOutput, output) && - !almostEqual(actualOutput, output) // XXX: for float + !_.isEqual(actualOutput, example.output) && + !almostEqual(actualOutput, example.output) // XXX: for float ) { - console.log('Failed: ', input, output, actualOutput, i.desc); + console.log( + 'Failed: ', + example.input, + example.output, + actualOutput, + i.desc + ); console.log(JSON.stringify(args)); ok = false; } - }); + } } catch (e) { ok = false; } diff --git a/src/function.ts b/src/function.ts index 4047f90..f153acc 100644 --- a/src/function.ts +++ b/src/function.ts @@ -54,35 +54,47 @@ export class Function { } } -type DefinedFunctionType = { - (args: { [key: string]: any }): Promise; +type DefinedFunctionType = { + (args: U): Promise; reason: string; errors: string[]; completion: any; }; -export function define( +export function define< + T, + U extends Record = { [key: string]: any }, +>( type: t.Type, template: string, - trainingExamples?: ExamplesType -): DefinedFunctionType; -export function define( + trainingExamples?: ExamplesType, + testExamples?: ExamplesType +): DefinedFunctionType; +export function define< + T, + U extends Record = { [key: string]: any }, +>( template: string, - trainingExamples?: ExamplesType -): DefinedFunctionType; -export function define(...args: unknown[]): DefinedFunctionType { + trainingExamples?: ExamplesType, + testExamples?: ExamplesType +): DefinedFunctionType; +export function define< + T, + U extends { [key: string]: any } = { [key: string]: any }, +>(...args: unknown[]): DefinedFunctionType { if (typeof args[0] != 'string') { const type = args[0] as t.Type; const template = args[1] as string; const trainingExamples = (args[2] as ExamplesType) || []; const f = new Function(type, template, trainingExamples); - const g = async function (args: { [key: string]: any } = []) { + const g = async function (args: U = {} as U) { + // XXX: as U needed? const [answer, reason, errors, completion] = await f.call(args); g.reason = reason; g.errors = errors; g.completion = completion; return answer; - } as DefinedFunctionType; + } as DefinedFunctionType; return g; } throw new Error('defined should be transpiled by AskIt'); diff --git a/src/info.ts b/src/info.ts index 2d31814..f5a108e 100644 --- a/src/info.ts +++ b/src/info.ts @@ -5,5 +5,6 @@ export type Info = { desc: string; params: [string, string][]; name: string; - examples: ExamplesType; + trainingExamples: ExamplesType; + testExamples: ExamplesType; }; diff --git a/src/transform/transformer.ts b/src/transform/transformer.ts index f4ea835..2d472c7 100644 --- a/src/transform/transformer.ts +++ b/src/transform/transformer.ts @@ -139,19 +139,95 @@ function rewriteDefineCall( if (node.arguments.length > 0 && !ts.isStringLiteralLike(node.arguments[0])) { return node; } - if (node.typeArguments?.length !== 1) { - throwError(node, `expects exactly one type parameter`); + if (node.typeArguments?.length !== 1 && node.typeArguments?.length !== 2) { + throwError(node, `expects exactly two type parameters`); } + const template = node.arguments[0] as ts.StringLiteral; + const variables = extractVariables(template.text); + const variableMapObject = makeVariableMapObject(variables); const returnType = node.typeArguments[0]; - const typeExpression = createTypeExpression(returnType, checker); - const newNode = ts.factory.updateCallExpression( - node, - node.expression, - node.typeArguments, - [typeExpression, ...node.arguments] + // if node.typeArguments.length === 1 param type should be {} + const paramType = + node.typeArguments[1] || ts.factory.createTypeLiteralNode([]); + + const generator = new JsTypeGenerator(); + // const typeString = generator.makeTypeDirection(returnType, checker); + // const typeExpression = makeExpressionFromString(typeString); + + const examplesNode = + node.arguments.length >= 2 + ? node.arguments[1] + : ts.factory.createArrayLiteralExpression([], false); + + const returnTypeString = checker.typeToString( + checker.getTypeAtLocation(returnType) + ); + const name = generateUniqueFunctionName( + // [template.text, ...paramTypeStrings, returnTypeString].join('_'), + [template.text, printNode(paramType), returnTypeString].join('_') ); - return newNode; + if (paramType && !ts.isTypeLiteralNode(paramType)) { + throwError(node, `paramType should be TypeLiteralNode`); + } + const decl = makeSignature2(name, returnType, paramType, variables, checker); + const signature = printNode(decl); + const trainingExamples = ( + node.arguments.length >= 2 && ts.isIdentifier(node.arguments[1]) + ? getIdentifierValue(node.arguments[1], checker) + : [] + ) as ExampleType[]; + const testExamples = ( + node.arguments.length >= 3 && ts.isIdentifier(node.arguments[2]) + ? getIdentifierValue(node.arguments[2], checker) + : [] + ) as ExampleType[]; + info.push({ + signature, + desc: convertTemplate(template.text), + params: variables.map((name) => ['any', name]), // XXX + name, + trainingExamples, + testExamples, + }); + + const sourceFileName = node.getSourceFile().fileName; + const { moduleName, modulePath } = makeModuleName(sourceFileName, name); + + if (fs.existsSync(modulePath)) { + console.log('Found:', moduleName); + + const importStatement = ts.factory.createImportDeclaration( + undefined, + ts.factory.createImportClause( + false, + undefined, + ts.factory.createNamedImports([ + ts.factory.createImportSpecifier( + false, + undefined, + ts.factory.createIdentifier(name) + ), + ]) + ), + ts.factory.createStringLiteral('./askit/' + moduleName) + ); + imports.push(importStatement); + return ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(`${moduleName}_1`), + ts.factory.createIdentifier(name) + ); + } else { + const returnType = node.typeArguments[0]; + const typeExpression = createTypeExpression(returnType, checker); + const newNode = ts.factory.updateCallExpression( + node, + node.expression, + node.typeArguments, + [typeExpression, ...node.arguments] + ); + return newNode; + } } function createTypeExpression( @@ -228,17 +304,23 @@ function rewriteAskCall( paramTypeStrings[i], sym.name, ]); - const examples = ( + const trainingExamples = ( node.arguments.length >= 2 && ts.isIdentifier(node.arguments[1]) ? getIdentifierValue(node.arguments[1], checker) : [] ) as ExampleType[]; + const testExamples = ( + node.arguments.length >= 3 && ts.isIdentifier(node.arguments[2]) + ? getIdentifierValue(node.arguments[2], checker) + : [] + ) as ExampleType[]; info.push({ signature, desc: convertTemplate(template.text), params, name, - examples, + trainingExamples, + testExamples, }); const sourceFileName = node.getSourceFile().fileName; @@ -342,4 +424,87 @@ function makeSignature( return signature; } +function makeSignature2( + name: string, + returnType: ts.TypeNode, + paramType: ts.TypeLiteralNode, + variables: string[], + checker: ts.TypeChecker +) { + // create TypeLiteralNode from paramType + const type = ts.factory.createTypeLiteralNode( + variables.map((name) => { + const member = paramType.members.find( + (member) => member.name?.getText() === name + ); + if (member && ts.isPropertySignature(member)) { + return member; + } else { + return ts.factory.createPropertySignature( + undefined, + name, + undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) + ); + } + }) + ); + + const params = variables.map((name) => { + const member = paramType.members.find( + (member) => member.name?.getText() === name + ); + // initial type is any + let type: ts.TypeNode = ts.factory.createKeywordTypeNode( + ts.SyntaxKind.AnyKeyword + ); + if (member && ts.isPropertySignature(member) && member.type) { + type = member.type; + } + return ts.factory.createBindingElement( + undefined, + undefined, + name, + undefined + ); + }); + + // create biding elements + // const params = symbols.map((sym) => { + // const member = paramType.members.find( + // (member) => member.name?.getText() === sym.name + // ); + // // initial type is any + // let type: ts.TypeNode = ts.factory.createKeywordTypeNode( + // ts.SyntaxKind.AnyKeyword + // ); + // if (member && ts.isPropertySignature(member) && member.type) { + // type = member.type; + // } + // return ts.factory.createBindingElement( + // undefined, + + const objParameter = ts.factory.createParameterDeclaration( + undefined, + undefined, + ts.factory.createObjectBindingPattern(params), + undefined, + type + ); + + const exportModifier = ts.factory.createModifier(ts.SyntaxKind.ExportKeyword); + + const signature = ts.factory.createFunctionDeclaration( + [exportModifier], + undefined, + name, + undefined, + [objParameter], + returnType, + undefined + ); + + return signature; +} + export default transformer;