Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions .vscode/global.code-snippets
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"Copyright Header": {
"prefix": "header",
"body": [
"/******************************************************************************",
" * Copyright ${CURRENT_YEAR} 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.",
" ******************************************************************************/",
"",
"$0"
],
"description": "Insert TypeFox copyright header"
}
}
23 changes: 22 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,26 @@
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
}
},
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#31afb4",
"activityBar.background": "#31afb4",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#af30ab",
"activityBarBadge.foreground": "#e7e7e7",
"commandCenter.border": "#e7e7e799",
"sash.hoverBorder": "#31afb4",
"statusBar.background": "#26888c",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#31afb4",
"statusBarItem.remoteBackground": "#26888c",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#26888c",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#26888c99",
"titleBar.inactiveForeground": "#e7e7e799"
},
"peacock.remoteColor": "#26888c",
"peacock.color": "#26888c",
}
8 changes: 6 additions & 2 deletions examples/arithmetics/example/example.calc
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ Module basicMath
def a: 5;
def b: 3;
def c: a + b; // 8
def d: (a ^ b); // 164
def d: (a^b); // 164

def root(x, y):
x^(1/y);

def empty(_):
0;

def sqrt(x):
root(x, 2);

Expand All @@ -17,4 +20,5 @@ b % 2; // 1
// This language is case-insensitive regarding symbol names
Root(D, 3); // 32
Root(64, 3); // 4
Sqrt(81); // 9
Sqrt(81); // 9
empty(a + b); // 0
7 changes: 7 additions & 0 deletions examples/arithmetics/example/infix-rule-bug.calc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Single

def a: 5;
def b: 99;

def adaduf(x, y, z):
x+y+z;
21 changes: 21 additions & 0 deletions examples/arithmetics/example/poor example.calc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Module basicMath
def a: 5;
def b: 3;
def c: a + b; // 8
def d: (a ^ b); // 164

def root(x, y):
x ^( 1 / y);

def sqrt(x):
root(x, 2);

def empty(_):
a +b+ c;
2 * c; // 16
b % 2; // 1

// This language is case-insensitive regarding symbol names
Root(D, 3); // 32
Root(64, 3); // 4
Sqrt(81); // 9
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 type { AbstractDefinition, Definition, Evaluation, Expression, Module, Statement } from './generated/ast.js';
import { isBinaryExpression, isDefinition, isEvaluation, isFunctionCall, isNumberLiteral } from './generated/ast.js';
import { isBinaryExpression, isDefinition, isEvaluation, isFunctionCall, isNestedExpression, isNumberLiteral } from './generated/ast.js';
import { applyOp } from './arithmetics-util.js';

export function interpretEvaluations(module: Module): Map<Evaluation, number> {
Expand Down Expand Up @@ -79,6 +79,9 @@ export function evalExpression(expr: Expression, ctx?: InterpreterContext): numb
}
return evalExpression(valueOrDef.expr, {module: ctx.module, context: localContext, result: ctx.result});
}
if (isNestedExpression(expr)) {
return evalExpression(expr.value, ctx);
}

throw new Error('Impossible type of Expression.');
}
87 changes: 87 additions & 0 deletions examples/arithmetics/src/language-server/arithmetics-formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/******************************************************************************
* 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.
******************************************************************************/

import type { AstNode } from 'langium';
import { AbstractFormatter, Formatting, type NodeFormatter } from 'langium/lsp';
import * as ast from './generated/ast.js';

