-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
518 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
name: Go Tests | ||
|
||
on: | ||
push: | ||
branches: [main] | ||
pull_request: | ||
branches: [main] | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v5 | ||
with: | ||
go-version: "1.23.2" | ||
|
||
- name: Run tests | ||
run: go test -v ./... -timeout 5s |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
test: | ||
go test -v ./... -timeout 5s |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
module github.com/wbhob/learn-compilers | ||
|
||
go 1.23.2 | ||
|
||
require github.com/stretchr/testify v1.9.0 | ||
|
||
require ( | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
) | ||
|
||
func runSequence(ast *ASTNode) []float64 { | ||
// ast root is always a sequence | ||
results := make([]float64, 0) | ||
for _, child := range ast.Children { | ||
switch child.Type { | ||
case LOOP: | ||
results = append(results, runLoop(child)...) | ||
case VALUE: | ||
results = append(results, runNumber(child)) | ||
case SEQUENCE: | ||
results = append(results, runSequence(child)...) | ||
} | ||
} | ||
|
||
return results | ||
} | ||
|
||
func runLoop(ast *ASTNode) []float64 { | ||
element := ast.Fields["repeat"].(*ASTNode) | ||
count := ast.Fields["count"].(*ASTNode) | ||
|
||
if count.Type != VALUE { | ||
panic(fmt.Sprintf("Count is not a number: %+v", count)) | ||
} | ||
|
||
countNum, err := strconv.Atoi(count.Fields["value"].(string)) | ||
if err != nil { | ||
panic(fmt.Sprintf("Failed to parse count: %s", count.Fields["value"].(string))) | ||
} | ||
|
||
results := make([]float64, 0) | ||
for i := 0; i < countNum; i++ { | ||
switch element.Type { | ||
case VALUE: | ||
results = append(results, runNumber(element)) | ||
case SEQUENCE: | ||
results = append(results, runSequence(element)...) | ||
default: | ||
panic(fmt.Sprintf("Unknown element type: %s", element.Type)) | ||
} | ||
} | ||
|
||
return results | ||
} | ||
|
||
func runNumber(ast *ASTNode) float64 { | ||
str := ast.Fields["value"].(string) | ||
num, err := strconv.ParseFloat(str, 64) | ||
if err != nil { | ||
panic(fmt.Sprintf("Failed to parse number: %s", str)) | ||
} | ||
|
||
return num | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package main | ||
|
||
import ( | ||
"unicode" | ||
) | ||
|
||
// Token represents a lexical unit in our language | ||
type Token struct { | ||
Type TokenType // The type of the token | ||
Value string // The string value of the token | ||
} | ||
|
||
// TokenType is a string that represents the category of a token | ||
type TokenType string | ||
|
||
// These constants define the possible types of tokens in our language | ||
const ( | ||
NUMBER TokenType = "NUMBER" // Represents numeric values | ||
COMMA TokenType = "COMMA" // Represents the comma separator | ||
X TokenType = "X" // Represents the 'x' used for repetition | ||
LPAREN TokenType = "LPAREN" // Represents a left parenthesis | ||
RPAREN TokenType = "RPAREN" // Represents a right parenthesis | ||
EOF TokenType = "EOF" // Represents the end of the input | ||
) | ||
|
||
// lex function takes a string input and returns a slice of Tokens | ||
// It breaks down the input string into individual lexical units (tokens) | ||
func lex(input string) []Token { | ||
tokens := []Token{} | ||
digits := "" | ||
|
||
// Iterate through each character in the input string | ||
for _, char := range input { | ||
// Skip whitespace characters | ||
if unicode.IsSpace(char) { | ||
// If we've been building a number, add it as a token | ||
if digits != "" { | ||
tokens = append(tokens, Token{Type: NUMBER, Value: digits}) | ||
digits = "" | ||
} | ||
continue | ||
} | ||
|
||
// Handle characters that could be part of a number | ||
if char == '.' || char == '-' || unicode.IsDigit(char) { | ||
digits += string(char) | ||
continue | ||
} | ||
|
||
// If we've been building a number, add it as a token | ||
if digits != "" { | ||
tokens = append(tokens, Token{Type: NUMBER, Value: digits}) | ||
digits = "" | ||
} | ||
|
||
// Handle special characters | ||
switch char { | ||
case ',': | ||
tokens = append(tokens, Token{Type: COMMA, Value: ","}) | ||
case 'x': | ||
tokens = append(tokens, Token{Type: X, Value: "x"}) | ||
case '(': | ||
tokens = append(tokens, Token{Type: LPAREN, Value: "("}) | ||
case ')': | ||
tokens = append(tokens, Token{Type: RPAREN, Value: ")"}) | ||
default: | ||
// If we encounter an unknown character, panic | ||
panic("Unknown character: " + string(char)) | ||
} | ||
} | ||
|
||
// If we've been building a number, add it as a token | ||
if digits != "" { | ||
tokens = append(tokens, Token{Type: NUMBER, Value: digits}) | ||
} | ||
|
||
// Add an EOF token to signify the end of the input | ||
tokens = append(tokens, Token{Type: EOF, Value: ""}) | ||
|
||
return tokens | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package main | ||
|
||
import "fmt" | ||
|
||
func ParseNumberSequenceShorthand(input string) []float64 { | ||
fmt.Print("==========================================\n") | ||
fmt.Printf("Parsing number sequence shorthand: %s\n", input) | ||
tokens := lex(input) | ||
ast := parseSequence(tokens) | ||
return runSequence(ast) | ||
} | ||
|
||
func ValidateNumberSequenceShorthand(input string) error { | ||
tokens := lex(input) | ||
parseSequence(tokens) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package main_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
main "github.com/wbhob/learn-compilers" | ||
) | ||
|
||
func TestParseNumberSequenceShorthand(t *testing.T) { | ||
t.Run("Basic Functionality", func(t *testing.T) { | ||
t.Run("handles repetition shorthand", func(t *testing.T) { | ||
assert.Equal(t, []float64{42, 42, 42}, main.ParseNumberSequenceShorthand("42x3")) | ||
assert.Equal(t, []float64{0, 2, 2, 2, 10, 42, 42, 42}, main.ParseNumberSequenceShorthand("0, 2x3, 10, 42x3")) | ||
assert.Equal(t, []float64{0.1, 0.1, 2.3, 2.3, -4.5, -4.5}, main.ParseNumberSequenceShorthand(".1x2, 2.3x2, -4.5x2")) | ||
}) | ||
|
||
t.Run("handles a group", func(t *testing.T) { | ||
assert.Equal(t, []float64{1, 2, 3, 1, 2, 3}, main.ParseNumberSequenceShorthand("(1, 2, 3)x2")) | ||
}) | ||
|
||
t.Run("handles a nested group", func(t *testing.T) { | ||
assert.Equal(t, []float64{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, main.ParseNumberSequenceShorthand("(((1, 2)x2)x2)x2")) | ||
}) | ||
|
||
t.Run("complex example", func(t *testing.T) { | ||
expected := []float64{1, 2, 1, 2, 42, 42, 42, 0.5, 1.5, 1.5, 8, 16, 8, 16, 8, 16, 0.5, 1.5, 1.5, 8, 16, 8, 16, 8, 16, 5.5} | ||
assert.Equal(t, expected, main.ParseNumberSequenceShorthand("(1, 2)x2, 42x3, (.5, 1.5x2, (8, 16)x3)x2, 5.5")) | ||
}) | ||
}) | ||
|
||
t.Run("Specific Features", func(t *testing.T) { | ||
t.Run("handles single number", func(t *testing.T) { | ||
assert.Equal(t, []float64{42}, main.ParseNumberSequenceShorthand("42")) | ||
}) | ||
|
||
t.Run("handles multiple numbers", func(t *testing.T) { | ||
assert.Equal(t, []float64{1, 2, 3, 4, 5, 6}, main.ParseNumberSequenceShorthand("1, 2, 3, 4, 5, 6")) | ||
}) | ||
|
||
t.Run("handles multiple numbers (strange whitespace)", func(t *testing.T) { | ||
assert.Equal(t, []float64{1, 2, 3, 4, 5, 6}, main.ParseNumberSequenceShorthand(" 1,2, 3 , 4 ,5, 6 ")) | ||
}) | ||
|
||
t.Run("handles decimal values", func(t *testing.T) { | ||
assert.Equal(t, []float64{0.1, 0.23, 0.45, 6.7}, main.ParseNumberSequenceShorthand(".1, .23, 0.45, 6.7")) | ||
}) | ||
|
||
t.Run("handles negative values", func(t *testing.T) { | ||
assert.Equal(t, []float64{-42, -0.1, -0.25, -3.33}, main.ParseNumberSequenceShorthand("-42, -.1, -0.25, -3.33")) | ||
}) | ||
|
||
t.Run("handles repetition shorthand, with 0 repetitions", func(t *testing.T) { | ||
assert.Equal(t, []float64{}, main.ParseNumberSequenceShorthand("42x0")) | ||
assert.Equal(t, []float64{1, 2, 3}, main.ParseNumberSequenceShorthand("1, 2, 42x0, 3")) | ||
}) | ||
|
||
t.Run("handles a group with a single value", func(t *testing.T) { | ||
assert.Equal(t, []float64{1, 1, 1}, main.ParseNumberSequenceShorthand("(1)x3")) | ||
}) | ||
|
||
t.Run("handles mixed values with a group", func(t *testing.T) { | ||
assert.Equal(t, []float64{0, 1, 1, 2, 3, 4, 4, 2, 3, 4, 4}, main.ParseNumberSequenceShorthand("0, 1x2, (2, 3, 4x2)x2")) | ||
}) | ||
}) | ||
|
||
t.Run("Validation", func(t *testing.T) { | ||
t.Run("failure cases", func(t *testing.T) { | ||
invalidInputs := []string{ | ||
"1 2", | ||
"(((1 2)x2)x2)x2", | ||
"1,,2", | ||
"(1, 2, 3)x, (4, 5)x2", | ||
"(1, 2, 3)2, (4, 5)x2", | ||
"(((1, 2x2)x2)x2", | ||
"((1, 2)x2)x2)x2", | ||
} | ||
|
||
for _, input := range invalidInputs { | ||
t.Run(input, func(t *testing.T) { | ||
assert.Panics(t, func() { main.ValidateNumberSequenceShorthand(input) }) | ||
}) | ||
} | ||
}) | ||
}) | ||
} |
Oops, something went wrong.