aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2022-05-09 00:01:02 +0200
committerMel <einebeere@gmail.com>2022-05-09 00:01:02 +0200
commitb09a14147d397904722ee7c25e4defc56135b96f (patch)
tree694dc725528310f3a65d04785b8eea33908ce69f /pkg
parentb5a9660b6ac42bce27c746e76013c3ce5992743a (diff)
downloadjinx-b09a14147d397904722ee7c25e4defc56135b96f.tar.zst
jinx-b09a14147d397904722ee7c25e4defc56135b96f.zip
Extract source walk part of scanner
Diffstat (limited to 'pkg')
-rw-r--r--pkg/lang/ast/expr.go4
-rw-r--r--pkg/lang/ast/nodes.go8
-rw-r--r--pkg/lang/ast/stmt.go4
-rw-r--r--pkg/lang/parser/parser_test.go92
-rw-r--r--pkg/lang/scanner/errors.go9
-rw-r--r--pkg/lang/scanner/scanner.go119
-rw-r--r--pkg/lang/scanner/scanner_test.go165
-rw-r--r--pkg/lang/scanner/token/token.go8
-rw-r--r--pkg/libs/source/errors.go10
-rw-r--r--pkg/libs/source/loc.go (renamed from pkg/lang/scanner/token/loc.go)2
-rw-r--r--pkg/libs/source/walker.go80
11 files changed, 261 insertions, 240 deletions
diff --git a/pkg/lang/ast/expr.go b/pkg/lang/ast/expr.go
index ab6578a..f98f9b3 100644
--- a/pkg/lang/ast/expr.go
+++ b/pkg/lang/ast/expr.go
@@ -1,6 +1,6 @@
package ast
-import "jinx/pkg/lang/scanner/token"
+import "jinx/pkg/libs/source"
type ExprKind int
@@ -25,7 +25,7 @@ const (
type Expr ExprT[any]
type ExprT[T any] struct {
- At token.Loc
+ At source.Loc
Kind ExprKind
Value T
}
diff --git a/pkg/lang/ast/nodes.go b/pkg/lang/ast/nodes.go
index 55dbb5b..117d8bf 100644
--- a/pkg/lang/ast/nodes.go
+++ b/pkg/lang/ast/nodes.go
@@ -1,19 +1,19 @@
package ast
-import "jinx/pkg/lang/scanner/token"
+import "jinx/pkg/libs/source"
type IdentNode struct {
- At token.Loc
+ At source.Loc
Value string
}
type BlockNode struct {
- At token.Loc
+ At source.Loc
Stmts []Stmt
}
type CondNode struct {
- At token.Loc
+ At source.Loc
Cond Expr
Then BlockNode
}
diff --git a/pkg/lang/ast/stmt.go b/pkg/lang/ast/stmt.go
index 4f24cc5..e25dee9 100644
--- a/pkg/lang/ast/stmt.go
+++ b/pkg/lang/ast/stmt.go
@@ -1,6 +1,6 @@
package ast
-import "jinx/pkg/lang/scanner/token"
+import "jinx/pkg/libs/source"
type StmtKind int
@@ -22,7 +22,7 @@ const (
type Stmt StmtT[any]
type StmtT[T any] struct {
- At token.Loc
+ At source.Loc
Kind StmtKind
Value T
}
diff --git a/pkg/lang/parser/parser_test.go b/pkg/lang/parser/parser_test.go
index 969e878..c3794e9 100644
--- a/pkg/lang/parser/parser_test.go
+++ b/pkg/lang/parser/parser_test.go
@@ -4,7 +4,7 @@ import (
"jinx/pkg/lang/ast"
"jinx/pkg/lang/parser"
"jinx/pkg/lang/scanner"
- "jinx/pkg/lang/scanner/token"
+ "jinx/pkg/libs/source"
"strings"
"testing"
@@ -12,8 +12,8 @@ import (
)
func TestIntLit(t *testing.T) {
- source := `123`
- p := cheatWithScanner(t, source)
+ src := `123`
+ p := cheatWithScanner(t, src)
program, err := p.Parse()
require.NoError(t, err)
@@ -30,8 +30,8 @@ func TestIntLit(t *testing.T) {
}
func TestSimpleBinaryExpr(t *testing.T) {
- source := `1 + 2`
- p := cheatWithScanner(t, source)
+ src := `1 + 2`
+ p := cheatWithScanner(t, src)
program, err := p.Parse()
require.NoError(t, err)
@@ -48,7 +48,7 @@ func TestSimpleBinaryExpr(t *testing.T) {
},
Op: ast.BinOpPlus,
Right: ast.Expr{
- At: token.NewLoc(0, 4),
+ At: source.NewLoc(0, 4),
Kind: ast.ExprKindIntLit,
Value: ast.ExprIntLit{Value: 2},
},
@@ -59,8 +59,8 @@ func TestSimpleBinaryExpr(t *testing.T) {
}
func TestLeftAssocBinaryExpr(t *testing.T) {
- source := `1 + 2 + 3`
- p := cheatWithScanner(t, source)
+ src := `1 + 2 + 3`
+ p := cheatWithScanner(t, src)
program, err := p.Parse()
require.NoError(t, err)
@@ -80,7 +80,7 @@ func TestLeftAssocBinaryExpr(t *testing.T) {
},
Op: ast.BinOpPlus,
Right: ast.Expr{
- At: token.NewLoc(0, 4),
+ At: source.NewLoc(0, 4),
Kind: ast.ExprKindIntLit,
Value: ast.ExprIntLit{Value: 2},
},
@@ -88,7 +88,7 @@ func TestLeftAssocBinaryExpr(t *testing.T) {
},
Op: ast.BinOpPlus,
Right: ast.Expr{
- At: token.NewLoc(0, 8),
+ At: source.NewLoc(0, 8),
Kind: ast.ExprKindIntLit,
Value: ast.ExprIntLit{Value: 3},
},
@@ -99,8 +99,8 @@ func TestLeftAssocBinaryExpr(t *testing.T) {
}
func TestRightAssocBinaryExpr(t *testing.T) {
- source := `x = y = z`
- p := cheatWithScanner(t, source)
+ src := `x = y = z`
+ p := cheatWithScanner(t, src)
program, err := p.Parse()
require.NoError(t, err)
@@ -117,19 +117,19 @@ func TestRightAssocBinaryExpr(t *testing.T) {
},
Op: ast.BinOpAssign,
Right: ast.Expr{
- At: token.NewLoc(0, 4),
+ At: source.NewLoc(0, 4),
Kind: ast.ExprKindBinary,
Value: ast.ExprBinary{
Left: ast.Expr{
- At: token.NewLoc(0, 4),
+ At: source.NewLoc(0, 4),
Kind: ast.ExprKindIdent,
- Value: ast.ExprIdent{Value: ast.IdentNode{At: token.NewLoc(0, 4), Value: "y"}},
+ Value: ast.ExprIdent{Value: ast.IdentNode{At: source.NewLoc(0, 4), Value: "y"}},
},
Op: ast.BinOpAssign,
Right: ast.Expr{
- At: token.NewLoc(0, 8),
+ At: source.NewLoc(0, 8),
Kind: ast.ExprKindIdent,
- Value: ast.ExprIdent{Value: ast.IdentNode{At: token.NewLoc(0, 8), Value: "z"}},
+ Value: ast.ExprIdent{Value: ast.IdentNode{At: source.NewLoc(0, 8), Value: "z"}},
},
},
},
@@ -140,8 +140,8 @@ func TestRightAssocBinaryExpr(t *testing.T) {
}
func TestVarDecl(t *testing.T) {
- source := `var x = 123`
- p := cheatWithScanner(t, source)
+ src := `var x = 123`
+ p := cheatWithScanner(t, src)
program, err := p.Parse()
require.NoError(t, err)
@@ -149,9 +149,9 @@ func TestVarDecl(t *testing.T) {
require.Equal(t, ast.Stmt{
Kind: ast.StmtKindVarDecl,
Value: ast.StmtVarDecl{
- Name: ast.IdentNode{At: token.NewLoc(0, 4), Value: "x"},
+ Name: ast.IdentNode{At: source.NewLoc(0, 4), Value: "x"},
Value: ast.Expr{
- At: token.NewLoc(0, 8),
+ At: source.NewLoc(0, 8),
Kind: ast.ExprKindIntLit,
Value: ast.ExprIntLit{Value: 123},
},
@@ -160,14 +160,14 @@ func TestVarDecl(t *testing.T) {
}
func TestEmptyStmt(t *testing.T) {
- source := `
+ src := `
;
`
- p := cheatWithScanner(t, source)
+ p := cheatWithScanner(t, src)
program, err := p.Parse()
require.NoError(t, err)
@@ -179,17 +179,17 @@ func TestEmptyStmt(t *testing.T) {
Value: ast.StmtEmpty{},
},
{
- At: token.NewLoc(3, 1),
+ At: source.NewLoc(3, 1),
Kind: ast.StmtKindEmpty,
Value: ast.StmtEmpty{},
},
{
- At: token.NewLoc(3, 2),
+ At: source.NewLoc(3, 2),
Kind: ast.StmtKindEmpty,
Value: ast.StmtEmpty{},
},
{
- At: token.NewLoc(6, 1),
+ At: source.NewLoc(6, 1),
Kind: ast.StmtKindEmpty,
Value: ast.StmtEmpty{},
},
@@ -199,13 +199,13 @@ func TestEmptyStmt(t *testing.T) {
}
func TestMultipleStmts(t *testing.T) {
- source := sourceify(
+ src := sourceify(
`var x = 1`,
`var y = 2`,
`z = 3`,
)
- p := cheatWithScanner(t, source)
+ p := cheatWithScanner(t, src)
program, err := p.Parse()
require.NoError(t, err)
@@ -215,42 +215,42 @@ func TestMultipleStmts(t *testing.T) {
{
Kind: ast.StmtKindVarDecl,
Value: ast.StmtVarDecl{
- Name: ast.IdentNode{At: token.NewLoc(0, 4), Value: "x"},
+ Name: ast.IdentNode{At: source.NewLoc(0, 4), Value: "x"},
Value: ast.Expr{
- At: token.NewLoc(0, 8),
+ At: source.NewLoc(0, 8),
Kind: ast.ExprKindIntLit,
Value: ast.ExprIntLit{Value: 1},
},
},
},
{
- At: token.NewLoc(1, 0),
+ At: source.NewLoc(1, 0),
Kind: ast.StmtKindVarDecl,
Value: ast.StmtVarDecl{
- Name: ast.IdentNode{At: token.NewLoc(1, 4), Value: "y"},
+ Name: ast.IdentNode{At: source.NewLoc(1, 4), Value: "y"},
Value: ast.Expr{
- At: token.NewLoc(1, 8),
+ At: source.NewLoc(1, 8),
Kind: ast.ExprKindIntLit,
Value: ast.ExprIntLit{Value: 2},
},
},
},
{
- At: token.NewLoc(2, 0),
+ At: source.NewLoc(2, 0),
Kind: ast.StmtKindExpr,
Value: ast.StmtExpr{
Value: ast.Expr{
- At: token.NewLoc(2, 0),
+ At: source.NewLoc(2, 0),
Kind: ast.ExprKindBinary,
Value: ast.ExprBinary{
Left: ast.Expr{
- At: token.NewLoc(2, 0),
+ At: source.NewLoc(2, 0),
Kind: ast.ExprKindIdent,
- Value: ast.ExprIdent{Value: ast.IdentNode{At: token.NewLoc(2, 0), Value: "z"}},
+ Value: ast.ExprIdent{Value: ast.IdentNode{At: source.NewLoc(2, 0), Value: "z"}},
},
Op: ast.BinOpAssign,
Right: ast.Expr{
- At: token.NewLoc(2, 4),
+ At: source.NewLoc(2, 4),
Kind: ast.ExprKindIntLit,
Value: ast.ExprIntLit{Value: 3},
},
@@ -263,12 +263,12 @@ func TestMultipleStmts(t *testing.T) {
}
func TestIfStmt(t *testing.T) {
- source := sourceify(
+ src := sourceify(
`if false {`,
` var x = 2`,
`}`,
)
- p := cheatWithScanner(t, source)
+ p := cheatWithScanner(t, src)
program, err := p.Parse()
require.NoError(t, err)
@@ -277,25 +277,25 @@ func TestIfStmt(t *testing.T) {
Kind: ast.StmtKindIf,
Value: ast.StmtIf{
Cond: ast.Expr{
- At: token.NewLoc(0, 3),
+ At: source.NewLoc(0, 3),
Kind: ast.ExprKindBoolLit,
Value: ast.ExprBoolLit{Value: false},
},
Then: ast.BlockNode{
- At: token.NewLoc(0, 9),
+ At: source.NewLoc(0, 9),
Stmts: []ast.Stmt{
{
- At: token.NewLoc(0, 10),
+ At: source.NewLoc(0, 10),
Kind: ast.StmtKindEmpty,
Value: ast.StmtEmpty{},
},
{
- At: token.NewLoc(1, 1),
+ At: source.NewLoc(1, 1),
Kind: ast.StmtKindVarDecl,
Value: ast.StmtVarDecl{
- Name: ast.IdentNode{At: token.NewLoc(1, 5), Value: "x"},
+ Name: ast.IdentNode{At: source.NewLoc(1, 5), Value: "x"},
Value: ast.Expr{
- At: token.NewLoc(1, 9),
+ At: source.NewLoc(1, 9),
Kind: ast.ExprKindIntLit,
Value: ast.ExprIntLit{Value: 2},
},
diff --git a/pkg/lang/scanner/errors.go b/pkg/lang/scanner/errors.go
index dd9aae3..6a6f9d9 100644
--- a/pkg/lang/scanner/errors.go
+++ b/pkg/lang/scanner/errors.go
@@ -7,15 +7,6 @@ var (
ErrUnclosedString = errors.New("unclosed string")
)
-type ErrUnexpectedChar struct {
- Expected rune
- Actual rune
-}
-
-func (e ErrUnexpectedChar) Error() string {
- return "unexpected character: expected " + string(e.Expected) + ", actual " + string(e.Actual)
-}
-
type ErrUnknownChar struct {
Char rune
}
diff --git a/pkg/lang/scanner/scanner.go b/pkg/lang/scanner/scanner.go
index 19e3462..2991083 100644
--- a/pkg/lang/scanner/scanner.go
+++ b/pkg/lang/scanner/scanner.go
@@ -1,29 +1,24 @@
package scanner
import (
- "bufio"
- "errors"
"io"
"jinx/pkg/lang/scanner/token"
+ "jinx/pkg/libs/source"
"strconv"
"strings"
"unicode"
)
type Scanner struct {
- source *bufio.Reader
- row int
- col int
+ src source.Walker
indent int
finished bool
}
-func New(source io.Reader) *Scanner {
+func New(src io.Reader) *Scanner {
return &Scanner{
- source: bufio.NewReader(source),
- row: 0,
- col: 0,
+ src: *source.NewWalker(src),
indent: 0,
finished: false,
}
@@ -62,14 +57,14 @@ func (s *Scanner) scanToken() (token.Token, error) {
return token.New(token.EOL, firstNewline, nil), nil
}
- c, eof, err := s.peek()
+ c, eof, err := s.src.Peek()
if err != nil {
return token.Token{}, err
}
if eof {
s.finished = true
- return token.Simple(token.EOF, s.loc()), nil
+ return token.Simple(token.EOF, s.src.Loc()), nil
}
if c == '"' {
@@ -80,8 +75,8 @@ func (s *Scanner) scanToken() (token.Token, error) {
return s.scanNumber()
}
- loc := s.loc()
- c, _, err = s.next()
+ loc := s.src.Loc()
+ c, _, err = s.src.Next()
if err != nil {
return token.Token{}, err
}
@@ -91,7 +86,7 @@ func (s *Scanner) scanToken() (token.Token, error) {
case '\n':
kind = token.EOL
case '=':
- if cont, err := s.consume('='); cont && err == nil {
+ if cont, err := s.src.Consume('='); cont && err == nil {
kind = token.Eq
} else if cont && err != nil {
kind = token.Assign
@@ -110,7 +105,7 @@ func (s *Scanner) scanToken() (token.Token, error) {
kind = token.Percent
case '<':
- if cont, err := s.consume('='); cont && err == nil {
+ if cont, err := s.src.Consume('='); cont && err == nil {
kind = token.Lte
} else if cont && err != nil {
kind = token.Lt
@@ -118,7 +113,7 @@ func (s *Scanner) scanToken() (token.Token, error) {
return token.Token{}, err
}
case '>':
- if cont, err := s.consume('='); cont && err == nil {
+ if cont, err := s.src.Consume('='); cont && err == nil {
kind = token.Gte
} else if cont && err != nil {
kind = token.Gt
@@ -126,7 +121,7 @@ func (s *Scanner) scanToken() (token.Token, error) {
return token.Token{}, err
}
case '!':
- if cont, err := s.consume('='); cont && err == nil {
+ if cont, err := s.src.Consume('='); cont && err == nil {
kind = token.Neq
} else if cont && err != nil {
kind = token.Bang
@@ -162,15 +157,15 @@ func (s *Scanner) scanToken() (token.Token, error) {
}
func (s *Scanner) scanString() (token.Token, error) {
- loc := s.loc()
- if _, err := s.consume('"'); err != nil {
+ loc := s.src.Loc()
+ if _, err := s.src.Consume('"'); err != nil {
return token.Token{}, err
}
var buf strings.Builder
for {
- c, eof, err := s.next()
+ c, eof, err := s.src.Next()
if err != nil {
return token.Token{}, err
}
@@ -190,12 +185,12 @@ func (s *Scanner) scanString() (token.Token, error) {
}
func (s *Scanner) scanIdentifierOrKeyword() (token.Token, error) {
- loc := s.loc()
+ loc := s.src.Loc()
var buf strings.Builder
for {
- c, eof, err := s.peek()
+ c, eof, err := s.src.Peek()
if err != nil {
return token.Token{}, err
}
@@ -204,7 +199,7 @@ func (s *Scanner) scanIdentifierOrKeyword() (token.Token, error) {
break
}
- if _, _, err = s.next(); err != nil {
+ if _, _, err = s.src.Next(); err != nil {
return token.Token{}, err
}
@@ -270,12 +265,12 @@ func (s *Scanner) scanIdentifierOrKeyword() (token.Token, error) {
}
func (s *Scanner) scanNumber() (token.Token, error) {
- loc := s.loc()
+ loc := s.src.Loc()
var buf strings.Builder
for {
- c, eof, err := s.peek()
+ c, eof, err := s.src.Peek()
if err != nil {
return token.Token{}, err
}
@@ -284,7 +279,7 @@ func (s *Scanner) scanNumber() (token.Token, error) {
break
}
- if _, _, err = s.next(); err != nil {
+ if _, _, err = s.src.Next(); err != nil {
return token.Token{}, err
}
@@ -299,18 +294,18 @@ func (s *Scanner) scanNumber() (token.Token, error) {
return token.New(token.Int, loc, num), nil
}
-func (s *Scanner) skipWhitespace() (bool, token.Loc, error) {
+func (s *Scanner) skipWhitespace() (bool, source.Loc, error) {
hadNewline := false
- firstNewline := token.Loc{}
+ firstNewline := source.Loc{}
for {
- c, eof, err := s.peek()
+ c, eof, err := s.src.Peek()
if err != nil {
- return false, token.Loc{}, err
+ return false, source.Loc{}, err
}
if c == '\n' && !hadNewline {
- firstNewline = s.loc()
+ firstNewline = s.src.Loc()
hadNewline = true
}
@@ -318,68 +313,10 @@ func (s *Scanner) skipWhitespace() (bool, token.Loc, error) {
break
}
- if _, _, err = s.next(); err != nil {
- return false, token.Loc{}, err
+ if _, _, err = s.src.Next(); err != nil {
+ return false, source.Loc{}, err
}
}
return hadNewline, firstNewline, nil
}
-
-func (s *Scanner) loc() token.Loc {
- return token.Loc{
- Row: s.row,
- Col: s.col,
- }
-}
-
-func (s *Scanner) next() (rune, bool, error) {
- r, _, err := s.source.ReadRune()
- if err != nil {
- if errors.Is(err, io.EOF) {
- return 0, true, nil
- }
-
- return 0, false, err
- }
-
- if r == '\n' {
- s.row++
- s.col = 0
- } else {
- s.col++
- }
-
- return r, false, nil
-}
-
-func (s *Scanner) consume(want rune) (bool, error) {
- c, _, err := s.next()
- if err != nil {
- return false, err
- }
-
- if c != want {
- return true, ErrUnexpectedChar{
- Expected: want,
- Actual: c,
- }
- }
-
- return true, nil
-}
-
-func (s *Scanner) peek() (rune, bool, error) {
- r, _, err := s.source.ReadRune()
- defer s.source.UnreadRune()
-
- if err != nil {
- if errors.Is(err, io.EOF) {
- return 0, true, nil
- }
-
- return 0, false, err
- }
-
- return r, false, nil
-}
diff --git a/pkg/lang/scanner/scanner_test.go b/pkg/lang/scanner/scanner_test.go
index 99e77fd..4df4b23 100644
--- a/pkg/lang/scanner/scanner_test.go
+++ b/pkg/lang/scanner/scanner_test.go
@@ -3,6 +3,7 @@ package scanner_test
import (
"jinx/pkg/lang/scanner"
"jinx/pkg/lang/scanner/token"
+ "jinx/pkg/libs/source"
"strings"
"testing"
@@ -10,7 +11,7 @@ import (
)
func TestBasic(t *testing.T) {
- source := `
+ src := `
fn basic() {
var x = 1
var y = x + 1
@@ -22,158 +23,158 @@ func TestBasic(t *testing.T) {
return true
}`
- s := scanner.New(strings.NewReader(source))
+ s := scanner.New(strings.NewReader(src))
tokens, err := s.Scan()
require.NoError(t, err)
expected := []token.Token{
- token.Simple(token.EOL, token.NewLoc(0, 0)),
+ token.Simple(token.EOL, source.NewLoc(0, 0)),
- token.Simple(token.KwFn, token.NewLoc(1, 1)),
- token.New(token.Ident, token.NewLoc(1, 4), "basic"),
- token.Simple(token.LParen, token.NewLoc(1, 9)),
- token.Simple(token.RParen, token.NewLoc(1, 10)),
- token.Simple(token.LBrace, token.NewLoc(1, 12)),
- token.Simple(token.EOL, token.NewLoc(1, 13)),
+ token.Simple(token.KwFn, source.NewLoc(1, 1)),
+ token.New(token.Ident, source.NewLoc(1, 4), "basic"),
+ token.Simple(token.LParen, source.NewLoc(1, 9)),
+ token.Simple(token.RParen, source.NewLoc(1, 10)),
+ token.Simple(token.LBrace, source.NewLoc(1, 12)),
+ token.Simple(token.EOL, source.NewLoc(1, 13)),
- token.Simple(token.KwVar, token.NewLoc(2, 2)),
- token.New(token.Ident, token.NewLoc(2, 6), "x"),
- token.Simple(token.Assign, token.NewLoc(2, 8)),
- token.New(token.Int, token.NewLoc(2, 10), uint64(1)),
- token.Simple(token.EOL, token.NewLoc(2, 11)),
+ token.Simple(token.KwVar, source.NewLoc(2, 2)),
+ token.New(token.Ident, source.NewLoc(2, 6), "x"),
+ token.Simple(token.Assign, source.NewLoc(2, 8)),
+ token.New(token.Int, source.NewLoc(2, 10), uint64(1)),
+ token.Simple(token.EOL, source.NewLoc(2, 11)),
- token.Simple(token.KwVar, token.NewLoc(3, 2)),
- token.New(token.Ident, token.NewLoc(3, 6), "y"),
- token.Simple(token.Assign, token.NewLoc(3, 8)),
- token.New(token.Ident, token.NewLoc(3, 10), "x"),
- token.Simple(token.Plus, token.NewLoc(3, 12)),
- token.New(token.Int, token.NewLoc(3, 14), uint64(1)),
- token.Simple(token.EOL, token.NewLoc(3, 15)),
+ token.Simple(token.KwVar, source.NewLoc(3, 2)),
+ token.New(token.Ident, source.NewLoc(3, 6), "y"),
+ token.Simple(token.Assign, source.NewLoc(3, 8)),
+ token.New(token.Ident, source.NewLoc(3, 10), "x"),
+ token.Simple(token.Plus, source.NewLoc(3, 12)),
+ token.New(token.Int, source.NewLoc(3, 14), uint64(1)),
+ token.Simple(token.EOL, source.NewLoc(3, 15)),
- token.Simple(token.KwIf, token.NewLoc(4, 2)),
- token.New(token.Ident, token.NewLoc(4, 5), "x"),
- token.Simple(token.Lt, token.NewLoc(4, 7)),
- token.New(token.Ident, token.NewLoc(4, 9), "y"),
- token.Simple(token.LBrace, token.NewLoc(4, 11)),
- token.Simple(token.EOL, token.NewLoc(4, 12)),
+ token.Simple(token.KwIf, source.NewLoc(4, 2)),
+ token.New(token.Ident, source.NewLoc(4, 5), "x"),
+ token.Simple(token.Lt, source.NewLoc(4, 7)),
+ token.New(token.Ident, source.NewLoc(4, 9), "y"),
+ token.Simple(token.LBrace, source.NewLoc(4, 11)),
+ token.Simple(token.EOL, source.NewLoc(4, 12)),
- token.New(token.Ident, token.NewLoc(5, 3), "say"),
- token.Simple(token.LParen, token.NewLoc(5, 6)),
- token.New(token.String, token.NewLoc(5, 7), "x is less than y"),
- token.Simple(token.RParen, token.NewLoc(5, 25)),
- token.Simple(token.EOL, token.NewLoc(5, 26)),
+ token.New(token.Ident, source.NewLoc(5, 3), "say"),
+ token.Simple(token.LParen, source.NewLoc(5, 6)),
+ token.New(token.String, source.NewLoc(5, 7), "x is less than y"),
+ token.Simple(token.RParen, source.NewLoc(5, 25)),
+ token.Simple(token.EOL, source.NewLoc(5, 26)),
- token.Simple(token.RBrace, token.NewLoc(6, 2)),
- token.Simple(token.KwElse, token.NewLoc(6, 4)),
- token.Simple(token.LBrace, token.NewLoc(6, 9)),
- token.Simple(token.EOL, token.NewLoc(6, 10)),
+ token.Simple(token.RBrace, source.NewLoc(6, 2)),
+ token.Simple(token.KwElse, source.NewLoc(6, 4)),
+ token.Simple(token.LBrace, source.NewLoc(6, 9)),
+ token.Simple(token.EOL, source.NewLoc(6, 10)),
- token.New(token.Ident, token.NewLoc(7, 3), "say"),
- token.Simple(token.LParen, token.NewLoc(7, 6)),
- token.New(token.String, token.NewLoc(7, 7), "x is greater than or equal to y"),
- token.Simple(token.RParen, token.NewLoc(7, 40)),
- token.Simple(token.EOL, token.NewLoc(7, 41)),
+ token.New(token.Ident, source.NewLoc(7, 3), "say"),
+ token.Simple(token.LParen, source.NewLoc(7, 6)),
+ token.New(token.String, source.NewLoc(7, 7), "x is greater than or equal to y"),
+ token.Simple(token.RParen, source.NewLoc(7, 40)),
+ token.Simple(token.EOL, source.NewLoc(7, 41)),
- token.Simple(token.RBrace, token.NewLoc(8, 2)),
- token.Simple(token.EOL, token.NewLoc(8, 3)),
+ token.Simple(token.RBrace, source.NewLoc(8, 2)),
+ token.Simple(token.EOL, source.NewLoc(8, 3)),
- token.Simple(token.KwReturn, token.NewLoc(9, 2)),
- token.Simple(token.KwTrue, token.NewLoc(9, 9)),
- token.Simple(token.EOL, token.NewLoc(9, 13)),
+ token.Simple(token.KwReturn, source.NewLoc(9, 2)),
+ token.Simple(token.KwTrue, source.NewLoc(9, 9)),
+ token.Simple(token.EOL, source.NewLoc(9, 13)),
- token.Simple(token.RBrace, token.NewLoc(10, 1)),
+ token.Simple(token.RBrace, source.NewLoc(10, 1)),
- token.Simple(token.EOF, token.NewLoc(10, 2)),
+ token.Simple(token.EOF, source.NewLoc(10, 2)),
}
require.Equal(t, expected, tokens)
}
func TestTightIdent(t *testing.T) {
- source := `say(message)`
+ src := `say(message)`
- s := scanner.New(strings.NewReader(source))
+ s := scanner.New(strings.NewReader(src))
tokens, err := s.Scan()
require.NoError(t, err)
expected := []token.Token{
- token.New(token.Ident, token.NewLoc(0, 0), "say"),
- token.Simple(token.LParen, token.NewLoc(0, 3)),
- token.New(token.Ident, token.NewLoc(0, 4), "message"),
- token.Simple(token.RParen, token.NewLoc(0, 11)),
- token.Simple(token.EOF, token.NewLoc(0, 12)),
+ token.New(token.Ident, source.NewLoc(0, 0), "say"),
+ token.Simple(token.LParen, source.NewLoc(0, 3)),
+ token.New(token.Ident, source.NewLoc(0, 4), "message"),
+ token.Simple(token.RParen, source.NewLoc(0, 11)),
+ token.Simple(token.EOF, source.NewLoc(0, 12)),
}
require.Equal(t, expected, tokens)
}
func TestTightNumber(t *testing.T) {
- source := `1+2+3`
+ src := `1+2+3`
- s := scanner.New(strings.NewReader(source))
+ s := scanner.New(strings.NewReader(src))
tokens, err := s.Scan()
require.NoError(t, err)
expected := []token.Token{
- token.New(token.Int, token.NewLoc(0, 0), uint64(1)),
- token.Simple(token.Plus, token.NewLoc(0, 1)),
- token.New(token.Int, token.NewLoc(0, 2), uint64(2)),
- token.Simple(token.Plus, token.NewLoc(0, 3)),
- token.New(token.Int, token.NewLoc(0, 4), uint64(3)),
- token.Simple(token.EOF, token.NewLoc(0, 5)),
+ token.New(token.Int, source.NewLoc(0, 0), uint64(1)),
+ token.Simple(token.Plus, source.NewLoc(0, 1)),
+ token.New(token.Int, source.NewLoc(0, 2), uint64(2)),
+ token.Simple(token.Plus, source.NewLoc(0, 3)),
+ token.New(token.Int, source.NewLoc(0, 4), uint64(3)),
+ token.Simple(token.EOF, source.NewLoc(0, 5)),
}
require.Equal(t, expected, tokens)
}
func TestNewlineStacking(t *testing.T) {
- source := `
+ src := `
x
y
`
- s := scanner.New(strings.NewReader(source))
+ s := scanner.New(strings.NewReader(src))
tokens, err := s.Scan()
require.NoError(t, err)
expected := []token.Token{
- token.Simple(token.EOL, token.NewLoc(0, 0)),
- token.New(token.Ident, token.NewLoc(1, 1), "x"),
- token.Simple(token.EOL, token.NewLoc(1, 2)),
- token.New(token.Ident, token.NewLoc(4, 1), "y"),
- token.Simple(token.EOL, token.NewLoc(4, 2)),
- token.Simple(token.EOF, token.NewLoc(5, 1)),
+ token.Simple(token.EOL, source.NewLoc(0, 0)),
+ token.New(token.Ident, source.NewLoc(1, 1), "x"),
+ token.Simple(token.EOL, source.NewLoc(1, 2)),
+ token.New(token.Ident, source.NewLoc(4, 1), "y"),
+ token.Simple(token.EOL, source.NewLoc(4, 2)),
+ token.Simple(token.EOF, source.NewLoc(5, 1)),
}
require.Equal(t, expected, tokens)
}
func TestEmojiInStrings(t *testing.T) {
- source := `
+ src := `
say("🇺🇦" + "❤️!")
`
- s := scanner.New(strings.NewReader(source))
+ s := scanner.New(strings.NewReader(src))
tokens, err := s.Scan()
require.NoError(t, err)
expected := []token.Token{
- token.Simple(token.EOL, token.NewLoc(0, 0)),
- token.New(token.Ident, token.NewLoc(1, 1), "say"),
- token.Simple(token.LParen, token.NewLoc(1, 4)),
- token.New(token.String, token.NewLoc(1, 5), "🇺🇦"),
- token.Simple(token.Plus, token.NewLoc(1, 10)),
- token.New(token.String, token.NewLoc(1, 12), "❤️!"),
- token.Simple(token.RParen, token.NewLoc(1, 17)),
- token.Simple(token.EOL, token.NewLoc(1, 18)),
- token.Simple(token.EOF, token.NewLoc(2, 1)),
+ token.Simple(token.EOL, source.NewLoc(0, 0)),
+ token.New(token.Ident, source.NewLoc(1, 1), "say"),
+ token.Simple(token.LParen, source.NewLoc(1, 4)),
+ token.New(token.String, source.NewLoc(1, 5), "🇺🇦"),
+ token.Simple(token.Plus, source.NewLoc(1, 10)),
+ token.New(token.String, source.NewLoc(1, 12), "❤️!"),
+ token.Simple(token.RParen, source.NewLoc(1, 17)),
+ token.Simple(token.EOL, source.NewLoc(1, 18)),
+ token.Simple(token.EOF, source.NewLoc(2, 1)),
}
require.Equal(t, expected, tokens)
diff --git a/pkg/lang/scanner/token/token.go b/pkg/lang/scanner/token/token.go
index 1ccd864..a39df8a 100644
--- a/pkg/lang/scanner/token/token.go
+++ b/pkg/lang/scanner/token/token.go
@@ -1,19 +1,21 @@
package token
+import "jinx/pkg/libs/source"
+
type Token struct {
Kind TokenKind
- At Loc
+ At source.Loc
Data any
}
-func Simple(kind TokenKind, at Loc) Token {
+func Simple(kind TokenKind, at source.Loc) Token {
return Token{
Kind: kind,
At: at,
}
}
-func New(kind TokenKind, at Loc, data any) Token {
+func New(kind TokenKind, at source.Loc, data any) Token {
return Token{
Kind: kind,
At: at,
diff --git a/pkg/libs/source/errors.go b/pkg/libs/source/errors.go
new file mode 100644
index 0000000..cfcd9e3
--- /dev/null
+++ b/pkg/libs/source/errors.go
@@ -0,0 +1,10 @@
+package source
+
+type ErrUnexpectedChar struct {
+ Expected rune
+ Actual rune
+}
+
+func (e ErrUnexpectedChar) Error() string {
+ return "unexpected character: expected " + string(e.Expected) + ", actual " + string(e.Actual)
+}
diff --git a/pkg/lang/scanner/token/loc.go b/pkg/libs/source/loc.go
index 7936cba..3089d3b 100644
--- a/pkg/lang/scanner/token/loc.go
+++ b/pkg/libs/source/loc.go
@@ -1,4 +1,4 @@
-package token
+package source
type Loc struct {
Row int
diff --git a/pkg/libs/source/walker.go b/pkg/libs/source/walker.go
new file mode 100644
index 0000000..aeac82a
--- /dev/null
+++ b/pkg/libs/source/walker.go
@@ -0,0 +1,80 @@
+package source
+
+import (
+ "bufio"
+ "errors"
+ "io"
+)
+
+type Walker struct {
+ source *bufio.Reader
+
+ row int
+ col int
+}
+
+func NewWalker(source io.Reader) *Walker {
+ return &Walker{
+ source: bufio.NewReader(source),
+ row: 0,
+ col: 0,
+ }
+}
+
+func (w *Walker) Loc() Loc {
+ return Loc{
+ Row: w.row,
+ Col: w.col,
+ }
+}
+
+func (w *Walker) Next() (rune, bool, error) {
+ r, _, err := w.source.ReadRune()
+ if err != nil {
+ if errors.Is(err, io.EOF) {
+ return 0, true, nil
+ }
+
+ return 0, false, err
+ }
+
+ if r == '\n' {
+ w.row++
+ w.col = 0
+ } else {
+ w.col++
+ }
+
+ return r, false, nil
+}
+
+func (w *Walker) Consume(want rune) (bool, error) {
+ c, _, err := w.Next()
+ if err != nil {
+ return false, err
+ }
+
+ if c != want {
+ return true, ErrUnexpectedChar{
+ Expected: want,
+ Actual: c,
+ }
+ }
+
+ return true, nil
+}
+
+func (w *Walker) Peek() (rune, bool, error) {
+ r, _, err := w.source.ReadRune()
+ defer w.source.UnreadRune()
+
+ if err != nil {
+ if errors.Is(err, io.EOF) {
+ return 0, true, nil
+ }
+
+ return 0, false, err
+ }
+
+ return r, false, nil
+}