Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Member and destructured assignment and rest operator #20

Open
wants to merge 58 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
5b8d31a
Add a few augmented assignment operators
jasonsbarr Dec 3, 2022
7aeafd7
Make sure the tokens are actually defined
jasonsbarr Dec 3, 2022
0a3ba8e
Type check member expression assignment
jasonsbarr Dec 3, 2022
3b6e337
Emit code for member expression assignment
jasonsbarr Dec 3, 2022
368fd8c
Parse tuple pattern assignment with spread operator
jasonsbarr Dec 3, 2022
56ebd6b
Don't forget to allow spread in parsing LHV
jasonsbarr Dec 3, 2022
76c175c
Disallow LHVs after rest parameter in parsing tuple pattern assignment
jasonsbarr Dec 3, 2022
e1bce4e
Note that if must have else branch
jasonsbarr Dec 3, 2022
69a8dec
Change parameter to object for options
jasonsbarr Dec 3, 2022
4648a27
Only allow return statements in function bodies
jasonsbarr Dec 3, 2022
68056b1
Make sure rest parameter in tuple pattern assignment is an identifier
jasonsbarr Dec 3, 2022
413ef91
Don't actually need that in LHV parser because it's already checked i…
jasonsbarr Dec 3, 2022
9bf15fa
Check tuple pattern variable declaration
jasonsbarr Dec 3, 2022
aef54b9
Type check tuple pattern assignment
jasonsbarr Dec 3, 2022
0eb8fab
Emit code for tuple type assignment
jasonsbarr Dec 3, 2022
5ea21b3
Redo handling tuple commas to allow parsing nested tuples
jasonsbarr Dec 3, 2022
07ddaff
Fix parsing nested tuple patterns
jasonsbarr Dec 3, 2022
b56ac7a
Type check nested tuple patterns
jasonsbarr Dec 3, 2022
eb222b4
Emit code for nested tuple assignment
jasonsbarr Dec 3, 2022
9928080
Make destructuring variable declaration more flexible
jasonsbarr Dec 4, 2022
3c1d4df
Check for type of types inline instead of with if
jasonsbarr Dec 4, 2022
d19c8aa
Need to specify that body block of function declaration is of a function
jasonsbarr Dec 4, 2022
80dad00
Actually, just make it return a value if it's not a statement
jasonsbarr Dec 4, 2022
cce5b0f
Allow slice expression as LHV
jasonsbarr Dec 4, 2022
c57cd0f
Flip checking on member and slice expression assignment so error mess…
jasonsbarr Dec 4, 2022
012aa17
Make constant value default to false
jasonsbarr Dec 4, 2022
a311b0f
Tuples are always constant
jasonsbarr Dec 4, 2022
4eca363
Disallow reassigning props/indices of constant object
jasonsbarr Dec 4, 2022
a0142cf
Document that constant objects are truly constant
jasonsbarr Dec 4, 2022
0242984
Rename 'syntax' in readme to 'the language'
jasonsbarr Dec 4, 2022
2825cca
Add set literal and object pattern ast nodes
jasonsbarr Dec 6, 2022
7f61e19
Refactor checking if identifier is defined into own method
jasonsbarr Dec 6, 2022
2a98f51
REmove redundant instanceof
jasonsbarr Dec 6, 2022
f519732
Parse set literals
jasonsbarr Dec 6, 2022
93eff52
Make sure emitter takes bound nodes
jasonsbarr Dec 6, 2022
16885fc
No longer need as string cast
jasonsbarr Dec 7, 2022
ef0101c
Just use parseLHV because if it's an identifier or spread it just ret…
jasonsbarr Dec 7, 2022
67fe8ad
Use intermediary variable to eliminate as cast
jasonsbarr Dec 7, 2022
4a2c27d
ga
jasonsbarr Dec 7, 2022
d60987d
Parse object pattern as LHV
jasonsbarr Dec 7, 2022
4e89be6
Add bound object pattern node
jasonsbarr Dec 7, 2022
537e3e8
Add object pattern case to setNestedDestructuring method for tuple de…
jasonsbarr Dec 7, 2022
c2e428d
Stub out case for object pattern
jasonsbarr Dec 7, 2022
22b603a
Check if identifier is defined for both identifier and spread cases
jasonsbarr Dec 7, 2022
b90bbad
Set variable name and object property type for identifiers in object …
jasonsbarr Dec 7, 2022
c0f159f
Check on member.kind instead of expr for invalid value
jasonsbarr Dec 7, 2022
45cd21a
Make sure rest parameter is a valid identifier in parser
jasonsbarr Dec 8, 2022
b0d0020
Get type for rest parameter in object pattern
jasonsbarr Dec 8, 2022
2e9a081
Throw error on invalid destructuring pattern
jasonsbarr Dec 8, 2022
77d2afc
CORRECTLY parse rest param for object destructuring
jasonsbarr Dec 8, 2022
c877971
Check identifier lhv in object pattern assignment
jasonsbarr Dec 8, 2022
ceeedea
Check rest parameter in object destructuring assignment
jasonsbarr Dec 8, 2022
141a996
Check object pattern assignment
jasonsbarr Dec 8, 2022
c38a12f
Emit code for object pattern destructuring
jasonsbarr Dec 8, 2022
9a7c2b5
Set nested destructuring for rest parameter on object pattern
jasonsbarr Dec 8, 2022
059c5ce
Make sure to check if identifiers are defined
jasonsbarr Dec 8, 2022
debc44f
Add note
jasonsbarr Dec 8, 2022
377c027
Remove redundant code
jasonsbarr Dec 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ Liszt compiles to JavaScript, so it can be used both for browser-based applicati

