Skip to content

Commit

Permalink
Merge pull request #7 from JunNishimura/feature/parse_math_op
Browse files Browse the repository at this point in the history
parse mathematical commands
  • Loading branch information
JunNishimura authored Sep 23, 2024
2 parents 446af62 + 9399be2 commit 67c822c
Show file tree
Hide file tree
Showing 3 changed files with 576 additions and 0 deletions.
82 changes: 82 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package ast

import (
"bytes"

"github.com/JunNishimura/jsop/token"
)

type Expression interface {
TokenLiteral() string
String() string
}

type Program struct {
Expressions []Expression
}

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

for _, exp := range p.Expressions {
out.WriteString(exp.String())
}

return out.String()
}

type IntegerLiteral struct {
Token token.Token
Value int64
}

func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal }
func (il *IntegerLiteral) String() string { return il.Token.Literal }

type PrefixAtom struct {
Token token.Token
Operator string
Right Expression
}

func (pa *PrefixAtom) TokenLiteral() string { return pa.Token.Literal }
func (pa *PrefixAtom) String() string {
var out bytes.Buffer

out.WriteString(pa.Operator)
out.WriteString(pa.Right.String())

return out.String()
}

type Symbol struct {
Token token.Token
Value string
}

func (s *Symbol) TokenLiteral() string { return s.Token.Literal }
func (s *Symbol) String() string { return s.Token.Literal }

type CommandObject struct {
Token token.Token
Symbol Expression
Args []Expression
}

func (c *CommandObject) TokenLiteral() string { return c.Token.Literal }
func (c *CommandObject) String() string {
var out bytes.Buffer

out.WriteString("{\"command\": {\"symbol\": ")
out.WriteString(c.Symbol.String())
out.WriteString(", \"args\": [")
for i, arg := range c.Args {
out.WriteString(arg.String())
if i != len(c.Args)-1 {
out.WriteString(", ")
}
}
out.WriteString("]}}")

return out.String()
}
246 changes: 246 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
package parser

import (
"errors"
"fmt"
"strconv"

"github.com/JunNishimura/jsop/ast"
"github.com/JunNishimura/jsop/lexer"
"github.com/JunNishimura/jsop/token"
)

type Parser struct {
l *lexer.Lexer

curToken token.Token
peekToken token.Token

errors error
}

func New(l *lexer.Lexer) *Parser {
p := &Parser{
l: l,
}

p.nextToken()
p.nextToken()

return p
}

func (p *Parser) Errors() []string {
out := []string{}

if errs, ok := p.errors.(interface{ Unwrap() []error }); ok {
for _, err := range errs.Unwrap() {
out = append(out, err.Error())
}
} else {
out = append(out, p.errors.Error())
}

return out
}

func (p *Parser) handleErr(err error) {
p.errors = errors.Join(p.errors, err)
}

func (p *Parser) nextToken() {
p.curToken = p.peekToken
p.peekToken = p.l.NextToken()
}

func (p *Parser) ParseProgram() (*ast.Program, error) {
program := &ast.Program{
Expressions: []ast.Expression{},
}

for !p.curTokenIs(token.EOF) {
exp, err := p.parseExpression()
if err != nil {
// run through the rest of the program to find all errors
p.handleErr(err)
}

program.Expressions = append(program.Expressions, exp)
}

// return all errors found after parsing the whole program
if p.errors != nil {
return nil, p.errors
}

return program, nil
}

func (p *Parser) curTokenIs(t token.TokenType) bool {
return p.curToken.Type == t
}

func (p *Parser) expectCurToken(t token.TokenType) error {
if p.curTokenIs(t) {
p.nextToken()
return nil
}

return fmt.Errorf("expected current token to be %s, got %s instead", t, p.curToken.Type)
}

func (p *Parser) expectTokens(tokens ...token.TokenType) error {
for _, t := range tokens {
if !p.curTokenIs(t) {
return fmt.Errorf("expected current token to be %s, got %s instead", t, p.curToken.Type)
}
p.nextToken()
}

return nil
}

func (p *Parser) parseExpression() (ast.Expression, error) {
defer p.nextToken()

if p.curTokenIs(token.LBRACE) {
return p.parseObject()
}
return p.parseAtom()
}

func (p *Parser) parseObject() (ast.Expression, error) {
if err := p.expectCurToken(token.LBRACE); err != nil {
return nil, err
}

// parse main key
if err := p.expectCurToken(token.DOUBLE_QUOTE); err != nil {
return nil, err
}
switch p.curToken.Type {
case token.COMMAND:
return p.parseCommand()
default:
return nil, fmt.Errorf("unexpected token type %s", p.curToken.Type)
}
}

func (p *Parser) parseCommand() (*ast.CommandObject, error) {
commandToken := p.curToken

// skip to symbol
if err := p.expectTokens(
token.COMMAND,
token.DOUBLE_QUOTE,
token.COLON,
token.LBRACE,
token.DOUBLE_QUOTE,
token.SYMBOL,
token.DOUBLE_QUOTE,
token.COLON,
token.DOUBLE_QUOTE,
); err != nil {
return nil, err
}

// parse symbol
symbol := &ast.Symbol{
Token: p.curToken,
Value: p.curToken.Literal,
}
p.nextToken()

// skip to args
if err := p.expectTokens(
token.DOUBLE_QUOTE,
token.COMMA,
token.DOUBLE_QUOTE,
token.ARGS,
token.DOUBLE_QUOTE,
token.COLON,
); err != nil {
return nil, err
}

// parse args
args, err := p.parseArgs()
if err != nil {
return nil, err
}

// skip to end of object
if err := p.expectTokens(token.RBRACE, token.RBRACE); err != nil {
return nil, err
}

return &ast.CommandObject{
Token: commandToken,
Symbol: symbol,
Args: args,
}, nil
}

func (p *Parser) parseArgs() ([]ast.Expression, error) {
args := []ast.Expression{}

if err := p.expectCurToken(token.LBRACKET); err != nil {
return nil, err
}

for !p.curTokenIs(token.RBRACKET) {
arg, err := p.parseExpression()
if err != nil {
return nil, err
}

args = append(args, arg)

if p.curTokenIs(token.COMMA) {
p.nextToken()
}
}

if err := p.expectCurToken(token.RBRACKET); err != nil {
return nil, err
}

return args, nil
}

func (p *Parser) parseAtom() (ast.Expression, error) {
switch p.curToken.Type {
case token.MINUS:
return p.parsePrefixAtom()
case token.INT:
return p.parseIntegerLiteral()
default:
return nil, fmt.Errorf("unexpected token type %s", p.curToken.Type)
}
}

func (p *Parser) parsePrefixAtom() (*ast.PrefixAtom, error) {
pa := &ast.PrefixAtom{
Token: p.curToken,
Operator: p.curToken.Literal,
}

p.nextToken()

right, err := p.parseAtom()
if err != nil {
return nil, err
}
pa.Right = right

return pa, nil
}

func (p *Parser) parseIntegerLiteral() (*ast.IntegerLiteral, error) {
intValue, err := strconv.ParseInt(p.curToken.Literal, 0, 64)
if err != nil {
return nil, fmt.Errorf("could not parse %q as integer", p.curToken.Literal)
}

return &ast.IntegerLiteral{Token: p.curToken, Value: intValue}, nil
}
Loading

0 comments on commit 67c822c

Please sign in to comment.