Skip to content

Commit

Permalink
add syntax node types with position context (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
wcho21 authored Jan 24, 2024
1 parent 1e0b789 commit 9d8f8c3
Show file tree
Hide file tree
Showing 12 changed files with 420 additions and 0 deletions.
76 changes: 76 additions & 0 deletions src/parser/source-token-reader/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import Lexer from "../../lexer";
import SourceTokenReader from "./";

const createReader = (input: string) => {
const lexer = new Lexer(input);

return new SourceTokenReader(lexer);
};

describe("read()", () => {
it("read a token", () => {
const input = "42";
const reader = createReader(input);
const expected = {
type: "number literal",
value: "42",
range: {
begin: { col: 0, row: 0 },
end: { col: 1, row: 0 },
},
};

expect(reader.read()).toEqual(expected);
});

it("read the end token if nothing to read", () => {
const input = "";
const reader = createReader(input);
const expected = {
type: "end",
value: "$end",
range: {
begin: { col: 0, row: 0 },
end: { col: 0, row: 0 },
},
};

expect(reader.read()).toEqual(expected);
});
});

describe("advance()", () => {
it("advance to next token", () => {
const input = "42 99";
const reader = createReader(input);
const expected = {
type: "number literal",
value: "99",
range: {
begin: { col: 3, row: 0 },
end: { col: 4, row: 0 },
},
};

reader.advance();
expect(reader.read()).toEqual(expected);
});
});

describe("isEnd()", () => {
it("return true if end", () => {
const input = "";
const reader = createReader(input);
const expected = true;

expect(reader.isEnd()).toEqual(expected);
});

it("return false if not end", () => {
const input = "42";
const reader = createReader(input);
const expected = false;

expect(reader.isEnd()).toEqual(expected);
});
});
24 changes: 24 additions & 0 deletions src/parser/source-token-reader/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Lexer from "../../lexer";
import type { SourceToken } from "../../lexer";

export default class SourceTokenReader {
private readonly lexer: Lexer;
private token: SourceToken;

constructor(lexer: Lexer) {
this.lexer = lexer;
this.token = lexer.getSourceToken();
}

read(): SourceToken {
return this.token;
}

advance(): void {
this.token = this.lexer.getSourceToken();
}

isEnd(): boolean {
return this.token.type === "end";
}
}
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";
Loading

0 comments on commit 9d8f8c3

Please sign in to comment.