Skip to content

Commit 2a1d303

Browse files
authored
Refactor Lexer, Parser, Token into packages (#427)
* refactor: lexer, parser, token packages * refactor: fix tests * refactor: remove stutter in lexer.NewLexer and parser.NewParser * refactor: remove dot imports
1 parent 7dd24a2 commit 2a1d303

18 files changed

+1145
-1116
lines changed

command.go

Lines changed: 86 additions & 141 deletions
Large diffs are not rendered by default.

command_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package main
33
import (
44
"reflect"
55
"testing"
6+
7+
"github.com/charmbracelet/vhs/parser"
68
)
79

810
func TestCommand(t *testing.T) {
911
const numberOfCommands = 27
10-
if len(CommandTypes) != numberOfCommands {
11-
t.Errorf("Expected %d commands, got %d", numberOfCommands, len(CommandTypes))
12+
if len(parser.CommandTypes) != numberOfCommands {
13+
t.Errorf("Expected %d commands, got %d", numberOfCommands, len(parser.CommandTypes))
1214
}
1315

1416
const numberOfCommandFuncs = 27

draw.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -205,20 +205,6 @@ func MakeBorderRadiusMask(width, height, radius int, targetpng string) {
205205
}
206206
}
207207

208-
// Check if a given windowbar type is valid
209-
func isValidWindowBar(windowbar string) bool {
210-
switch windowbar {
211-
case
212-
"",
213-
"Colorful",
214-
"ColorfulRight",
215-
"Rings",
216-
"RingsRight":
217-
return true
218-
}
219-
return false
220-
}
221-
222208
// Make a window bar and save it to a file
223209
func MakeWindowBar(termWidth, termHeight int, opts StyleOptions, file string) {
224210
var err error

error.go

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,25 @@ import (
44
"fmt"
55
"io"
66
"strings"
7+
8+
"github.com/charmbracelet/vhs/parser"
79
)
810

911
// InvalidSyntaxError is returned when the parser encounters one or more errors.
1012
type InvalidSyntaxError struct {
11-
Errors []ParserError
13+
Errors []parser.Error
1214
}
1315

1416
func (e InvalidSyntaxError) Error() string {
1517
return fmt.Sprintf("parser: %d error(s)", len(e.Errors))
1618
}
1719

18-
// ParserError represents an error with parsing a tape file.
19-
// It tracks the token causing the error and a human readable error message.
20-
type ParserError struct {
21-
Token Token
22-
Msg string
23-
}
24-
25-
// NewError returns a new ParserError with the given token and message.
26-
func NewError(token Token, msg string) ParserError {
27-
return ParserError{
28-
Token: token,
29-
Msg: msg,
30-
}
31-
}
32-
3320
// ErrorColumnOffset is the number of columns that an error should be printed
3421
// to the left to account for the line number.
3522
const ErrorColumnOffset = 5
3623

37-
// String returns a human readable error message printing the token line number
38-
// and message.
39-
func (e ParserError) String() string {
40-
return fmt.Sprintf("%2d:%-2d │ %s", e.Token.Line, e.Token.Column, e.Msg)
41-
}
42-
43-
func (e ParserError) Error() string {
44-
return e.String()
45-
}
46-
4724
// Underline returns a string of ^ characters which helps underline the problematic token
48-
// in a ParserError.
25+
// in a parser.Error.
4926
func Underline(n int) string {
5027
return ErrorStyle.Render(strings.Repeat("^", n))
5128
}
@@ -55,7 +32,7 @@ func LineNumber(line int) string {
5532
return LineNumberStyle.Render(fmt.Sprintf(" %2d │ ", line))
5633
}
5734

58-
func printParserError(out io.Writer, tape string, err ParserError) {
35+
func printError(out io.Writer, tape string, err parser.Error) {
5936
lines := strings.Split(tape, "\n")
6037

6138
fmt.Fprint(out, LineNumber(err.Token.Line))
@@ -70,7 +47,7 @@ func printErrors(out io.Writer, tape string, errs []error) {
7047
switch err := err.(type) {
7148
case InvalidSyntaxError:
7249
for _, v := range err.Errors {
73-
printParserError(out, tape, v)
50+
printError(out, tape, v)
7451
}
7552
fmt.Fprintln(out, ErrorStyle.Render(err.Error()))
7653

evaluator.go

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import (
66
"io"
77
"log"
88
"os"
9+
10+
"github.com/charmbracelet/vhs/lexer"
11+
"github.com/charmbracelet/vhs/parser"
12+
"github.com/charmbracelet/vhs/token"
913
)
1014

1115
// EvaluatorOption is a function that can be used to modify the VHS instance.
@@ -14,8 +18,8 @@ type EvaluatorOption func(*VHS)
1418
// Evaluate takes as input a tape string, an output writer, and an output file
1519
// and evaluates all the commands within the tape string and produces a GIF.
1620
func Evaluate(ctx context.Context, tape string, out io.Writer, opts ...EvaluatorOption) []error {
17-
l := NewLexer(tape)
18-
p := NewParser(l)
21+
l := lexer.New(tape)
22+
p := parser.New(l)
1923

2024
cmds := p.Parse()
2125
errs := p.Errors()
@@ -25,8 +29,8 @@ func Evaluate(ctx context.Context, tape string, out io.Writer, opts ...Evaluator
2529

2630
v := New()
2731
for _, cmd := range cmds {
28-
if cmd.Type == SET && cmd.Options == "Shell" {
29-
cmd.Execute(&v)
32+
if cmd.Type == token.SET && cmd.Options == "Shell" {
33+
Execute(cmd, &v)
3034
}
3135
}
3236

@@ -39,10 +43,10 @@ func Evaluate(ctx context.Context, tape string, out io.Writer, opts ...Evaluator
3943
// Run Output and Set commands as they only modify options on the VHS instance.
4044
var offset int
4145
for i, cmd := range cmds {
42-
if cmd.Type == SET || cmd.Type == OUTPUT || cmd.Type == REQUIRE {
43-
fmt.Fprintln(out, cmd.Highlight(false))
46+
if cmd.Type == token.SET || cmd.Type == token.OUTPUT || cmd.Type == token.REQUIRE {
47+
fmt.Fprintln(out, Highlight(cmd, false))
4448
if cmd.Options != "Shell" {
45-
cmd.Execute(&v)
49+
Execute(cmd, &v)
4650
}
4751
} else {
4852
offset = i
@@ -77,14 +81,14 @@ func Evaluate(ctx context.Context, tape string, out io.Writer, opts ...Evaluator
7781
// If the first command (after Settings and Outputs) is a Hide command, we can
7882
// begin executing the commands before we start recording to avoid capturing
7983
// any unwanted frames.
80-
if cmds[offset].Type == HIDE {
84+
if cmds[offset].Type == token.HIDE {
8185
for i, cmd := range cmds[offset:] {
82-
if cmd.Type == SHOW {
86+
if cmd.Type == token.SHOW {
8387
offset += i
8488
break
8589
}
86-
fmt.Fprintln(out, cmd.Highlight(true))
87-
cmd.Execute(&v)
90+
fmt.Fprintln(out, Highlight(cmd, true))
91+
Execute(cmd, &v)
8892
}
8993
}
9094

@@ -130,13 +134,13 @@ func Evaluate(ctx context.Context, tape string, out io.Writer, opts ...Evaluator
130134
// GIF as the frame sequence will change dimensions. This is fixable.
131135
//
132136
// We should remove if isSetting statement.
133-
isSetting := cmd.Type == SET && cmd.Options != "TypingSpeed"
134-
if isSetting || cmd.Type == REQUIRE {
135-
fmt.Fprintln(out, cmd.Highlight(true))
137+
isSetting := cmd.Type == token.SET && cmd.Options != "TypingSpeed"
138+
if isSetting || cmd.Type == token.REQUIRE {
139+
fmt.Fprintln(out, Highlight(cmd, true))
136140
continue
137141
}
138-
fmt.Fprintln(out, cmd.Highlight(!v.recording || cmd.Type == SHOW || cmd.Type == HIDE || isSetting))
139-
cmd.Execute(&v)
142+
fmt.Fprintln(out, Highlight(cmd, !v.recording || cmd.Type == token.SHOW || cmd.Type == token.HIDE || isSetting))
143+
Execute(cmd, &v)
140144
}
141145

142146
// If running as an SSH server, the output file is a temporary file

examples/demo.gif

Lines changed: 2 additions & 2 deletions
Loading

lexer.go renamed to lexer/lexer.go

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
package main
1+
package lexer
2+
3+
import "github.com/charmbracelet/vhs/token"
24

35
// Lexer is a lexer that tokenizes the input.
46
type Lexer struct {
@@ -10,8 +12,8 @@ type Lexer struct {
1012
column int
1113
}
1214

13-
// NewLexer returns a new lexer for tokenizing the input string.
14-
func NewLexer(input string) *Lexer {
15+
// New returns a new lexer for tokenizing the input string.
16+
func New(input string) *Lexer {
1517
l := &Lexer{input: input, line: 1, column: 0}
1618
l.readChar()
1719
return l
@@ -26,64 +28,64 @@ func (l *Lexer) readChar() {
2628
}
2729

2830
// NextToken returns the next token in the input.
29-
func (l *Lexer) NextToken() Token {
31+
func (l *Lexer) NextToken() token.Token {
3032
l.skipWhitespace()
3133

32-
var tok = Token{Line: l.line, Column: l.column}
34+
var tok = token.Token{Line: l.line, Column: l.column}
3335

3436
switch l.ch {
3537
case 0:
36-
tok = l.newToken(EOF, l.ch)
38+
tok = l.newToken(token.EOF, l.ch)
3739
case '@':
38-
tok = l.newToken(AT, l.ch)
40+
tok = l.newToken(token.AT, l.ch)
3941
l.readChar()
4042
case '=':
41-
tok = l.newToken(EQUAL, l.ch)
43+
tok = l.newToken(token.EQUAL, l.ch)
4244
l.readChar()
4345
case '%':
44-
tok = l.newToken(PERCENT, l.ch)
46+
tok = l.newToken(token.PERCENT, l.ch)
4547
l.readChar()
4648
case '#':
47-
tok.Type = COMMENT
49+
tok.Type = token.COMMENT
4850
tok.Literal = l.readComment()
4951
case '+':
50-
tok = l.newToken(PLUS, l.ch)
52+
tok = l.newToken(token.PLUS, l.ch)
5153
l.readChar()
5254
case '{':
53-
tok.Type = JSON
55+
tok.Type = token.JSON
5456
tok.Literal = "{" + l.readJSON() + "}"
5557
l.readChar()
5658
case '`':
57-
tok.Type = STRING
59+
tok.Type = token.STRING
5860
tok.Literal = l.readString('`')
5961
l.readChar()
6062
case '\'':
61-
tok.Type = STRING
63+
tok.Type = token.STRING
6264
tok.Literal = l.readString('\'')
6365
l.readChar()
6466
case '"':
65-
tok.Type = STRING
67+
tok.Type = token.STRING
6668
tok.Literal = l.readString('"')
6769
l.readChar()
6870
default:
6971
if isDigit(l.ch) || (isDot(l.ch) && isDigit(l.peekChar())) {
7072
tok.Literal = l.readNumber()
71-
tok.Type = NUMBER
73+
tok.Type = token.NUMBER
7274
} else if isLetter(l.ch) || isDot(l.ch) {
7375
tok.Literal = l.readIdentifier()
74-
tok.Type = LookupIdentifier(tok.Literal)
76+
tok.Type = token.LookupIdentifier(tok.Literal)
7577
} else {
76-
tok = l.newToken(ILLEGAL, l.ch)
78+
tok = l.newToken(token.ILLEGAL, l.ch)
7779
l.readChar()
7880
}
7981
}
8082
return tok
8183
}
8284

8385
// newToken creates a new token with the given type and literal.
84-
func (l *Lexer) newToken(tokenType TokenType, ch byte) Token {
86+
func (l *Lexer) newToken(tokenType token.Type, ch byte) token.Token {
8587
literal := string(ch)
86-
return Token{
88+
return token.Token{
8789
Type: tokenType,
8890
Literal: literal,
8991
Line: l.line,

0 commit comments

Comments
 (0)