The type checker implementation owes a great deal to Jake Donham's [Reconstructing TypeScript](https://jaked.org/blog/2021-09-07-Reconstructing-TypeScript-part-0) series.

## Syntax
## The language

Comments start with a semicolon and continue to the end of the line.

### Literals

There are literals for Integer, Float, String, Boolean, Symbol, Object, Tuple, Vector, and Nil types.

```ruby
Expand All @@ -40,15 +42,19 @@ nil

Note that both Integer and Float are subtypes of Number, so they can be mixed together in most operations (need to fix this).

### Declaring bindings

Declare variables with `var` and constants with `const`. Note that if you reassign a variable the new value must be of the same type as the one it was declared with.

```ruby
var changeMe = "I can be changed!"
const cantChangeMe = "Try to change me and it will throw an error"

changeMe = 42 ;=> Type error!
cantChangeMe = 42 ;=> Type error!
```

### Defining functions

Define functions with the `def` keyword and end the body with `end`. Note that we strongly recommend using type annotations with the function parameters, though a return type annotation is not necessary. If you don't annotate the function parameters, they will be typed as Any which basically turns off the type checker.

```ruby
Expand All @@ -59,6 +65,8 @@ end

If you want to annotate the return type you can do that too; sometimes it's a big help to the type checker (especially with recursive functions).

Note that the if operation is an expression, not a statement, and it MUST have an else branch.

```ruby
def fib(n: integer): integer
if n == 0
Expand All @@ -74,6 +82,8 @@ You can also just assign a lambda to a variable (though a lambda must use an exp
const inc: (x: integer) => integer = (x) => x + 1
```

### Generic functions

You can use type variables in your function definitions for parametric polymorphism. Type variables start with a single quote character.

```ruby
Expand All @@ -82,6 +92,8 @@ def id(x: 'a)
end
```

### Iteration

Iterate over vectors with for statements.

```ruby
Expand All @@ -94,6 +106,36 @@ end
sum == 55 ;=> true
```

### Constants are really constant

In JavaScript, you can reassign properties of an object (or indices of an array) even if the object is declared with `const`, like so:

```js
const obj = { greeting: "Hello" };
obj.greeting = "Hi!";

const arr = [1, 2, 3];
arr[2] = 5;
```

Trying to do that with a Liszt object or vector will cause a type error at compile time, so this

```ruby
const obj = { greeting: "Hello" };
obj.greeting = "Hi!"
```

or this

```ruby
const v = vec[1, 2, 3]
v[2] = 5
```

will throw an error. You can always reassign properties or indices of objects that are declared with `var` though.

### Creating types

You can also create your own types as aliases for built-in types, tuple types, and object types.