export class ArithmeticsFormatter extends AbstractFormatter {
protected override format(node: AstNode): void {
if (ast.isModule(node)) {
const formatter = this.getNodeFormatter(node);
// Format module declaration: "module" keyword followed by space, then name
formatter.keyword('module').append(Formatting.oneSpace());
formatter.property('name').append(Formatting.newLine());
// All statements should be aligned to the root (no additional indentation)
const statements = formatter.nodes(...node.statements);
statements.prepend(Formatting.noIndent());
} else if (ast.isDefinition(node)) {
const formatter = this.getNodeFormatter(node);
formatter.keyword('def').append(Formatting.oneSpace());
formatter.keyword(':').prepend(Formatting.noSpace());
if (node.args.length > 0) {
// Format Definition of a function
formatParameters(formatter);
formatter.property('expr').prepend(Formatting.indent());
} else {
// Format Definition of a constant
formatter.property('expr').prepend(Formatting.oneSpace());
}

} else if (ast.isFunctionCall(node)) {
const formatter = this.getNodeFormatter(node);
formatParameters(formatter);
} else if (ast.isNestedExpression(node)) {
const formatter = this.getNodeFormatter(node);
// Keep parentheses tight with no spaces inside (but don't restrict spaces outside)
formatter.keyword('(').append(Formatting.noSpace());
formatter.keyword(')').prepend(Formatting.noSpace());
} else if (ast.isBinaryExpression(node)) {
// const formatter = this.getNodeFormatter(node);
// FIXME: Infix rules assign incorrect CST nodes to left/right in some cases.
/* Example:
* ```calc
* module Single
*
* def adaduf(x, y, z):
* x+y+z;
* ```
* The `+` between `x` and `y` is incorrectly represented with left CST text =`x+y+z`, right CST text = `z`
* AST Nodes, however, seems to be attached correctly: on the left CST node we have a BinaryExpression with left=`x`, right=`y`
*
* For now, we don't apply spacing within BinaryExpressions at all, else it not only gets partial formatting,
* but even unexpectedly affects rules *outside* of the BinaryExpression CST!
*/
// operators cannot be formatted neither as keywords nor as properties
// left/right property cannot be formatted either
// formatter.node(node.left).append(getOperatorSpacing(node.operator));
// formatter.node(node.right).prepend(getOperatorSpacing(node.operator));
}

// No space around semicolons in all cases
const formatter = this.getNodeFormatter(node);
formatter.keyword(';').surround(Formatting.noSpace());
}
}

function formatParameters(formatter: NodeFormatter<ast.AbstractDefinition | ast.FunctionCall>): void {
formatter.keywords('(', ')').surround(Formatting.noSpace());
formatter.keywords(',')
.prepend(Formatting.noSpace()).append(Formatting.oneSpace({ allowMore: false }));
}

// function getOperatorSpacing(operator: ast.BinaryExpression['operator']): FormattingAction {
// switch (operator) {
// case '+':
// case '-':
// case '%':
// return Formatting.oneSpace({ allowMore: false });
// case '*':
// case '/':
// case '^':
// return Formatting.noSpace();
// }
// }
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { type Module, inject } from 'langium';
import { createDefaultModule, createDefaultSharedModule, type DefaultSharedModuleContext, type LangiumServices, type LangiumSharedServices, type PartialLangiumServices } from 'langium/lsp';
import { ArithmeticsFormatter } from './arithmetics-formatter.js';
import { ArithmeticsScopeProvider } from './arithmetics-scope-provider.js';
import { ArithmeticsValidator, registerValidationChecks } from './arithmetics-validator.js';
import { ArithmeticsGeneratedModule, ArithmeticsGeneratedSharedModule } from './generated/module.js';
Expand Down Expand Up @@ -39,7 +40,8 @@ export const ArithmeticsModule: Module<ArithmeticsServices, PartialLangiumServic
ArithmeticsValidator: () => new ArithmeticsValidator()
},
lsp: {
CodeActionProvider: () => new ArithmeticsCodeActionProvider()
CodeActionProvider: () => new ArithmeticsCodeActionProvider(),
Formatter: () => new ArithmeticsFormatter()
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ infix BinaryExpression on PrimaryExpression:
> '+' | '-';

PrimaryExpression infers Expression:
'(' Expression ')' |
{infer NestedExpression} '(' value=Expression ')' |
{infer NumberLiteral} value=NUMBER |
{infer FunctionCall} func=[AbstractDefinition] ('(' args+=Expression (',' args+=Expression)* ')')?;

Expand Down
33 changes: 29 additions & 4 deletions examples/arithmetics/src/language-server/generated/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function isAbstractDefinition(item: unknown): item is AbstractDefinition
}

