Skip to content

Commit

Permalink
add syntax node with position context
Browse files Browse the repository at this point in the history
  • Loading branch information
wcho21 committed Jan 24, 2024
1 parent 2b027e7 commit a2caab4
Show file tree
Hide file tree
Showing 9 changed files with 316 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/parser/syntax-node/base/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type Position from "../../../util/position";

export interface SyntaxNodeBase<T extends string = string, F extends {} = {}> {
type: T,
range: {
begin: Position,
end: Position,
},
fields: F,
};

export function createNodeCreator<N extends SyntaxNodeBase>(type: N["type"]) {
function createNode(fields: N["fields"], rangeBegin: Position, rangeEnd: Position) {
const range = {
begin: rangeBegin,
end: rangeEnd,
};

return { type, range, fields };
};

return createNode;
};

export type CreateNode<N extends SyntaxNodeBase> = (fields: N["fields"], rangeBegin: Position, rangeEnd: Position) => N;
118 changes: 118 additions & 0 deletions src/parser/syntax-node/expression/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {
createIdentifierNode,
createNumberNode,
createStringNode,
createPrefixNode,
createInfixNode,
createFunctionNode,
createCallNode,
createAssignmentNode,
} from "./";
import type {
IdentifierNode,
ExpressionNode,
} from "./";
import type {
BlockNode,
} from "../group";
import { fakePos } from "../testing/fixtures";

const cases = [
{
name: "identifier",
node: createIdentifierNode({ value: "foo" }, fakePos, fakePos),
expected: {
type: "identifier",
fields: {
value: "foo",
},
range: { begin: fakePos, end: fakePos },
},
},
{
name: "number",
node: createNumberNode({ value: 42 }, fakePos, fakePos),
expected: {
type: "number",
fields: {
value: 42,
},
range: { begin: fakePos, end: fakePos },
},
},
{
name: "string",
node: createStringNode({ value: "foo" }, fakePos, fakePos),
expected: {
type: "string",
fields: {
value: "foo",
},
range: { begin: fakePos, end: fakePos },
},
},
{
name: "prefix",
node: createPrefixNode({ prefix: "+", right: {} as ExpressionNode }, fakePos, fakePos),
expected: {
type: "prefix",
fields: {
prefix: "+",
right: {},
},
range: { begin: fakePos, end: fakePos },
},
},
{
name: "infix",
node: createInfixNode({ infix: "+", left: {} as ExpressionNode, right: {} as ExpressionNode }, fakePos, fakePos),
expected: {
type: "infix",
fields: {
infix: "+",
left: {},
right: {},
},
range: { begin: fakePos, end: fakePos },
},
},
{
name: "function",
node: createFunctionNode({ parameters: [] as IdentifierNode[], body: {} as BlockNode }, fakePos, fakePos),
expected: {
type: "function",
fields: {
parameters: [],
body: {},
},
range: { begin: fakePos, end: fakePos },
},
},
{
name: "call",
node: createCallNode({ func: {} as IdentifierNode, args: [] as ExpressionNode[] }, fakePos, fakePos),
expected: {
type: "call",
fields: {
func: {},
args: [],
},
range: { begin: fakePos, end: fakePos },
},
},
{
name: "assignment",
node: createAssignmentNode({ left: {} as IdentifierNode, right: {} as ExpressionNode }, fakePos, fakePos),
expected: {
type: "assignment",
fields: {
left: {},
right: {},
},
range: { begin: fakePos, end: fakePos },
},
},
];
it.each(cases)("create $name node", ({ node, expected }) => {
expect(node).toEqual(expected);
});
35 changes: 35 additions & 0 deletions src/parser/syntax-node/expression/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { SyntaxNodeBase, CreateNode } from "../base";
import { createNodeCreator } from "../base";
import type { BlockNode } from "../group";

export type Prefix = "+" | "-" | "!";
export type Infix = "+" | "-" | "*" | "/" | "=" | "==" | "!=" | ">" | "<" | ">=" | "<=";

export type ExpressionNode = IdentifierNode
| NumberNode
| BooleanNode
| StringNode
| PrefixNode
| InfixNode
| FunctionNode
| CallNode
| AssignmentNode;

export interface IdentifierNode extends SyntaxNodeBase<"identifier", { value: string }> {};
export interface NumberNode extends SyntaxNodeBase<"number", { value: number }> {};
export interface BooleanNode extends SyntaxNodeBase<"boolean", { value: boolean }> {};
export interface StringNode extends SyntaxNodeBase<"string", { value: string }> {};
export interface PrefixNode extends SyntaxNodeBase<"prefix", { prefix: Prefix, right: ExpressionNode }> {};
export interface InfixNode extends SyntaxNodeBase<"infix", { infix: Infix, left: ExpressionNode, right: ExpressionNode }> {};
export interface FunctionNode extends SyntaxNodeBase<"function", { parameters: IdentifierNode[], body: BlockNode }> {};
export interface CallNode extends SyntaxNodeBase<"call", { func: IdentifierNode | FunctionNode, args: ExpressionNode[] }> {};
export interface AssignmentNode extends SyntaxNodeBase<"assignment", { left: IdentifierNode, right: ExpressionNode }> {};