```ruby
Expand Down
48 changes: 42 additions & 6 deletions src/emitter/Emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ASTNode } from "../syntax/parser/ast/ASTNode";
import { CallExpression } from "../syntax/parser/ast/CallExpression";
import { VariableDeclaration } from "../syntax/parser/ast/VariableDeclaration";
import { BoundAssignmentExpression } from "../typechecker/bound/BoundAssignmentExpression";
import { BoundASTNode } from "../typechecker/bound/BoundASTNode";
import { BoundBinaryOperation } from "../typechecker/bound/BoundBinaryOperation";
import { BoundBlock } from "../typechecker/bound/BoundBlock";
import { BoundBooleanLiteral } from "../typechecker/bound/BoundBooleanLiteral";
Expand All @@ -19,6 +20,7 @@ import { BoundMemberExpression } from "../typechecker/bound/BoundMemberExpressio
import { BoundNilLiteral } from "../typechecker/bound/BoundNilLiteral";
import { BoundNodes } from "../typechecker/bound/BoundNodes";
import { BoundObjectLiteral } from "../typechecker/bound/BoundObjectLiteral";
import { BoundObjectPattern } from "../typechecker/bound/BoundObjectPattern";
import { BoundParenthesizedExpression } from "../typechecker/bound/BoundParenthesizedExpression";
import { BoundProgramNode } from "../typechecker/bound/BoundProgramNode";
import { BoundReturnStatement } from "../typechecker/bound/BoundReturnStatement";
Expand All @@ -27,6 +29,7 @@ import { BoundStringLiteral } from "../typechecker/bound/BoundStringLiteral";
import { BoundSymbolLiteral } from "../typechecker/bound/BoundSymbolLiteral";
import { BoundTree } from "../typechecker/bound/BoundTree";
import { BoundTuple } from "../typechecker/bound/BoundTuple";
import { BoundTuplePattern } from "../typechecker/bound/BoundTuplePattern";
import { BoundUnaryOperation } from "../typechecker/bound/BoundUnaryOperation";
import { BoundVariableDeclaration } from "../typechecker/bound/BoundVariableDeclaration";
import { BoundVector } from "../typechecker/bound/BoundVector";
Expand All @@ -44,7 +47,7 @@ export class Emitter {
return this.emitNode(program);
}

private emitNode(node: ASTNode): string {
private emitNode(node: BoundASTNode): string {
switch (node.kind) {
case BoundNodes.BoundProgramNode:
return this.emitProgram(node as BoundProgramNode);
Expand Down Expand Up @@ -101,6 +104,10 @@ export class Emitter {
return this.emitSliceExpression(node as BoundSliceExpression);
case BoundNodes.BoundForStatement:
return this.emitForStatement(node as BoundForStatement);
case BoundNodes.BoundTuplePattern:
return this.emitTuplePattern(node as BoundTuplePattern);
case BoundNodes.BoundObjectPattern:
return this.emitObjectPattern(node as BoundObjectPattern);
default:
throw new Error(`Unknown bound node type ${node.kind}`);
}
Expand Down Expand Up @@ -182,7 +189,7 @@ export class Emitter {
return code;
}

private emitCallExpression(node: CallExpression): string {
private emitCallExpression(node: BoundCallExpression): string {
let code = `${this.emitNode(node.func)}`;
code += `(${node.args.map((arg) => this.emitNode(arg)).join(", ")})`;
return code;
Expand All @@ -191,15 +198,15 @@ export class Emitter {
private emitAssignment(node: BoundAssignmentExpression): string {
return `${this.emitNode(node.left)} ${node.operator} ${this.emitNode(
node.right
)}`;
)};\n`;
}

private emitVariableDeclaration(node: BoundVariableDeclaration): string {
if (node.constant) {
return `const ${this.emitNode(node.assignment)};\n`;
return `const ${this.emitNode(node.assignment)}`;
}

return `let ${this.emitNode(node.assignment)};\n`;
return `let ${this.emitNode(node.assignment)}`;
}

