Comparing version 0.0.3-30 to 0.0.3-31
@@ -73,3 +73,3 @@ "use strict"; | ||
console.log("Installing packages..."); | ||
await installPackages(validPackage, isQuiet); | ||
await installPackages(validPackage, isQuiet, []); | ||
if (!isQuiet) | ||
@@ -87,6 +87,12 @@ console.log(`Installed ${validPackage.dependencies.length} packages`); | ||
} | ||
async function installPackages(validPackage, isQuiet) { | ||
function isAlreadyInstalled(dependency, alreadyInstalledDependency) { | ||
return (alreadyInstalledDependency.filter((d) => d.name === dependency.name && d.version === dependency.version).length > 0); | ||
} | ||
async function installPackages(validPackage, isQuiet, alreadyInstalledDependencies) { | ||
await utils_1.ensureDirectoryExists("derw-packages"); | ||
const installedPackages = []; | ||
for (const dependency of validPackage.dependencies) { | ||
const alreadyInstalled = isAlreadyInstalled(dependency, alreadyInstalledDependencies); | ||
if (alreadyInstalled) | ||
continue; | ||
if (!isQuiet) | ||
@@ -97,3 +103,6 @@ console.log(`Fetching ${dependency.name}@${dependency.version}...`); | ||
if (!isPackageAlreadyThere(depPackage.value, installedPackages)) { | ||
const subpackages = await installPackages(depPackage.value, isQuiet); | ||
const subpackages = await installPackages(depPackage.value, isQuiet, [ | ||
...alreadyInstalledDependencies, | ||
...validPackage.dependencies, | ||
]); | ||
for (const subpackage of subpackages) { | ||
@@ -100,0 +109,0 @@ installedPackages.push(subpackage); |
@@ -374,4 +374,6 @@ "use strict"; | ||
const args = lambda.args.join(", "); | ||
const body = generateExpression(lambda.body); | ||
return ` | ||
const isSimple = types_1.isSimpleValue(lambda.body.kind); | ||
const body = common_1.prefixLines(generateExpression(lambda.body), isSimple ? 0 : 4); | ||
if (isSimple) { | ||
return ` | ||
function(${args}) { | ||
@@ -381,3 +383,9 @@ return ${body}; | ||
`.trim(); | ||
} | ||
return ` | ||
function(${args}) { | ||
${body} | ||
} | ||
`.trim(); | ||
} | ||
function generateLambdaCall(lambdaCall) { | ||
@@ -384,0 +392,0 @@ const args = lambdaCall.lambda.args.join(", "); |
@@ -133,3 +133,3 @@ "use strict"; | ||
} | ||
function generateIfStatement(ifStatement, parentTypes) { | ||
function generateIfStatement(ifStatement, parentTypeArguments) { | ||
const isSimpleIfBody = types_1.isSimpleValue(ifStatement.ifBody.kind); | ||
@@ -142,6 +142,6 @@ const isSimpleElseBody = types_1.isSimpleValue(ifStatement.elseBody.kind); | ||
common_1.prefixLines(ifStatement.ifLetBody | ||
.map((block) => generateBlock(block, parentTypes)) | ||
.map((block) => generateBlock(block, parentTypeArguments)) | ||
.join("\n"), 4) | ||
: ""; | ||
const ifBody = generateExpression(ifStatement.ifBody, parentTypes); | ||
const ifBody = generateExpression(ifStatement.ifBody, parentTypeArguments); | ||
const indentedIfBody = ifBody.split("\n").length === 1 | ||
@@ -156,6 +156,6 @@ ? ifBody | ||
common_1.prefixLines(ifStatement.elseLetBody | ||
.map((block) => generateBlock(block, parentTypes)) | ||
.map((block) => generateBlock(block, parentTypeArguments)) | ||
.join("\n"), 4) | ||
: ""; | ||
const elseBody = generateExpression(ifStatement.elseBody, parentTypes); | ||
const elseBody = generateExpression(ifStatement.elseBody, parentTypeArguments); | ||
const indentedElseBody = elseBody.split("\n").length === 1 | ||
@@ -259,9 +259,9 @@ ? elseBody | ||
} | ||
function generateBranch(predicate, branch, parentTypes) { | ||
function generateBranch(predicate, branch, parentTypeArguments) { | ||
const returnWrapper = types_1.isSimpleValue(branch.body.kind) ? " return " : ""; | ||
const body = common_1.prefixLines(generateExpression(branch.body, parentTypes), types_1.isSimpleValue(branch.body.kind) ? 0 : 4); | ||
const body = common_1.prefixLines(generateExpression(branch.body, parentTypeArguments), types_1.isSimpleValue(branch.body.kind) ? 0 : 4); | ||
const maybeLetBody = branch.letBody.length > 0 | ||
? "\n" + | ||
common_1.prefixLines(branch.letBody | ||
.map((block) => generateBlock(block, parentTypes)) | ||
.map((block) => generateBlock(block, parentTypeArguments)) | ||
.join("\n"), 4) | ||
@@ -383,6 +383,6 @@ : ""; | ||
} | ||
function generateCaseStatement(caseStatement, parentTypes) { | ||
function generateCaseStatement(caseStatement, parentTypeArguments) { | ||
const predicate = generateExpression(caseStatement.predicate); | ||
const name = `_res${utils_1.hashCode(predicate)}`; | ||
const branches = caseStatement.branches.map((branch) => generateBranch(name, branch, parentTypes || [])); | ||
const branches = caseStatement.branches.map((branch) => generateBranch(name, branch, parentTypeArguments || [])); | ||
const isString = caseStatement.branches.filter((branch) => branch.pattern.kind === "StringValue").length > 0; | ||
@@ -557,3 +557,3 @@ if (isString) { | ||
} | ||
function generateFunctionCall(functionCall) { | ||
function generateFunctionCall(functionCall, parentTypeArguments, parentTypes) { | ||
const right = functionCall.args | ||
@@ -566,4 +566,6 @@ .map((item) => generateExpression(item)) | ||
const args = lambda.args.map((arg) => `${arg}: any`).join(", "); | ||
const body = generateExpression(lambda.body); | ||
return ` | ||
const isSimple = types_1.isSimpleValue(lambda.body.kind); | ||
const body = common_1.prefixLines(generateExpression(lambda.body), isSimple ? 0 : 4); | ||
if (isSimple) { | ||
return ` | ||
function(${args}) { | ||
@@ -573,3 +575,9 @@ return ${body}; | ||
`.trim(); | ||
} | ||
return ` | ||
function(${args}) { | ||
${body} | ||
} | ||
`.trim(); | ||
} | ||
function generateLambdaCall(lambdaCall) { | ||
@@ -634,3 +642,3 @@ const args = lambdaCall.lambda.args | ||
} | ||
function generateExpression(expression, parentTypes) { | ||
function generateExpression(expression, parentTypeArguments, parentTypes) { | ||
switch (expression.kind) { | ||
@@ -650,5 +658,5 @@ case "Value": | ||
case "IfStatement": | ||
return generateIfStatement(expression, parentTypes || []); | ||
return generateIfStatement(expression, parentTypeArguments || []); | ||
case "CaseStatement": | ||
return generateCaseStatement(expression, parentTypes || []); | ||
return generateCaseStatement(expression, parentTypeArguments || []); | ||
case "Addition": | ||
@@ -675,3 +683,3 @@ return generateAddition(expression); | ||
case "FunctionCall": | ||
return generateFunctionCall(expression); | ||
return generateFunctionCall(expression, parentTypeArguments || [], parentTypes || []); | ||
case "Lambda": | ||
@@ -714,3 +722,3 @@ return generateLambda(expression); | ||
} | ||
function generateFunction(function_, parentTypes) { | ||
function generateFunction(function_, parentTypeArguments, parentTypes) { | ||
const functionArguments = function_.args | ||
@@ -729,3 +737,3 @@ .map((arg) => { | ||
.filter((value, index, arr) => arr.indexOf(value) === index && | ||
parentTypes.indexOf(value) === -1); | ||
parentTypeArguments.indexOf(value) === -1); | ||
const maybeLetBody = function_.letBody.length > 0 | ||
@@ -736,3 +744,3 @@ ? "\n" + | ||
...typeArguments, | ||
...parentTypes, | ||
...parentTypeArguments, | ||
])) | ||
@@ -746,6 +754,3 @@ .join("\n"), 4) | ||
const body = bodyPrefix + | ||
generateExpression(function_.body, [ | ||
...typeArguments, | ||
...parentTypes, | ||
]) + | ||
generateExpression(function_.body, [...typeArguments, ...parentTypeArguments], [...parentTypes, function_.returnType]) + | ||
bodySuffix; | ||
@@ -794,3 +799,3 @@ const prefixedBody = common_1.prefixLines(body, 4); | ||
} | ||
function generateBlock(syntax, parentTypes) { | ||
function generateBlock(syntax, parentTypeArguments, parentTypes) { | ||
switch (syntax.kind) { | ||
@@ -806,3 +811,3 @@ case "Import": | ||
case "Function": | ||
return generateFunction(syntax, parentTypes || []); | ||
return generateFunction(syntax, parentTypeArguments || [], parentTypes || []); | ||
case "Const": | ||
@@ -809,0 +814,0 @@ return generateConst(syntax); |
{ | ||
"name": "derw", | ||
"version": "0.0.3-30", | ||
"version": "0.0.3-31", | ||
"description": "An Elm-inspired language that transpiles to TypeScript", | ||
@@ -37,3 +37,3 @@ "main": "index.js", | ||
"@eeue56/adeilad": "^0.0.1", | ||
"@eeue56/bach": "^0.0.11", | ||
"@eeue56/bach": "^0.0.12", | ||
"@eeue56/baner": "^0.0.2", | ||
@@ -40,0 +40,0 @@ "@eeue56/ts-core": "^1.1.0", |
@@ -564,1 +564,24 @@ # derw | ||
derw which means oak. Oak is one of the native trees in Wales, famous for it's long life, tall stature, and hard, good quality wood. An English speaker might pronounce it as "deh-ru". | ||
# Working on the compiler | ||
Right now the compiler is easiest for me to work on, which means that it's better to either open an issue or reach out to me on Twitter before opening a pull request. That being said: well reasoned pull requests that fit into my plans are totally welcome, across any Derw repo. | ||
This repo contains the compiler, which is split into: parser, tokenizer, type checking, cli and generators. | ||
The flow for compliation looks roughly like: | ||
- Split content into blocks of functions, constants, imports, exports and types. | ||
- Tokenize each block | ||
- Parse each block into an AST | ||
- Check names for collisions and imports | ||
- Check types | ||
- Generate target code | ||
The CLI is mostly responsible for handling all these steps, but the library can be used programatically as it is in the [playground](https://github.com/derw-lang/playground/). Each file in the [src/cli](./src/cli/) folder is responsible for the different abilities of the CLI: installing, formatting, bundling, testing, compiling, templating, etc. These functions all follow the same API to make working on them easier. My rule in designing the CLIs is that they should be obvious on how to use them. There should be a limited number of commands: but I don't want developers to need to install multiple tools to perform standard operations, like formatting or testing. It uses [Baner](https://github.com/eeue56/baner) under the hood for parsing the flags and arguments, so check out the [docs](https://github.com/eeue56/baner/blob/main/docs/src/baner.md) for that if you're unsure. | ||
The generation files are split between Derw, TypeScript, Javascript and Elm. Generally the rule is to avoid code sharing between these as much as possible, as it's easier to read when the code is all in one file and you can clearly see what each AST token generates. That being said, there are some shared files - for example, code for handling indentation. Adding a new code generation target is simplest done by copying the TypeScript generation file - but please reach out to me if you have a new target in mind! | ||
The parser follows a simple rule of one function per expression. There should be only one expression type returned by each parser, and figuring out which to call is done by the main `parseExpression` function. The code here is mostly token based, though in some places it is just done through string manipulation. This is intended to be re-written in Derw in the future, so big refactors aren't necessary at the moment. | ||
Every bug encountered in the parser needs to have a test added for it. There's a combination of tests, some running on library code, some running on short snippets to ensure the parser and generators are consistent. You can find this all in the [src/tests](./src/tests/) folder. To make a new test, just copy one of the existing tests and rename it. It must end with `_test.ts` in order for the test runner to pick it up. You can run the whole suite through `npm run test`, or specifics via `npm run test --file {name of file}`. You can also specify a specific function via the `--function {name of function}` flag, useful for testing just the `testParse` or `testGenerate` of each file. There is also the `--only-fails` flag, useful for just seeing failing tests. Typically snippet tests should test block analysis, parsing, generation, and running the generated TypeScript through tsc. Check out [src/tests/simple_function_test.ts](./src/tests/simple_function_test.ts) for an example on how each of those is done. You'll want to also have a [derw-lang/stdlib](https://github.com/derw-lang/stdlib/) folder in the same folder as `derw` to ensure that the stdlib tests can run. |
@@ -105,3 +105,3 @@ import { | ||
if (!isQuiet) console.log("Installing packages..."); | ||
await installPackages(validPackage, isQuiet); | ||
await installPackages(validPackage, isQuiet, [ ]); | ||
if (!isQuiet) | ||
@@ -124,5 +124,18 @@ console.log( | ||
function isAlreadyInstalled( | ||
dependency: Dependency, | ||
alreadyInstalledDependency: Dependency[] | ||
): boolean { | ||
return ( | ||
alreadyInstalledDependency.filter( | ||
(d) => | ||
d.name === dependency.name && d.version === dependency.version | ||
).length > 0 | ||
); | ||
} | ||
async function installPackages( | ||
validPackage: Package, | ||
isQuiet: boolean | ||
isQuiet: boolean, | ||
alreadyInstalledDependencies: Dependency[] | ||
): Promise<Package[]> { | ||
@@ -133,2 +146,8 @@ await ensureDirectoryExists("derw-packages"); | ||
for (const dependency of validPackage.dependencies) { | ||
const alreadyInstalled = isAlreadyInstalled( | ||
dependency, | ||
alreadyInstalledDependencies | ||
); | ||
if (alreadyInstalled) continue; | ||
if (!isQuiet) | ||
@@ -143,3 +162,7 @@ console.log(`Fetching ${dependency.name}@${dependency.version}...`); | ||
depPackage.value, | ||
isQuiet | ||
isQuiet, | ||
[ | ||
...alreadyInstalledDependencies, | ||
...validPackage.dependencies, | ||
] | ||
); | ||
@@ -146,0 +169,0 @@ |
@@ -558,4 +558,7 @@ import { exportTests } from "../blocks"; | ||
const args = lambda.args.join(", "); | ||
const body = generateExpression(lambda.body); | ||
return ` | ||
const isSimple = isSimpleValue(lambda.body.kind); | ||
const body = prefixLines(generateExpression(lambda.body), isSimple ? 0 : 4); | ||
if (isSimple) { | ||
return ` | ||
function(${args}) { | ||
@@ -565,3 +568,10 @@ return ${body}; | ||
`.trim(); | ||
} | ||
return ` | ||
function(${args}) { | ||
${body} | ||
} | ||
`.trim(); | ||
} | ||
@@ -568,0 +578,0 @@ function generateLambdaCall(lambdaCall: LambdaCall): string { |
@@ -223,3 +223,3 @@ import { exportTests } from "../blocks"; | ||
ifStatement: IfStatement, | ||
parentTypes: string[] | ||
parentTypeArguments: string[] | ||
): string { | ||
@@ -237,3 +237,3 @@ const isSimpleIfBody = isSimpleValue(ifStatement.ifBody.kind); | ||
ifStatement.ifLetBody | ||
.map((block) => generateBlock(block, parentTypes)) | ||
.map((block) => generateBlock(block, parentTypeArguments)) | ||
.join("\n"), | ||
@@ -244,3 +244,3 @@ 4 | ||
const ifBody = generateExpression(ifStatement.ifBody, parentTypes); | ||
const ifBody = generateExpression(ifStatement.ifBody, parentTypeArguments); | ||
const indentedIfBody = | ||
@@ -259,3 +259,3 @@ ifBody.split("\n").length === 1 | ||
ifStatement.elseLetBody | ||
.map((block) => generateBlock(block, parentTypes)) | ||
.map((block) => generateBlock(block, parentTypeArguments)) | ||
.join("\n"), | ||
@@ -266,3 +266,6 @@ 4 | ||
const elseBody = generateExpression(ifStatement.elseBody, parentTypes); | ||
const elseBody = generateExpression( | ||
ifStatement.elseBody, | ||
parentTypeArguments | ||
); | ||
const indentedElseBody = | ||
@@ -400,7 +403,7 @@ elseBody.split("\n").length === 1 | ||
branch: Branch, | ||
parentTypes: string[] | ||
parentTypeArguments: string[] | ||
): string { | ||
const returnWrapper = isSimpleValue(branch.body.kind) ? " return " : ""; | ||
const body = prefixLines( | ||
generateExpression(branch.body, parentTypes), | ||
generateExpression(branch.body, parentTypeArguments), | ||
isSimpleValue(branch.body.kind) ? 0 : 4 | ||
@@ -413,3 +416,3 @@ ); | ||
branch.letBody | ||
.map((block) => generateBlock(block, parentTypes)) | ||
.map((block) => generateBlock(block, parentTypeArguments)) | ||
.join("\n"), | ||
@@ -578,3 +581,3 @@ 4 | ||
caseStatement: CaseStatement, | ||
parentTypes: string[] | ||
parentTypeArguments: string[] | ||
): string { | ||
@@ -584,3 +587,3 @@ const predicate = generateExpression(caseStatement.predicate); | ||
const branches = caseStatement.branches.map((branch) => | ||
generateBranch(name, branch, parentTypes || [ ]) | ||
generateBranch(name, branch, parentTypeArguments || [ ]) | ||
); | ||
@@ -813,3 +816,7 @@ | ||
function generateFunctionCall(functionCall: FunctionCall): string { | ||
function generateFunctionCall( | ||
functionCall: FunctionCall, | ||
parentTypeArguments?: string[], | ||
parentTypes?: Type[] | ||
): string { | ||
const right = functionCall.args | ||
@@ -824,4 +831,7 @@ .map((item) => generateExpression(item)) | ||
const args = lambda.args.map((arg: any) => `${arg}: any`).join(", "); | ||
const body = generateExpression(lambda.body); | ||
return ` | ||
const isSimple = isSimpleValue(lambda.body.kind); | ||
const body = prefixLines(generateExpression(lambda.body), isSimple ? 0 : 4); | ||
if (isSimple) { | ||
return ` | ||
function(${args}) { | ||
@@ -831,3 +841,10 @@ return ${body}; | ||
`.trim(); | ||
} | ||
return ` | ||
function(${args}) { | ||
${body} | ||
} | ||
`.trim(); | ||
} | ||
@@ -907,3 +924,4 @@ function generateLambdaCall(lambdaCall: LambdaCall): string { | ||
expression: Expression, | ||
parentTypes?: string[] | ||
parentTypeArguments?: string[], | ||
parentTypes?: Type[] | ||
): string { | ||
@@ -924,5 +942,8 @@ switch (expression.kind) { | ||
case "IfStatement": | ||
return generateIfStatement(expression, parentTypes || [ ]); | ||
return generateIfStatement(expression, parentTypeArguments || [ ]); | ||
case "CaseStatement": | ||
return generateCaseStatement(expression, parentTypes || [ ]); | ||
return generateCaseStatement( | ||
expression, | ||
parentTypeArguments || [ ] | ||
); | ||
case "Addition": | ||
@@ -949,3 +970,7 @@ return generateAddition(expression); | ||
case "FunctionCall": | ||
return generateFunctionCall(expression); | ||
return generateFunctionCall( | ||
expression, | ||
parentTypeArguments || [ ], | ||
parentTypes || [ ] | ||
); | ||
case "Lambda": | ||
@@ -989,3 +1014,7 @@ return generateLambda(expression); | ||
function generateFunction(function_: Function, parentTypes: string[]): string { | ||
function generateFunction( | ||
function_: Function, | ||
parentTypeArguments: string[], | ||
parentTypes: Type[] | ||
): string { | ||
const functionArguments = function_.args | ||
@@ -1012,3 +1041,3 @@ .map((arg) => { | ||
arr.indexOf(value) === index && | ||
parentTypes.indexOf(value) === -1 | ||
parentTypeArguments.indexOf(value) === -1 | ||
); | ||
@@ -1024,3 +1053,3 @@ | ||
...typeArguments, | ||
...parentTypes, | ||
...parentTypeArguments, | ||
]) | ||
@@ -1040,6 +1069,7 @@ ) | ||
bodyPrefix + | ||
generateExpression(function_.body, [ | ||
...typeArguments, | ||
...parentTypes, | ||
]) + | ||
generateExpression( | ||
function_.body, | ||
[ ...typeArguments, ...parentTypeArguments ], | ||
[ ...parentTypes, function_.returnType ] | ||
) + | ||
bodySuffix; | ||
@@ -1104,3 +1134,7 @@ | ||
function generateBlock(syntax: Block, parentTypes?: string[]): string { | ||
function generateBlock( | ||
syntax: Block, | ||
parentTypeArguments?: string[], | ||
parentTypes?: Type[] | ||
): string { | ||
switch (syntax.kind) { | ||
@@ -1116,3 +1150,7 @@ case "Import": | ||
case "Function": | ||
return generateFunction(syntax, parentTypes || [ ]); | ||
return generateFunction( | ||
syntax, | ||
parentTypeArguments || [ ], | ||
parentTypes || [ ] | ||
); | ||
case "Const": | ||
@@ -1119,0 +1157,0 @@ return generateConst(syntax); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
834130
23895
586
+ Added@eeue56/bach@0.0.12(transitive)
- Removed@eeue56/bach@0.0.11(transitive)
Updated@eeue56/bach@^0.0.12