Skip to content

Commit

Permalink
Add type system
Browse files Browse the repository at this point in the history
  • Loading branch information
Lotes committed Feb 4, 2025
1 parent 094ed45 commit 1962c41
Show file tree
Hide file tree
Showing 14 changed files with 150 additions and 19 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
2 changes: 1 addition & 1 deletion examples/expression/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@
"watch": "concurrently -n tsc -c blue \"tsc -b tsconfig.json --watch\""
},
"dependencies": {
"typir": "~0.0.2"
"typir": "0.1.2"
}
}
52 changes: 44 additions & 8 deletions examples/expression/src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,81 @@ export interface BinaryExpression {
op: '+'|'-'|'/'|'*'|'%';
}

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

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

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

export interface VariableUsage {
type: 'variable-usage';
ref: Variable;
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 type Expression = UnaryExpression | BinaryExpression | Identifier | Numeric | CharString;
export function isCharString(node: unknown): node is CharString {
return isAstNode(node) && node.type === 'string';
}

export interface Variable {
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 Printout {
type: 'printout';
value: Expression;
}

export type Model = Array<Variable | Printout>;
export function isPrintout(node: unknown): node is Printout {
return isAstNode(node) && node.type === 'printout';
}

export type Model = Array<VariableDeclaration | Printout>;

export type Node = Expression | Printout | VariableDeclaration;

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

export namespace AST {
export function variable(name: string, value: Expression): Variable {
export function variable(name: string, value: Expression): VariableDeclaration {
return { type: 'variable-declaration', name, value };
}
export function printout(value: Expression): Printout {
Expand Down Expand Up @@ -80,8 +117,7 @@ export namespace AST {
operand
};
}

export function useVariable(variable: Variable): Identifier {
export function useVariable(variable: VariableDeclaration): VariableUsage {
return {
ref: variable,
type: 'variable-usage'
Expand Down
File renamed without changes.
10 changes: 5 additions & 5 deletions examples/expression/src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
* terms of the MIT License, which is available in the project root.
******************************************************************************/

import { Expression, BinaryExpression, UnaryExpression, Identifier, Numeric, CharString, Variable, Printout, Model as AST } from './ast.js';
import { Token, tokenize, TokenType } from './tokenizer.js';
import { Expression, BinaryExpression, UnaryExpression, VariableUsage, Numeric, CharString, VariableDeclaration, Printout, Model as AST } from './ast.js';
import { Token, tokenize, TokenType } from './lexer.js';

export class Parser {
private tokens: Token[];
private tokenIndex: number;
private symbols: Record<string, Variable>;
private symbols: Record<string, VariableDeclaration>;
private skip(...tokenTypes: TokenType[]) {
while(this.tokenIndex < this.tokens.length && tokenTypes.includes(this.tokens[this.tokenIndex].type)) {
this.tokenIndex++;
Expand Down Expand Up @@ -88,7 +88,7 @@ export class Parser {
return {
type: 'variable-usage',
ref: symbol
} as Identifier;
} as VariableUsage;
} else if(this.canConsume('NUM')) {
return {
type: 'numeric',
Expand All @@ -104,7 +104,7 @@ export class Parser {
throw new Error("Don't know how to continue...");
}
}
private variableDeclaration(): Variable {
private variableDeclaration(): VariableDeclaration {
this.consume('VAR');
const name = this.consume('ID').content;
this.consume('ASSIGN');
Expand Down
51 changes: 51 additions & 0 deletions examples/expression/src/type-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,55 @@
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { createTypirServices, InferenceRuleNotApplicable, InferOperatorWithMultipleOperands, InferOperatorWithSingleOperand } from 'typir';
import { BinaryExpression, isAstNode, isBinaryExpression, isNumeric, isPrintout, isUnaryExpression, isVariableDeclaration, isVariableUsage, UnaryExpression } from './ast.js';

export function initializeTypir() {
const typir = createTypirServices();
const typeNumber = typir.factory.Primitives.create({
primitiveName: 'number', inferenceRules: [
isNumeric,
(node: unknown) => isAstNode(node) && node.type === 'numeric'
]
});
const typeString = typir.factory.Primitives.create({
primitiveName: 'void', inferenceRules:
(node: unknown) => isAstNode(node) && node.type === 'string'
});
//const typeVoid = typir.factory.Primitives.create({ primitiveName: 'void' });

const binaryInferenceRule: InferOperatorWithMultipleOperands<BinaryExpression> = {
filter: isBinaryExpression,
matching: (node: BinaryExpression, name: string) => node.op === name,
operands: (node: BinaryExpression) => [node.left, node.right],
};
typir.factory.Operators.createBinary({ name: '+', signature: { left: typeNumber, right: typeNumber, return: typeNumber }, inferenceRule: binaryInferenceRule });
typir.factory.Operators.createBinary({ name: '-', signature: { left: typeNumber, right: typeNumber, return: typeNumber }, inferenceRule: binaryInferenceRule });
typir.factory.Operators.createBinary({ name: '/', signature: { left: typeNumber, right: typeNumber, return: typeNumber }, inferenceRule: binaryInferenceRule });
typir.factory.Operators.createBinary({ name: '*', signature: { left: typeNumber, right: typeNumber, return: typeNumber }, inferenceRule: binaryInferenceRule });
typir.factory.Operators.createBinary({ name: '%', signature: { left: typeNumber, right: typeNumber, return: typeNumber }, inferenceRule: binaryInferenceRule });
typir.factory.Operators.createBinary({ name: '+', signature: { left: typeString, right: typeString, return: typeString }, inferenceRule: binaryInferenceRule });

const unaryInferenceRule: InferOperatorWithSingleOperand<UnaryExpression> = {
filter: isUnaryExpression,
matching: (node: UnaryExpression, name: string) => node.op === name,
operand: (node: UnaryExpression, _name: string) => node.operand,
};
typir.factory.Operators.createUnary({ name: '+', signature: { operand: typeNumber, return: typeNumber }, inferenceRule: unaryInferenceRule });
typir.factory.Operators.createUnary({ name: '-', signature: { operand: typeNumber, return: typeNumber }, inferenceRule: unaryInferenceRule });

typir.Conversion.markAsConvertible(typeNumber, typeString, 'IMPLICIT_EXPLICIT');

typir.Inference.addInferenceRule((languageNode) => {
if (isVariableDeclaration(languageNode)) {
return languageNode.value;
} else if (isVariableUsage(languageNode)) {
return languageNode.ref;
} else if (isPrintout(languageNode)) {
return typeString;
}
return InferenceRuleNotApplicable;
});

return typir;
}
11 changes: 11 additions & 0 deletions examples/expression/src/validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/******************************************************************************
* Copyright 2024 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.
******************************************************************************/
import { TypirServices } from 'typir';
import { Model } from './ast.js';

export function validate(typir: TypirServices, model: Model, accept: (message: string) => void) {
model.forEach(i => typir.validation.Collector.validate(i).forEach(m => accept(m.message)));
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { describe, expect, test } from 'vitest';
import { tokenize, TokenType } from '../src/tokenizer.js';
import { tokenize, TokenType } from '../src/lexer.js';

function expectTokenTypes(text: string, ...expecteds: TokenType[]) {
const actuals = [...tokenize(text)].map(t => t.type);
Expand Down
25 changes: 25 additions & 0 deletions examples/expression/test/validator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/******************************************************************************
* Copyright 2024 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.
******************************************************************************/
import { describe, expect, test } from 'vitest';
import { Parser } from '../src/parser.js';
import { initializeTypir } from '../src/type-system.js';
import { validate } from '../src/validator.js';

const typir = initializeTypir();

describe('Validator', () => {
test('quak', () => {
expectValidationMessages('PRINT 1+2;');
});
});


function expectValidationMessages(text: string, ...messages: string[]) {
const model = new Parser().parse(text);
const actual: string[] = [];
validate(typir, model, m => actual.push(m));
expect(actual).toStrictEqual(messages);
}
1 change: 0 additions & 1 deletion examples/ox/src/language/ox-type-checking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ export class OxTypeCreator extends AbstractLangiumTypeCreator {
}
}


export function createOxTypirModule(langiumServices: LangiumSharedCoreServices): Module<LangiumServicesForTypirBinding, PartialTypirLangiumServices> {
return {
// specific configurations for OX
Expand Down
6 changes: 4 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"packages/typir-langium",
"examples/expression",
"examples/ox",
"examples/lox"
"examples/lox",
"examples/expression"
]
}
1 change: 1 addition & 0 deletions scripts/update-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ async function runUpdate() {
await Promise.all([
replaceAll('typir', true, versions),
replaceAll('typir-langium', true, versions),
replaceAll('expression', false, versions),
replaceAll('ox', false, versions),
replaceAll('lox', false, versions),
]);
Expand Down
2 changes: 2 additions & 0 deletions tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@
{ "path": "examples/lox/tsconfig.test.json" },
{ "path": "examples/ox/tsconfig.src.json" },
{ "path": "examples/ox/tsconfig.test.json" },
{ "path": "examples/expression/tsconfig.src.json" },
{ "path": "examples/expression/tsconfig.test.json" },
]
}

0 comments on commit 1962c41

Please sign in to comment.