export const createIdentifierNode: CreateNode<IdentifierNode> = createNodeCreator<IdentifierNode>("identifier");
export const createNumberNode: CreateNode<NumberNode> = createNodeCreator<NumberNode>("number");
export const createStringNode: CreateNode<StringNode> = createNodeCreator<StringNode>("string");
export const createPrefixNode: CreateNode<PrefixNode> = createNodeCreator<PrefixNode>("prefix");
export const createInfixNode: CreateNode<InfixNode> = createNodeCreator<InfixNode>("infix");
export const createFunctionNode: CreateNode<FunctionNode> = createNodeCreator<FunctionNode>("function");
export const createCallNode: CreateNode<CallNode> = createNodeCreator<CallNode>("call");
export const createAssignmentNode: CreateNode<AssignmentNode> = createNodeCreator<AssignmentNode>("assignment");
33 changes: 33 additions & 0 deletions src/parser/syntax-node/group/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
createProgramNode,
createBlockNode,
} from "./";
import { fakePos } from "../testing/fixtures";

const cases = [
{
name: "program",
node: createProgramNode({ statements: [] }, fakePos, fakePos),
expected: {
type: "program",
fields: {
statements: [],
},
range: { begin: fakePos, end: fakePos },
},
},
{
name: "block",
node: createBlockNode({ statements: [] }, fakePos, fakePos),
expected: {
type: "block",
fields: {
statements: [],
},
range: { begin: fakePos, end: fakePos },
},
},
];
it.each(cases)("create $name node", ({ node, expected }) => {
expect(node).toEqual(expected);
});
12 changes: 12 additions & 0 deletions src/parser/syntax-node/group/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { SyntaxNodeBase, CreateNode } from "../base";
import { createNodeCreator } from "../base";

export type GroupNode = ProgramNode | BlockNode;

/** a root node for a syntax tree of a program */
export interface ProgramNode extends SyntaxNodeBase<"program", { statements: any[] }> {};
/** a group of statements */
export interface BlockNode extends SyntaxNodeBase<"block", { statements: any[] }> {};

export const createProgramNode: CreateNode<ProgramNode> = createNodeCreator<ProgramNode>("program");
export const createBlockNode: CreateNode<BlockNode> = createNodeCreator<BlockNode>("block");
12 changes: 12 additions & 0 deletions src/parser/syntax-node/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { GroupNode } from "./group";
import type { StatementNode } from "./statement";
import type { ExpressionNode } from "./expression";

export type SyntaxNode = GroupNode | StatementNode | ExpressionNode;

export * from "./group";
export type * from "./group";
export * from "./statement";
export type * from "./statement";
export * from "./expression";
export type * from "./expression";
65 changes: 65 additions & 0 deletions src/parser/syntax-node/statement/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
createBranchNode,
createReturnNode,
createExpressionStatementNode,
} from "./";
import type {
ExpressionNode,
} from "../expression";
import type {
BlockNode,
} from "../group";
import { fakePos } from "../testing/fixtures";

const cases = [
{
name: "branch",
node: createBranchNode({ predicate: {} as ExpressionNode, consequence: {} as BlockNode }, fakePos, fakePos),
expected: {
type: "branch",
fields: {
predicate: {},
consequence: {},
},
range: { begin: fakePos, end: fakePos },
},
},
{
name: "branch with alternative",
node: createBranchNode({ predicate: {} as ExpressionNode, consequence: {} as BlockNode, alternative: {} as BlockNode }, fakePos, fakePos),
expected: {
type: "branch",
fields: {
predicate: {},
consequence: {},
alternative: {},
},
range: { begin: fakePos, end: fakePos },
},
},
{
name: "return",
node: createReturnNode({ expression: {} as ExpressionNode }, fakePos, fakePos),
expected: {
type: "return",
fields: {
expression: {},
},
range: { begin: fakePos, end: fakePos },
},
},
{
name: "expression statement",
node: createExpressionStatementNode({ expression: {} as ExpressionNode }, fakePos, fakePos),
expected: {
type: "expression statement",
fields: {
expression: {},
},
range: { begin: fakePos, end: fakePos },
},
},
];
it.each(cases)("create $name node", ({ node, expected }) => {
expect(node).toEqual(expected);
});
15 changes: 15 additions & 0 deletions src/parser/syntax-node/statement/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { SyntaxNodeBase, CreateNode } from "../base";
import { createNodeCreator } from "../base";
import type { BlockNode } from "../group";
import type { ExpressionNode } from "../expression";

export type StatementNode = BranchNode | ReturnNode | ExpressionStatementNode;

export interface BranchNode extends SyntaxNodeBase<"branch", { predicate: ExpressionNode, consequence: BlockNode, alternative?: BlockNode }> {};
export interface ReturnNode extends SyntaxNodeBase<"return", { expression: ExpressionNode }> {};
/** A wrapper type to treat a single expression as a statement. */
export interface ExpressionStatementNode extends SyntaxNodeBase<"expression statement", { expression: ExpressionNode }> {};

export const createBranchNode: CreateNode<BranchNode> = createNodeCreator<BranchNode>("branch");
export const createReturnNode: CreateNode<ReturnNode> = createNodeCreator<ReturnNode>("return");
export const createExpressionStatementNode: CreateNode<ExpressionStatementNode> = createNodeCreator<ExpressionStatementNode>("expression statement");
1 change: 1 addition & 0 deletions src/parser/syntax-node/testing/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const fakePos = { col: 0, row: 0 };

0 comments on commit a2caab4

Please sign in to comment.