private emitFunctionDeclaration(node: BoundFunctionDeclaration): string {
Expand All @@ -218,7 +225,7 @@ export class Emitter {
// this case is redundant, but I'm putting it here for emphasis
if (expr.kind === BoundNodes.BoundReturnStatement) {
return this.emitNode(expr);
} else if (i === a.length - 1 && node.statement) {
} else if (i === a.length - 1 && !node.statement) {
return `return ${this.emitNode(expr)};`;
}
return this.emitNode(expr);
Expand Down Expand Up @@ -297,4 +304,33 @@ export class Emitter {

return code;
}

private emitTuplePattern(node: BoundTuplePattern): string {
return `[${node.names
.map((n, i, a) => {
if (node.rest && i === a.length - 1) {
return `...${(n as BoundIdentifier).name}`;
} else if (n instanceof BoundTuplePattern) {
return this.emitTuplePattern(n);
} else if (n instanceof BoundObjectPattern) {
return this.emitObjectPattern(n);
}

return n.name;
})
.join(", ")}]`;
}

private emitObjectPattern(node: BoundObjectPattern): string {
return `{${node.names
.map((n, i, a) => {
if (node.rest && i === a.length - 1) {
return `...${(n as BoundIdentifier).name}`;
}
// handle cases with nested tuple and object properties

return (n as BoundIdentifier).name;
})
.join(", ")}}`;
}
}
5 changes: 5 additions & 0 deletions src/syntax/lexer/TokenNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export enum TokenNames {
Mod = "Mod",
Exp = "Exp",
Equals = "Equals",
PlusEquals = "PlusEquals",
MinusEquals = "MinusEquals",
TimesEquals = "TimesEquals",
DivEquals = "DivEquals",
ModEquals = "ModEquals",
FatArrow = "FatArrow",
As = "As",
And = "And",
Expand Down
5 changes: 5 additions & 0 deletions src/syntax/lexer/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export type kw = keyof typeof KEYWORDS;

export const OPERATORS = {
"=": TokenNames.Equals,
"+=": TokenNames.PlusEquals,
"-=": TokenNames.MinusEquals,
"*=": TokenNames.TimesEquals,
"/=": TokenNames.DivEquals,
"%=": TokenNames.ModEquals,
"=>": TokenNames.FatArrow,
"+": TokenNames.Plus,
"-": TokenNames.Minus,
Expand Down
88 changes: 83 additions & 5 deletions src/syntax/parser/LHVParser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { LexResult } from "../lexer/LexResult";
import { ASTNode } from "./ast/ASTNode";
import { DestructuringLHV } from "./ast/DestructuringLHV";
import { Identifier } from "./ast/Identifier";
import { ObjectPattern } from "./ast/ObjectPattern";
import { SetLiteral } from "./ast/SetLiteral";
import { SpreadOperation } from "./ast/SpreadOperation";
import { SyntaxNodes } from "./ast/SyntaxNodes";
import { Tuple } from "./ast/Tuple";
import { TuplePattern } from "./ast/TuplePattern";
Expand All @@ -20,24 +24,98 @@ export class LHVParser extends BaseParser {
return expr;
}

if (expr.kind === SyntaxNodes.SliceExpression) {
return expr;
}

if (expr.kind === SyntaxNodes.Tuple) {
return this.parseTuplePattern(expr as Tuple);
}

if (expr.kind === SyntaxNodes.SetLiteral) {
return this.parseObjectPattern(expr as SetLiteral);
}

if (expr.kind === SyntaxNodes.SpreadOperation) {
return this.parseSpreadOperation(expr as SpreadOperation);
}

throw new Error(`Invalid left side expression type ${expr.kind}`);
}

private parseObjectPattern(expr: SetLiteral) {
let names: DestructuringLHV[] = [];
let rest = false;
for (let member of expr.members) {
if (rest) {
throw new Error("No left hand values allowed after rest parameter");
}

if (
![
SyntaxNodes.Identifier,
SyntaxNodes.SpreadOperation,
SyntaxNodes.Tuple,
SyntaxNodes.SetLiteral,
].includes(member.kind)
) {
throw new Error(
`Object pattern assignment requires valid assignment parameter; ${member.kind} given`
);
}

if (member.kind === SyntaxNodes.SpreadOperation) {
rest = true;
}

let name = this.parseLHV(member) as DestructuringLHV;

names.push(name);
}

return ObjectPattern.new(names, rest, expr.start, expr.end);
}

private parseSpreadOperation(expr: SpreadOperation) {
if (expr.expression.kind !== SyntaxNodes.Identifier) {
throw new Error(
`Rest parameter in left hand assignment value must be a valid identifier; ${expr.kind} given`
);
}

return expr;
}

private parseTuplePattern(expr: Tuple) {
let names: Identifier[] = [];
let names: DestructuringLHV[] = [];
let rest = false;
for (let value of expr.values) {
if (value.kind !== SyntaxNodes.Identifier) {
if (rest) {
throw new Error(`No left hand values allowed after rest parameter`);
}

if (
![
SyntaxNodes.Identifier,
SyntaxNodes.SpreadOperation,
SyntaxNodes.Tuple,
SyntaxNodes.SetLiteral,
].includes(value.kind)
) {
throw new Error(
`Tuple pattern assignment expects valid identifiers; ${expr} given`
`Tuple pattern assignment requires valid assignment parameter; ${expr} given`
);
}
names.push(value as Identifier);

if (value.kind === SyntaxNodes.SpreadOperation) {
rest = true;
}

let name = this.parseLHV(value) as DestructuringLHV;

names.push(name);
}

return TuplePattern.new(names, expr.start, expr.end);
return TuplePattern.new(names, rest, expr.start, expr.end);
}
}
Loading