├── package.json ├── examples ├── user_greeting.neuro ├── hello_world.neuro └── animals.neuro ├── src ├── OpenAi.ts ├── cli.ts ├── generator.ts └── parser.ts ├── LICENSE ├── .gitignore ├── README.md └── tsconfig.json /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@eeue56/baner": "^0.0.3", 4 | "dotenv": "^16.0.3", 5 | "openai": "^3.2.1" 6 | }, 7 | "devDependencies": { 8 | "@types/node": "^18.15.11" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/user_greeting.neuro: -------------------------------------------------------------------------------- 1 | type Person = { 2 | name: string; 3 | } 4 | 5 | function greeting(user: Person): string { 6 | // return and say hi to the user 7 | } 8 | 9 | pinned function main() { 10 | // greet a user with the name "Noah" 11 | } -------------------------------------------------------------------------------- /examples/hello_world.neuro: -------------------------------------------------------------------------------- 1 | type Person = { 2 | name: string; 3 | } 4 | 5 | function add(a: number, b: number): number { 6 | // Add two numbers together 7 | } 8 | 9 | function main() { 10 | // Print "Hello World" to the console 11 | // Print the result of add(2, 3) 12 | } -------------------------------------------------------------------------------- /src/OpenAi.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | import { Configuration, OpenAIApi } from "openai"; 3 | 4 | dotenv.config(); 5 | 6 | const configuration = new Configuration({ 7 | apiKey: process.env.OPENAI_API_KEY || "", 8 | }); 9 | 10 | export const openai = new OpenAIApi(configuration); 11 | -------------------------------------------------------------------------------- /examples/animals.neuro: -------------------------------------------------------------------------------- 1 | type Cat = { 2 | lives: number; 3 | } 4 | 5 | type Dog = { 6 | tailWags: number; 7 | } 8 | 9 | type Error = { 10 | kind: "Error"; 11 | value: error; 12 | } 13 | 14 | type Ok = { 15 | kind: "Ok"; 16 | value: ok; 17 | } 18 | 19 | union type Result = Error | Ok; 20 | 21 | union type Animal = Cat | Dog; 22 | 23 | function parseCat(line: string): Result { 24 | // write a function that parses a line, looking for lives: number 25 | // where number is a number 26 | // if found, return a Cat object 27 | // otherwise return null 28 | } 29 | 30 | function parseDog(line: string): Result { 31 | // write a function that parses a line, looking for tailWags: number 32 | // where number is a number 33 | // if found, return a Dog object 34 | // otherwise return null 35 | } 36 | 37 | function parseLine(line: string): Result { 38 | // write a function that parses a line into an Animal using parseCat and parseDog 39 | // If it does not match either parseCat or parseDog, return null 40 | } 41 | 42 | function testParseLine() { 43 | // write a function that will test parseLine for edge cases using node's assert module 44 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Noah 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | build/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # neuro-lingo 2 | 3 | A language where you only implement comments and types, and let LLMs deal with the rest. LLMs don't always provide workable examples, so your comments need to really match what you're trying to implement. I [wrote about this on Derw's blog](https://derw.substack.com/p/introducing-neuro-lingo-a-llm-powered). 4 | 5 | ## Filenames 6 | 7 | Neuro files end with .neuro 8 | 9 | ## Hello world 10 | 11 | ```typescript 12 | function main() { 13 | // Print "Hello World" to the console 14 | } 15 | ``` 16 | 17 | when run produces 18 | 19 | ``` 20 | Hello World 21 | ``` 22 | 23 | ## Adding two numbers 24 | 25 | ```typescript 26 | function add(a: number, b: number): number { 27 | // Add two numbers together 28 | } 29 | 30 | function main() { 31 | // Print "Hello World" to the console 32 | // Print the result of add(2, 3) 33 | } 34 | ``` 35 | 36 | when run produces 37 | 38 | ``` 39 | Hello World 40 | 5 41 | ``` 42 | 43 | ## Types 44 | 45 | ```typescript 46 | type Person = { 47 | name: string; 48 | }; 49 | ``` 50 | 51 | ## Acknowledging a user 52 | 53 | ```typescript 54 | type Person = { 55 | name: string; 56 | }; 57 | 58 | function greeting(user: Person): string { 59 | // return and say hi to the user 60 | } 61 | 62 | function main() { 63 | // greet a user with the name "Noah" 64 | } 65 | ``` 66 | 67 | when run produces 68 | 69 | ``` 70 | Hi Noah, nice to meet you! 71 | ``` 72 | 73 | ## Pinning a function 74 | 75 | In order to use a previous implementation from the LLM, you can use the pinned keyword which will attempt to look up the previous implementation of that function. For consistent results I recommend git-adding the build files along with `pinned`. 76 | 77 | ```typescript 78 | pinned function main() { 79 | // greet a user with the name "Noah" 80 | } 81 | ``` 82 | 83 | # Installing 84 | 85 | Clone the repo and run ts-node src/cli.ts. 86 | 87 | Create an .env file with your OPENAI_API_KEY. 88 | You can also set OPENAI_GPT_MODEL to use an alternative model. The default is gpt-3.5-turbo. 89 | 90 | ## Using the CLI 91 | 92 | ```bash 93 | --file string: The filename to compile 94 | --quiet : Avoid writing non-esstential output to terminal 95 | -h, --help : This help text 96 | --run : Run the file after generating it 97 | ``` 98 | 99 | # Notes 100 | 101 | You can modify the prompts in [src/generate.ts](src/generator.ts) to your liking - if you find a better one, please open a pull request. 102 | 103 | neuro-lingo is absolutely not recommened for use in productiong. It is a thought exercise which just so happens to have a functioning compiler. 104 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | import { 2 | allErrors, 3 | bothFlag, 4 | empty, 5 | help, 6 | longFlag, 7 | parse, 8 | parser, 9 | string, 10 | } from "@eeue56/baner"; 11 | import { spawnSync } from "child_process"; 12 | import { mkdir, readFile, writeFile } from "fs/promises"; 13 | import path from "path"; 14 | import { generateProgram } from "./generator"; 15 | import { parseFile } from "./parser"; 16 | 17 | const cliParser = parser([ 18 | longFlag("file", "The filename to compile ", string()), 19 | longFlag("quiet", "Avoid writing non-esstential output to terminal", empty()), 20 | longFlag("output", "An output file location", string()), 21 | bothFlag("h", "help", "This help text", empty()), 22 | longFlag("run", "Run the file after generating it", empty()), 23 | ]); 24 | 25 | function runFile(fullName: string): void { 26 | let child; 27 | child = spawnSync(`npx`, [`ts-node`, `${fullName}`], { 28 | stdio: "inherit", 29 | encoding: "utf-8", 30 | }); 31 | } 32 | 33 | async function main() { 34 | const cliProgram = parse(cliParser, process.argv); 35 | 36 | if (cliProgram.flags["h/help"].isPresent) { 37 | console.log(help(cliParser)); 38 | return; 39 | } 40 | 41 | if (allErrors(cliProgram).length > 0) { 42 | console.log("Error"); 43 | console.log(allErrors(cliProgram).join("\n")); 44 | return; 45 | } 46 | 47 | const fileNameFlag = cliProgram.flags["file"]; 48 | 49 | if (!fileNameFlag.isPresent || fileNameFlag.arguments.kind === "Err") { 50 | console.error("Provide a filename to compile via --file"); 51 | return; 52 | } 53 | 54 | const fileName: string = (fileNameFlag.arguments.value as string) || ""; 55 | const providedOutputFileName = 56 | cliProgram.flags["output"].isPresent && 57 | cliProgram.flags["output"].arguments.kind === "Ok" 58 | ? (cliProgram.flags["output"].arguments.value as string) 59 | : null; 60 | const outputFileName = 61 | providedOutputFileName === null 62 | ? "build/" + path.basename(fileName) + ".ts" 63 | : providedOutputFileName; 64 | 65 | const contents = await readFile(fileName, "utf-8"); 66 | let generatedContents = ""; 67 | try { 68 | if (outputFileName !== "/dev/stdout") { 69 | generatedContents = await readFile(outputFileName, "utf-8"); 70 | } 71 | } catch (e) {} 72 | const program = parseFile(contents, generatedContents); 73 | 74 | // if (2 > 1) return; 75 | 76 | if (program.kind === "Err") { 77 | console.log("Failed to parse program"); 78 | console.error(program.value); 79 | return; 80 | } 81 | 82 | if (!cliProgram.flags["quiet"].isPresent) { 83 | console.log("Parsed successfully..."); 84 | console.log("Generating code..."); 85 | } 86 | 87 | try { 88 | await mkdir("build"); 89 | } catch (e) {} 90 | const code = await generateProgram(program.value); 91 | 92 | if (!cliProgram.flags["quiet"].isPresent) { 93 | console.log("Writing to", outputFileName); 94 | } 95 | 96 | if (outputFileName === "/dev/stdout") { 97 | console.log(code); 98 | } else { 99 | await writeFile(`${outputFileName}`, code); 100 | } 101 | 102 | if (cliProgram.flags["run"].isPresent) { 103 | if (!cliProgram.flags["quiet"].isPresent) { 104 | console.log("Running program"); 105 | } 106 | runFile(outputFileName); 107 | } 108 | } 109 | 110 | main(); 111 | -------------------------------------------------------------------------------- /src/generator.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | import { openai } from "./OpenAi"; 3 | import { 4 | Construct, 5 | Export, 6 | NeuroFunction, 7 | PinnedNeuroFunction, 8 | Program, 9 | TypeDefinition, 10 | UnionTypeDefinition, 11 | } from "./parser"; 12 | 13 | dotenv.config(); 14 | 15 | function generateArg(name: string, type_: string): string { 16 | return `${name}: ${type_}`; 17 | } 18 | 19 | async function generateNeuroFunction( 20 | func: NeuroFunction, 21 | previousBlocks: string[] 22 | ): Promise { 23 | const prompt = `Auto complete only the TypeScript function called ${func.name} as purely plain TypeScript, no wrapping or explaining text. Only complete the function given and assume others are implemented. Do not wrap the code in markdown. Do not explain the code. Only provide the code for the function ${func.name}. Do not provide the code for other functions or types. Make sure the code is well-typed. Start your response with \`\`\`typescript`; 24 | const testWrapping = func.name.startsWith("test") ? "export " : ""; 25 | const content = ` 26 | ${testWrapping}function ${func.name}(${func.args 27 | .map((arg) => generateArg(arg.name, arg.type_)) 28 | .join(",")}): ${func.returnType || "void"} { 29 | // ${func.comment} 30 | } 31 | `; 32 | 33 | const otherMessages: { role: "user"; content: string }[] = previousBlocks.map( 34 | (block) => { 35 | return { 36 | role: "user", 37 | content: block, 38 | }; 39 | } 40 | ); 41 | 42 | const completion = await openai.createChatCompletion({ 43 | model: process.env.OPENAI_GPT_MODEL || "gpt-3.5-turbo", 44 | messages: [ 45 | { role: "system", content: prompt }, 46 | ...otherMessages, 47 | { role: "user", content: content }, 48 | ], 49 | temperature: 1, 50 | n: 1, 51 | }); 52 | 53 | let message: string | undefined = completion.data.choices[0].message?.content; 54 | 55 | if (message?.startsWith("```")) { 56 | const lines = message.split("\n"); 57 | message = lines.slice(1, lines.length - 1).join("\n"); 58 | } 59 | 60 | return message || ""; 61 | } 62 | 63 | async function generatePinnedNeuroFunction( 64 | func: PinnedNeuroFunction, 65 | previousBlocks: string[] 66 | ): Promise { 67 | return func.body; 68 | } 69 | 70 | async function generateTypeDefinition( 71 | typeDefinition: TypeDefinition 72 | ): Promise { 73 | return typeDefinition.body; 74 | } 75 | 76 | async function generateUnionTypeDefinition( 77 | unionTypeDefinition: UnionTypeDefinition 78 | ): Promise { 79 | return ` 80 | type ${unionTypeDefinition.name} = ${unionTypeDefinition.tags.join(" | ")}; 81 | `.trim(); 82 | } 83 | 84 | async function generateExport(export_: Export): Promise { 85 | return ` 86 | export { ${export_.exports.join(", ")} }; 87 | `.trim(); 88 | } 89 | 90 | async function generateConstruct( 91 | construct: Construct, 92 | previousBlocks: string[] 93 | ): Promise { 94 | switch (construct.kind) { 95 | case "NeuroFunction": { 96 | return await generateNeuroFunction(construct, previousBlocks); 97 | } 98 | case "PinnedNeuroFunction": { 99 | return await generatePinnedNeuroFunction(construct, previousBlocks); 100 | } 101 | case "TypeDefinition": { 102 | return await generateTypeDefinition(construct); 103 | } 104 | case "UnionTypeDefinition": { 105 | return await generateUnionTypeDefinition(construct); 106 | } 107 | case "Export": { 108 | return await generateExport(construct); 109 | } 110 | } 111 | } 112 | 113 | export async function generateProgram(program: Program): Promise { 114 | let hasMain = false; 115 | const outBlocks: string[] = []; 116 | let i = 0; 117 | for (const block of program.blocks) { 118 | const generatedBlock = await generateConstruct(block, outBlocks); 119 | if ( 120 | (block.kind === "NeuroFunction" || 121 | block.kind === "PinnedNeuroFunction") && 122 | block.name === "main" 123 | ) { 124 | hasMain = true; 125 | } 126 | outBlocks.push(generatedBlock); 127 | i++; 128 | } 129 | 130 | if (hasMain) { 131 | outBlocks.push("main();"); 132 | } 133 | 134 | const joinedBlocks = outBlocks.join("\n\n"); 135 | 136 | return joinedBlocks; 137 | } 138 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | export type Argument = { 2 | kind: "Argument"; 3 | name: string; 4 | type_: string; 5 | }; 6 | 7 | export type NeuroFunction = { 8 | kind: "NeuroFunction"; 9 | name: string; 10 | args: Argument[]; 11 | comment: string; 12 | returnType: string; 13 | }; 14 | 15 | export type PinnedNeuroFunction = { 16 | kind: "PinnedNeuroFunction"; 17 | name: string; 18 | args: Argument[]; 19 | comment: string; 20 | returnType: string; 21 | body: string; 22 | }; 23 | 24 | export type TypeDefinition = { 25 | kind: "TypeDefinition"; 26 | body: string; 27 | }; 28 | 29 | export type UnionTypeDefinition = { 30 | kind: "UnionTypeDefinition"; 31 | name: string; 32 | tags: string[]; 33 | }; 34 | 35 | export type Export = { 36 | kind: "Export"; 37 | exports: string[]; 38 | }; 39 | 40 | export type Construct = 41 | | NeuroFunction 42 | | PinnedNeuroFunction 43 | | TypeDefinition 44 | | UnionTypeDefinition 45 | | Export; 46 | 47 | export type Ok = { 48 | kind: "Ok"; 49 | value: value; 50 | }; 51 | 52 | export type Err = { 53 | kind: "Err"; 54 | value: error; 55 | }; 56 | 57 | export type ParsingResult = Ok | Err; 58 | 59 | function parseFunction(lines: string[]): ParsingResult { 60 | const functionAndTypeLine = lines[0]; 61 | 62 | let functionName = ""; 63 | const maybeFunctionName = functionAndTypeLine.match(/function (.+)\(/); 64 | if (!maybeFunctionName) { 65 | return { 66 | kind: "Err", 67 | value: "Failed to find function name in " + functionAndTypeLine, 68 | }; 69 | } 70 | functionName = maybeFunctionName[1]; 71 | 72 | let args: Argument[] = []; 73 | const maybeArgs = functionAndTypeLine.match(/function .+\((.*)\)/); 74 | if (!maybeArgs) { 75 | return { 76 | kind: "Err", 77 | value: "Failed to find function arguments in " + functionAndTypeLine, 78 | }; 79 | } 80 | args = maybeArgs[1] 81 | .trim() 82 | .split(",") 83 | .map((arg) => { 84 | const name = arg.split(":")[0]; 85 | const type_ = arg.split(":")[1]; 86 | 87 | return { 88 | kind: "Argument", 89 | name: name, 90 | type_: type_, 91 | }; 92 | }); 93 | 94 | args = args.filter((arg) => { 95 | return arg.name.trim().length > 0; 96 | }); 97 | 98 | let returnType = "void"; 99 | const maybeReturnType = functionAndTypeLine.match( 100 | /function .+\(.*\): (.+) {/ 101 | ); 102 | if (maybeReturnType) { 103 | returnType = maybeReturnType[1]; 104 | } 105 | 106 | const comments: string[] = []; 107 | for (const line of lines.slice(1, lines.length - 1)) { 108 | const trimmedLine = line.trim(); 109 | if (trimmedLine.startsWith("//")) { 110 | comments.push(line); 111 | } 112 | } 113 | 114 | const comment = comments.join("\n"); 115 | 116 | return { 117 | kind: "Ok", 118 | value: { 119 | kind: "NeuroFunction", 120 | name: functionName, 121 | args: args, 122 | comment: comment, 123 | returnType: returnType, 124 | }, 125 | }; 126 | } 127 | 128 | function parseTypeDefinition( 129 | lines: string[] 130 | ): ParsingResult { 131 | return { 132 | kind: "Ok", 133 | value: { 134 | kind: "TypeDefinition", 135 | body: lines.join("\n"), 136 | }, 137 | }; 138 | } 139 | 140 | function parseUnionTypeDefinition( 141 | lines: string[] 142 | ): ParsingResult { 143 | const joinedLines = lines.join("\n"); 144 | const splitOnAssign = joinedLines.split("="); 145 | const name = splitOnAssign[0].split("union type")[1].trim(); 146 | const tags = splitOnAssign 147 | .slice(1, splitOnAssign.length) 148 | .join("=") 149 | .split("|") 150 | .map((tag) => tag.replace(";", "").trim()); 151 | return { 152 | kind: "Ok", 153 | value: { 154 | kind: "UnionTypeDefinition", 155 | name, 156 | tags, 157 | }, 158 | }; 159 | } 160 | 161 | function parseExport(line: string): ParsingResult { 162 | const split = line.split("export"); 163 | const withoutExportKeyword = split.slice(1, split.length).join("export"); 164 | 165 | return { 166 | kind: "Ok", 167 | value: { 168 | kind: "Export", 169 | exports: withoutExportKeyword.split(",").map((name) => name.trim()), 170 | }, 171 | }; 172 | } 173 | 174 | function findClosingBracket(lines: string[]): number { 175 | for (let i = 0; i < lines.length; i++) { 176 | const line = lines[i]; 177 | 178 | if (line.trim() === "}") { 179 | return i; 180 | } 181 | } 182 | 183 | return -1; 184 | } 185 | 186 | function findClosingSemicolon(lines: string[]): number { 187 | for (let i = 0; i < lines.length; i++) { 188 | const line = lines[i]; 189 | 190 | if (line.endsWith(";")) { 191 | return i; 192 | } 193 | } 194 | 195 | return -1; 196 | } 197 | 198 | export type Program = { 199 | kind: "Program"; 200 | blocks: Construct[]; 201 | }; 202 | 203 | function findPinnedFunction( 204 | func: NeuroFunction, 205 | generatedFileContents: string 206 | ): ParsingResult { 207 | const lines = generatedFileContents.split("\n"); 208 | 209 | for (let i = 0; i < lines.length; i++) { 210 | const line = lines[i]; 211 | 212 | if (line.trim().length === 0) continue; 213 | 214 | if (line.startsWith("function")) { 215 | const endLineIndex = findClosingBracket(lines.slice(i, lines.length)); 216 | 217 | if (endLineIndex === -1) { 218 | return { 219 | kind: "Err", 220 | value: "Failed to find closing bracket for function " + line, 221 | }; 222 | } else { 223 | let functionName = ""; 224 | const maybeFunctionName = line.match(/function (.+)\(/); 225 | if (!maybeFunctionName) { 226 | return { 227 | kind: "Err", 228 | value: "Failed to find function name in " + line, 229 | }; 230 | } 231 | functionName = maybeFunctionName[1]; 232 | 233 | if (functionName === func.name) { 234 | return { 235 | kind: "Ok", 236 | value: { 237 | ...func, 238 | kind: "PinnedNeuroFunction", 239 | body: lines.slice(i, i + endLineIndex + 1).join("\n"), 240 | }, 241 | }; 242 | } 243 | i = i + endLineIndex + 1; 244 | } 245 | } 246 | } 247 | return { 248 | kind: "Ok", 249 | value: func, 250 | }; 251 | } 252 | 253 | export function parseFile( 254 | fileContents: string, 255 | generatedFileContents: string 256 | ): ParsingResult { 257 | const lines = fileContents.split("\n"); 258 | const blocks: ParsingResult[] = []; 259 | 260 | for (let i = 0; i < lines.length; i++) { 261 | const line = lines[i]; 262 | 263 | if (line.trim().length === 0) continue; 264 | 265 | if (line.startsWith("function") || line.startsWith("pinned function")) { 266 | const endLineIndex = findClosingBracket(lines.slice(i, lines.length)); 267 | 268 | if (endLineIndex === -1) { 269 | return { 270 | kind: "Err", 271 | value: "Failed to find closing bracket for function " + line, 272 | }; 273 | } else { 274 | const func = parseFunction(lines.slice(i, i + endLineIndex + 1)); 275 | if (func.kind === "Ok" && line.startsWith("pinned")) { 276 | const pinned = findPinnedFunction(func.value, generatedFileContents); 277 | blocks.push(pinned); 278 | } else { 279 | blocks.push(func); 280 | } 281 | i = i + endLineIndex + 1; 282 | } 283 | } else if (line.startsWith("type")) { 284 | const endLineIndex = findClosingBracket(lines.slice(i, lines.length)); 285 | 286 | if (endLineIndex === -1) { 287 | return { 288 | kind: "Err", 289 | value: "Failed to find closing bracket for type definition " + line, 290 | }; 291 | } else { 292 | blocks.push(parseTypeDefinition(lines.slice(i, i + endLineIndex + 1))); 293 | i = i + endLineIndex + 1; 294 | } 295 | } else if (line.startsWith("union type")) { 296 | const endLineIndex = findClosingSemicolon(lines.slice(i, lines.length)); 297 | 298 | if (endLineIndex === -1) { 299 | return { 300 | kind: "Err", 301 | value: "Failed to find semicolon for union type definition " + line, 302 | }; 303 | } else { 304 | blocks.push( 305 | parseUnionTypeDefinition(lines.slice(i, i + endLineIndex + 1)) 306 | ); 307 | i = i + endLineIndex + 1; 308 | } 309 | } else if (line.startsWith("export")) { 310 | blocks.push(parseExport(line)); 311 | } 312 | } 313 | 314 | if (blocks.filter((block) => block.kind === "Err").length > 0) { 315 | return { 316 | kind: "Err", 317 | value: blocks 318 | .filter((block) => block.kind === "Err") 319 | .map((block) => block.value) 320 | .join("\n"), 321 | }; 322 | } 323 | 324 | return { 325 | kind: "Ok", 326 | value: { 327 | kind: "Program", 328 | blocks: blocks 329 | .filter((block) => block.kind === "Ok") 330 | .map((block) => (block as Ok).value), 331 | }, 332 | }; 333 | } 334 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | }, 109 | "include": ["src/**/*"] 110 | } 111 | --------------------------------------------------------------------------------