Skip to content

Commit

Permalink
Merge 933573e into 8d1cfd7
Browse files Browse the repository at this point in the history
  • Loading branch information
RauliL committed Sep 4, 2023
2 parents 8d1cfd7 + 933573e commit 82006bf
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 30 deletions.
3 changes: 3 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import stripAnsi from "strip-ansi";
import { compile } from "./compiler";
import { Context } from "./context";
import { executeScript } from "./execute";
import { runInteractive } from "./interactive";
import { ExitStatus } from "./status";

type CommandLineOptions = {
Expand Down Expand Up @@ -139,6 +140,8 @@ export const run = () => {
fs.readFileSync(options.filename, "utf-8"),
options.filename
);
} else if (process.stdin.isTTY) {
runInteractive(context);
} else {
let source = "";

Expand Down
2 changes: 1 addition & 1 deletion src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const compile = (
): Promise<Statement[]> =>
new Promise<Statement[]>((resolve, reject) => {
try {
resolve(parse(lex(filename, source)));
resolve(parse(lex(filename, 1, source)));
} catch (err) {
reject(err);
}
Expand Down
43 changes: 43 additions & 0 deletions src/interactive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as readline from "readline";

import { Statement } from "./ast";
import { Context } from "./context";
import { executeScript } from "./execute";
import { lex } from "./lexer";
import { parse } from "./parser";
import { ExitStatus } from "./status";

export const runInteractive = (context: Context) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: "juokse:1> ",
});
let lineNumber = 1;

rl.on("line", (source) => {
let script: Statement[];

try {
script = parse(lex("<stdin>", lineNumber++, source));
} catch (err) {
console.error(err);
return;
}

executeScript(context, script)
.catch((err) => {
console.error(err);
})
.finally(() => {
rl.setPrompt(`juokse:${lineNumber}> `);
rl.prompt();
});
});

rl.on("close", () => {
process.exit(ExitStatus.OK);
});

rl.prompt();
};
26 changes: 13 additions & 13 deletions src/lexer/lexer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import { lex } from "./lexer";

