Writing "Writing An Interpreter In Go" In TypeScript
Tags: typescript, go
Published: 2022-10-25 03:54:45

> Date: 08/2020, Repository: [wafuwafu13/Interpreter-made-in-TypeScript](https://github.com/wafuwafu13/Interpreter-made-in-TypeScript)

I wrote [Writing An Interpreter In Go](https://interpreterbook.com/) in TypeScript up to chapter 2.
This article will focus on the analysis of let statements.

### environment building

I used TypeScript, webpack, Jest, ESLint, Prettier.
[first commit](https://github.com/wafuwafu13/Interpreter-made-in-TypeScript/commit/313d7a08d3213a0f89c0c0d2920fea091ce7fe1f)

### Lexer

It takes source code as input and returns a sequence of tokens representing that source code as output.
Replacing Go [Struct](https://pkg.go.dev/go/types#Struct) with JavaScript [class](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/class) worked.

```go
type Lexer struct {
	input        string 
	position     int
	readPosition int
	ch           byte
}

func New(input string) *Lexer {
	l := &Lexer{input: input}
	l.readChar()
	return l
}

func (l *Lexer) readChar() {
	if l.readPosition >= len(l.input) {
		l.ch = 0
	} else {
		l.ch = l.input[l.readPosition]
	}
	l.position = l.readPosition
	l.readPosition += 1
}
```
->
```javascript
export interface LexerProps {
  input: string;
  position: number;
  readPosition: number;
  ch: string | number;
}

export class Lexer<T extends LexerProps> {
  input: T['input'];
  position: T['position'];
  readPosition: T['readPosition'];
  ch: T['ch'];

  constructor(
    input: T['input'],
    position: T['position'] = 0,
    readPosition: T['readPosition'] = 0,
    ch: T['ch'] = '',
  ) {
    this.input = input;
    this.position = position;
    this.readPosition = readPosition;
    this.ch = ch;

    this.readChar();
  }

  readChar(): void {
    if (this.readPosition >= this.input.length) {
      this.ch = 'EOF';
    } else {
      this.ch = this.input[this.readPosition];
    }
    this.position = this.readPosition;
    this.readPosition += 1;
  }
```

### AST

It is used as an internal representation of the source code.
In Go, the type of the `Value` field is `Expression`, but in TypeScript, the type of the `Identifier` class was used as in the `Name` field.

```go
type Node interface {
	TokenLiteral() string
	String() string
}

type Statement interface {
	Node
	statementNode()
}

type Expression interface {
	Node
	expressionNode()
}

type Program struct {
	Statements []Statement
}

func (p *Program) TokenLiteral() string {
	if len(p.Statements) > 0 {
		return p.Statements[0].TokenLiteral()
	} else {
		return ""
	}
}

func (p *Program) String() string {
	var out bytes.Buffer

	for _, s := range p.Statements {
		out.WriteString(s.String())
	}

	return out.String()
}

type LetStatement struct {
	Token token.Token
	Name  *Identifier
	Value Expression
}

func (ls *LetStatement) statementNode() {}

func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal }

func (ls *LetStatement) String() string {
	var out bytes.Buffer

	out.WriteString(ls.TokenLiteral() + " ")
	out.WriteString(ls.Name.String())
	out.WriteString(" = ")

	if ls.Value != nil {
		out.WriteString(ls.Value.String())
	}

	out.WriteString(";")

	return out.String()
}

type Identifier struct {
	Token token.Token
	Value string
}

func (i *Identifier) expressionNode() {}
func (i *Identifier) TokenLiteral() string { return i.Token.Literal }
func (i *Identifier) String() string { return i.Value }
```
->
```javascript
export interface ProgramProps {
  statements:
    | LetStatement<LetStatementProps>[]
    | ReturnStatement<ReturnStatementProps>[]
    | ExpressionStatement<ExpressionStatementProps>[];
}

export class Program<T extends ProgramProps> {
  statements: T['statements'];

  constructor(statements: T['statements'] = []) {
    this.statements = statements;
  }
}

export interface LetStatementProps {
  token: Token<TokenProps>;
  name: Identifier<IdentifierProps>;
  value?: Identifier<IdentifierProps>;
}

export class LetStatement<T extends LetStatementProps> {
  token: T['token'];
  name?: T['name'];
  value?: T['value'];

  constructor(token: T['token']) {
    this.token = token;
  }

  tokenLiteral(): string | number {
    return this.token.literal;
  }

  string(): string {
    let statements = [];
    statements.push(this.tokenLiteral() + ' ');
    statements.push(this.name!.string());
    statements.push(' = ');

    if (this.value != null) {
      statements.push(this.value.string());
    }

    statements.push(';');

    return statements.join('');
  }
}

export interface IdentifierProps {
  token: Token<TokenProps>;
  value: string | number;
}

export class Identifier<T extends IdentifierProps> {
  token: T['token'];
  value: T['value'];

  constructor(token: T['token'], value: T['value']) {
    this.token = token;
    this.value = value;
  }

  tokenLiteral(): string | number {
    return this.token.literal;
  }

  string(): string | number {
    return this.value;
  }
}
```

### Parser

It takes input data and builds an abstract syntax tree data structure.
Since the Type Guard was cumbersome, I defined a new `DEFAULT` token.
[commit](https://github.com/wafuwafu13/Interpreter-made-in-TypeScript/commit/42f923e1b2ea74d42393f6dad17fae868a72447b)

```go
func (p *Parser) parseLetStatement() *ast.LetStatement {
	stmt := &ast.LetStatement{Token: p.curToken}

	if !p.expectPeek(token.IDENT) {
		return nil
	}

	stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}

	if !p.expectPeek(token.ASSIGN) {
		return nil
	}

	p.nextToken()

	stmt.Value = p.parseExpression(LOWEST)

	if p.peekTokenIs(token.SEMICOLON) {
		p.nextToken()
	}

	return stmt
}
```
->
```ts
parseLetStatement(): LetStatement<LetStatementProps> {
    const stmt: LetStatement<LetStatementProps> = new LetStatement(
      this.curToken,
    );

    if (!this.expectPeek(TokenDef.IDENT)) {
      return stmt;
    }

    stmt.name = new Identifier(this.curToken, this.curToken.literal);

    if (!this.expectPeek(TokenDef.ASSIGN)) {
      return stmt;
    }

    while (!this.curTokenIs(TokenDef.SEMICOLON)) {
      this.nextToken();
    }

    return stmt;
  }
```

### Testing

I used Jest.

```go
func TestLetStatements(t *testing.T) {
	tests := []struct {
		input              string
		expectedIdentifier string
		expectedValue      interface{}
	}{
		{"let x = 5;", "x", 5},
		{"let y = true;", "y", true},
		{"let foobar = y;", "foobar", "y"},
	}

	for _, tt := range tests {
		l := lexer.New(tt.input)
		p := New(l)
		program := p.ParseProgram()
		checkParserErrors(t, p)

		if len(program.Statements) != 1 {
			t.Fatalf("program.Statements does not contain 1 statements. got=%d",
				len(program.Statements))
		}

		stmt := program.Statements[0]
		if !testLetStatement(t, stmt, tt.expectedIdentifier) {
			return
		}

		val := stmt.(*ast.LetStatement).Value
		if !testLiteralExpression(t, val, tt.expectedValue) {
			return
		}
	}
}
```
->
```ts
describe('testLetStatement', () => {
  const tests = [
    { input: 'let x = 5;', expectedIdentifier: 'x', expectedValue: 5 },
    { input: 'let y = true;', expectedIdentifier: 'y', expectedValue: true },
    {
      input: 'let foobar = y;',
      expectedIdentifier: 'foobar',
      expectedValue: 'y',
    },
  ];

  for (const test of tests) {
    const l = new Lexer(test['input']);
    const p = new Parser(l);

    const program: Program<ProgramProps> = p.parseProgram();

    it('checkParserErrros', () => {
      const errors = p.Errors();
      if (errors.length != 0) {
        for (let i = 0; i < errors.length; i++) {
          console.log('parser error: %s', errors[i]);
        }
      }
      expect(errors.length).toBe(0);
    });

    it('parseProgram', () => {
      expect(program).not.toBe(null);
      expect(program.statements.length).toBe(1);
    });

    const stmt: LetStatement<LetStatementProps> | any = program.statements[0];

    it('letStatement', () => {
      expect(stmt.token.literal).toBe('let');
      expect(stmt.name.value).toBe(test['expectedIdentifier']);
      expect(stmt.value.value).toBe(test['expectedValue']);
    });
  }
});
```

### Debugging

It may just be that I am new to Go and debugging was not the right way to do it, but with TypeScript, I was able to debug the AST structure cleanly.


![Debbuging with Go](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8r4epcgb79eo45xi47kt.png)
->
![Debbuging with TS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jakzda9u4n2f71l0elgv.png)