export interface BinaryExpression extends langium.AstNode {
readonly $container: BinaryExpression | Definition | Evaluation | FunctionCall;
readonly $container: BinaryExpression | Definition | Evaluation | FunctionCall | NestedExpression;
readonly $type: 'BinaryExpression';
left: Expression;
operator: '%' | '*' | '+' | '-' | '/' | '^';
Expand Down Expand Up @@ -111,7 +111,7 @@ export function isEvaluation(item: unknown): item is Evaluation {
return reflection.isInstance(item, Evaluation.$type);
}

export type Expression = BinaryExpression | FunctionCall | NumberLiteral;
export type Expression = BinaryExpression | FunctionCall | NestedExpression | NumberLiteral;

export const Expression = {
$type: 'Expression'
Expand All @@ -122,7 +122,7 @@ export function isExpression(item: unknown): item is Expression {
}

export interface FunctionCall extends langium.AstNode {
readonly $container: BinaryExpression | Definition | Evaluation | FunctionCall;
readonly $container: BinaryExpression | Definition | Evaluation | FunctionCall | NestedExpression;
readonly $type: 'FunctionCall';
args: Array<Expression>;
func: langium.Reference<AbstractDefinition>;
Expand Down Expand Up @@ -154,8 +154,23 @@ export function isModule(item: unknown): item is Module {
return reflection.isInstance(item, Module.$type);
}

export interface NestedExpression extends langium.AstNode {
readonly $container: BinaryExpression | Definition | Evaluation | FunctionCall | NestedExpression;
readonly $type: 'NestedExpression';
value: Expression;
}

export const NestedExpression = {
$type: 'NestedExpression',
value: 'value'
} as const;

export function isNestedExpression(item: unknown): item is NestedExpression {
return reflection.isInstance(item, NestedExpression.$type);
}

export interface NumberLiteral extends langium.AstNode {
readonly $container: BinaryExpression | Definition | Evaluation | FunctionCall;
readonly $container: BinaryExpression | Definition | Evaluation | FunctionCall | NestedExpression;
readonly $type: 'NumberLiteral';
value: number;
}
Expand Down Expand Up @@ -188,6 +203,7 @@ export type ArithmeticsAstType = {
Expression: Expression
FunctionCall: FunctionCall
Module: Module
NestedExpression: NestedExpression
NumberLiteral: NumberLiteral
Statement: Statement
}
Expand Down Expand Up @@ -282,6 +298,15 @@ export class ArithmeticsAstReflection extends langium.AbstractAstReflection {
},
superTypes: []
},
NestedExpression: {
name: NestedExpression.$type,
properties: {
value: {
name: NestedExpression.value
}
},
superTypes: [Expression.$type]
},
NumberLiteral: {
name: NumberLiteral.$type,
properties: {
Expand Down
22 changes: 17 additions & 5 deletions examples/arithmetics/src/language-server/generated/grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,16 +310,28 @@ export const ArithmeticsGrammar = (): Grammar => loadedArithmeticsGrammar ?? (lo
{
"$type": "Group",
"elements": [
{
"$type": "Action",
"inferredType": {
"$type": "InferredType",
"name": "NestedExpression"
}
},
{
"$type": "Keyword",
"value": "("
},
{
"$type": "RuleCall",
"rule": {
"$ref": "#/rules@5"
},
"arguments": []
"$type": "Assignment",
"feature": "value",
"operator": "=",
"terminal": {
"$type": "RuleCall",
"rule": {
"$ref": "#/rules@5"
},
"arguments": []
}
},
{
"$type": "Keyword",
Expand Down
Loading
Loading