Skip to content
Merged
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib"
}
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
We roughly follow the ideas of [semantic versioning](https://semver.org/).
Note that the versions "0.x.0" probably will include breaking changes.


## v0.3.0 (2025-??-??)

### New features

- New example how to use Typir (core) for a simple expression language with a handwritten parser (#59)

## Fixed bugs

- Fixed the implementation for merging modules for dependency injection, it is exactly the same fix from [Langium](https://github.com/eclipse-langium/langium/pull/1939), since we reused its DI implementation (#79).
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ This repository contains the following stand-alone applications, which demonstra

- [LOX](./examples/lox/README.md) - static type checking for LOX, implemented with Typir-Langium
- [OX](./examples/ox/README.md) - a reduced version of LOX, implemented with Typir-Langium
- [Expression](./examples/expression/README.md) - a handwritten parser for a simple expression language with type checking implemented with Typir (core)


## Tiny Typir Example
Expand Down
131 changes: 131 additions & 0 deletions examples/expression/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# A handwritten parser

This package contains a handwritten parser for a simple expression language.
The language supports:

- Variables declarations with the types `number` and `string`
- Variable Assignments
- Arithmetic expressions
- Print statements
- Expressions, like basic arithmetic operations, string concatenation, variable references, literals and parentheses

## How does it work?

Parsing is a linear process that takes a string of text and produces a tree-like structure that represents the structure of the text.

```mermaid
flowchart LR
A[Lexer] --> B[Parser]
CC@{shape: brace-r, label: "Typir is applied here"} --> C
B --> C[Type System]
C --> D[Validator]

style C fill:#f9f,stroke:#333,stroke-width:4px
```

The following sections describe each step in the process.

### Lexer

**Input**: A string of text

**Output**: A list of tokens

**Task**: Splits the text to tokens and classifies each token.

```mermaid
flowchart LR
subgraph Text
A["variable = 123"]
end
A --> B[Lexer]
B --> Tokens
subgraph Tokens
T1[variable:ID]
T2[=:ASSIGN]
T3[123:NUMBER]
end
```

### Parser

**Input**: A list of tokens

**Output**: An Abstract Syntax Tree (AST)

**Task**: Takes token and arranges them as a tree.

```mermaid
flowchart LR
subgraph Tokens
T1[variable:ID]
T2[=:ASSIGN]
T3[123:NUMBER]
end

Tokens --> D[Parser]
subgraph AST
EE1[variable]
EE2[=]
EE3[123]
EE2 --> EE1
EE2 --> EE3
end
D --> AST
```

### Type system

**Input**: An AST

**Output**: A typed AST

**Task**: Assigns types to the nodes of the AST.

```mermaid
flowchart LR
subgraph AST
EE1[variable]
EE2[=]
EE3[123]
EE2 --> EE1
EE2 --> EE3
end
FF@{shape: brace-r, label: "described by Typir"} --> F
AST --> F[Type System]
F --> AST2
subgraph AST2["Typed AST"]
FF1[variable:STRING]
FF2[=]
FF3[123:NUMBER]
FF2 --> FF1
FF2 --> FF3
end

style F fill:#f9f,stroke:#333,stroke-width:4px
```

### Validator

**Input**: A typed AST

**Output**: a list of errors

**Task**: Checks if the AST is valid.

```mermaid
flowchart LR
subgraph AST["Typed AST"]
FF1[variable:STRING]
FF2[=]
FF3[123:NUMBER]
FF2 --> FF1
FF2 --> FF3
end
AST --> H[Validator]
H --> Errors
subgraph Errors
I1["Variable got wrong type assigned"]
end
style I1 fill:#fdd,stroke:#333,stroke-width:4px
```
30 changes: 30 additions & 0 deletions examples/expression/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "typir-example-expression",
"displayName": "expression",
"version": "0.2.0",
"private": true,
"description": "",
"author": {
"name": "TypeFox",
"url": "https://www.typefox.io"
},
"license": "MIT",
"type": "module",
"engines": {
"vscode": "^1.67.0"
},
"volta": {
"node": "18.20.4",
"npm": "10.7.0"
},
"scripts": {
"build": "tsc -b tsconfig.json",
"clean": "shx rm -rf out *.tsbuildinfo",
"lint": "eslint src --ext ts",
"test": "vitest",
"watch": "concurrently -n tsc -c blue \"tsc -b tsconfig.json --watch\""
},
"dependencies": {
"typir": "~0.2.0"
}
}
141 changes: 141 additions & 0 deletions examples/expression/src/expression-ast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/******************************************************************************
* Copyright 2025 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
export interface BinaryExpression {
type: 'binary';
left: Expression;
right: Expression;
op: '+'|'-'|'/'|'*'|'%';
}

export function isBinaryExpression(node: unknown): node is BinaryExpression {
return isAstNode(node) && node.type === 'binary';
}

export interface UnaryExpression {
type: 'unary';
operand: Expression;
op: '+'|'-';
}

export function isUnaryExpression(node: unknown): node is UnaryExpression {
return isAstNode(node) && node.type === 'unary';
}

export interface VariableUsage {
type: 'variable-usage';
ref: VariableDeclaration;
}


export function isVariableUsage(node: unknown): node is VariableUsage {
return isAstNode(node) && node.type === 'variable-usage';
}


export interface Numeric {
type: 'numeric';
value: number;
}

export function isNumeric(node: unknown): node is Numeric {
return isAstNode(node) && node.type === 'numeric';
}

export interface CharString {
type: 'string';
value: string;
}

export function isCharString(node: unknown): node is CharString {
return isAstNode(node) && node.type === 'string';
}

export type Expression = UnaryExpression | BinaryExpression | VariableUsage | Numeric | CharString;

export interface VariableDeclaration {
type: 'variable-declaration';
name: string;
value: Expression;
}

export function isVariableDeclaration(node: unknown): node is VariableDeclaration {
return isAstNode(node) && node.type === 'variable-declaration';
}

export interface Assignment {
type: 'assignment';
variable: VariableDeclaration;
value: Expression;
}

export function isAssignment(node: unknown): node is Assignment {
return isAstNode(node) && node.type === 'assignment';
}


export interface Printout {
type: 'printout';
value: Expression;
}

export function isPrintout(node: unknown): node is Printout {
return isAstNode(node) && node.type === 'printout';
}

export type Statement = VariableDeclaration | Printout | Assignment;

export type Model = Statement[];

export type Node = Expression | Printout | VariableDeclaration | Assignment;

export function isAstNode(node: unknown): node is Node {
return Object.getOwnPropertyNames(node).includes('type') && ['variable-usage', 'unary', 'binary', 'numeric', 'string', 'printout', 'variable-declaration', 'assignment'].includes((node as Node).type);
}

export namespace AST {
export function variable(name: string, value: Expression): VariableDeclaration {
return { type: 'variable-declaration', name, value };
}
export function assignment(variable: VariableDeclaration, value: Expression): Assignment {
return { type: 'assignment', variable, value };
}
export function printout(value: Expression): Printout {
return { type: 'printout', value };
}
export function num(value: number): Numeric {
return {
type: 'numeric',
value
};
}
export function string(value: string): CharString {
return {
type: 'string',
value
};
}
export function binary(left: Expression, op: BinaryExpression['op'], right: Expression): BinaryExpression {
return {
type: 'binary',
left,
op,
right
};
}
export function unary(op: UnaryExpression['op'], operand: Expression): UnaryExpression {
return {
type: 'unary',
op,
operand
};
}
export function useVariable(variable: VariableDeclaration): VariableUsage {
return {
ref: variable,
type: 'variable-usage'
};
}
}
Loading