describe("lex()", () => {
it("should return nothing when there is nothing to lex", () => {
expect(lex("test", "")).toHaveLength(0);
expect(lex("test", 1, "")).toHaveLength(0);
});

it("should ignore comments", () => {
expect(lex("test", "# This should be ignored.\n")).toHaveLength(0);
expect(lex("test", 1, "# This should be ignored.\n")).toHaveLength(0);
});

it("should ignore empty lines", () => {
expect(lex("test", " \t \n")).toHaveLength(0);
expect(lex("test", 1, " \t \n")).toHaveLength(0);
});

it("should be able to detect when indentation is increased", () => {
expect(lex("test", " foo")).toEqual([
expect(lex("test", 1, " foo")).toEqual([
{
position: {
filename: "test",
Expand Down Expand Up @@ -53,7 +53,7 @@ describe("lex()", () => {
});

it("should be able to lex simple words", () => {
expect(lex("test", "foo bar")).toEqual([
expect(lex("test", 1, "foo bar")).toEqual([
{
position: {
filename: "test",
Expand All @@ -76,7 +76,7 @@ describe("lex()", () => {
});

it("should parse escape sequences in simple words", () => {
expect(lex("test", "a\\u0030")).toEqual([
expect(lex("test", 1, "a\\u0030")).toEqual([
{
position: {
filename: "test",
Expand All @@ -98,12 +98,12 @@ describe("lex()", () => {
])(
"should be able to distinguish reserved keywords from simple words",
(input, expectedType) => {
expect(lex("test", input)).toHaveProperty([0, "type"], expectedType);
expect(lex("test", 1, input)).toHaveProperty([0, "type"], expectedType);
}
);

it.each([":", ";"])("should be able to lex separators", (separator) => {
expect(lex("test", separator)).toEqual([
expect(lex("test", 1, separator)).toEqual([
{
position: {
filename: "test",
Expand All @@ -116,7 +116,7 @@ describe("lex()", () => {
});

it("should be able to lex double quoted strings", () => {
expect(lex("test", '"foo"')).toEqual([
expect(lex("test", 1, '"foo"')).toEqual([
{
position: {
filename: "test",
Expand All @@ -130,7 +130,7 @@ describe("lex()", () => {
});

it("should be able to lex single quoted strings", () => {
expect(lex("test", "'foo'")).toEqual([
expect(lex("test", 1, "'foo'")).toEqual([
{
position: {
filename: "test",
Expand All @@ -144,7 +144,7 @@ describe("lex()", () => {
});

it("should parse escape sequences inside double quoted strings", () => {
expect(lex("test", '"\\u0030"')).toEqual([
expect(lex("test", 1, '"\\u0030"')).toEqual([
{
position: {
filename: "test",
Expand All @@ -158,7 +158,7 @@ describe("lex()", () => {
});

it("should not parse escape sequences inside single quoted strings", () => {
expect(lex("test", "'\\u0030'")).toEqual([
expect(lex("test", 1, "'\\u0030'")).toEqual([
{
position: {
filename: "test",
Expand All @@ -172,6 +172,6 @@ describe("lex()", () => {
});

it("should throw exception if unable to parse an escape sequence", () => {
expect(() => lex("test", '"\\z"')).toThrowError(JuokseError);
expect(() => lex("test", 1, '"\\z"')).toThrowError(JuokseError);
});
});
8 changes: 6 additions & 2 deletions src/lexer/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,12 @@ const lexLogicalLine = (
}
};

export const lex = (filename: string, source: string): Token[] => {
const state = new State(filename, source);
export const lex = (
filename: string,
line: number,
source: string
): Token[] => {
const state = new State(filename, line, source);
const tokens: Token[] = [];
const indentStack = new Stack<number>();

Expand Down
22 changes: 11 additions & 11 deletions src/lexer/state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ import { State } from "./state";
describe("class State", () => {
describe("eof()", () => {
it("should return `true` if there are no more characters to be read", () =>
expect(new State("test", "").eof()).toBe(true));
expect(new State("test", 1, "").eof()).toBe(true));

it("should return `false` if there are more characters to be read", () =>
expect(new State("test", "a").eof()).toBe(false));
expect(new State("test", 1, "a").eof()).toBe(false));
});

describe("current()", () => {
it("should return next character if there are more characters to be read", () =>
expect(new State("test", "a").current()).toBe("a"));
expect(new State("test", 1, "a").current()).toBe("a"));

it("should throw error if there are no more characters to be read", () =>
expect(() => new State("test", "").current()).toThrow(JuokseError));
expect(() => new State("test", 1, "").current()).toThrow(JuokseError));
});

describe("peek()", () => {
it("should return `false` if there are no more characters to be read", () =>
expect(new State("test", "").peek("a")).toBe(false));
expect(new State("test", 1, "").peek("a")).toBe(false));

it.each([
[true, "a", "a"],
Expand All @@ -32,16 +32,16 @@ describe("class State", () => {
])(
"should return %p when the next character is %p and given pattern is %p",
(expectedResult, input, pattern) =>
expect(new State("test", input).peek(pattern)).toBe(expectedResult)
expect(new State("test", 1, input).peek(pattern)).toBe(expectedResult)
);
});

describe("peekRead()", () => {
it("should return false if the next character does not match given pattern", () =>
expect(new State("test", "a").peekRead("b")).toBe(false));
expect(new State("test", 1, "a").peekRead("b")).toBe(false));

it("should return true and advance when the next character matches given pattern", () => {
const state = new State("test", "a");
const state = new State("test", 1, "a");

expect(state.peekRead("a")).toBe(true);
expect(state).toHaveProperty("offset", 1);
Expand All @@ -50,15 +50,15 @@ describe("class State", () => {

describe("advance()", () => {
it("should throw error if there are no more characters to be read", () =>
expect(() => new State("test", "").advance()).toThrow(JuokseError));
expect(() => new State("test", 1, "").advance()).toThrow(JuokseError));

it("should return next character from the source code", () =>
expect(new State("test", "a").advance()).toBe("a"));
expect(new State("test", 1, "a").advance()).toBe("a"));

it.each(["\n", "\r"])(
"should increase line number when new line is encountered",
(input) => {
const state = new State("test", input);
const state = new State("test", 1, input);

expect(state.advance()).toBe("\n");
expect(state).toHaveProperty(["position", "line"], 2);
Expand Down
4 changes: 2 additions & 2 deletions src/lexer/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ export class State {
private readonly source: string;
private offset: number;

public constructor(filename: string, source: string) {
public constructor(filename: string, line: number, source: string) {
this.position = {
filename,
line: 1,
line,
column: 1,
};
this.source = source;
Expand Down
2 changes: 1 addition & 1 deletion src/parser/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const mockPosition: Readonly<Position> = {
};

const createState = (source: string) =>
new State(lex(mockPosition.filename, source));
new State(lex(mockPosition.filename, 1, source));

describe("parseBlockStatement()", () => {
it("should require indentation after new line", () =>
Expand Down

0 comments on commit 82006bf

Please sign in to comment.