diff --git a/golox/cmd/golox/main.go b/golox/cmd/golox/main.go new file mode 100644 index 0000000..2b335a0 --- /dev/null +++ b/golox/cmd/golox/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "os" + + "github.com/AYM1607/crafting-interpreters/golox/internal/runner" +) + +func main() { + argc := len(os.Args) + if argc > 2 { + fmt.Println("Usage: golox [script]") + os.Exit(64) + } else if argc == 2 { + + } else { + runner.RunPrompt() + } +} diff --git a/golox/internal/runner/errors.go b/golox/internal/runner/errors.go new file mode 100644 index 0000000..307242a --- /dev/null +++ b/golox/internal/runner/errors.go @@ -0,0 +1,18 @@ +package runner + +import "fmt" + +func emitError(line int, message string) { + report(line, "", message) +} + +func report(line int, where, message string) { + fmt.Printf( + "[%d] Error%s: %s\n", + line, + where, + message, + ) + // TODO: The book sets `hadError` as true here, need to figure out where + // that's used. +} diff --git a/golox/internal/runner/runner.go b/golox/internal/runner/runner.go new file mode 100644 index 0000000..6ebc18f --- /dev/null +++ b/golox/internal/runner/runner.go @@ -0,0 +1,37 @@ +package runner + +import ( + "bufio" + "fmt" + "os" +) + +func RunPrompt() { + s := bufio.NewScanner(os.Stdin) + fmt.Print("> ") + for s.Scan() { + line := s.Text() + Run(line) + // TODO: resed hadError wherever it is set. + fmt.Print("> ") + } +} + +func RunFile(path string) error { + fBytes, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("could not read script file: %w", err) + } + Run(string(fBytes)) + // TODO: check hadError and exit with a 65 code if so. + return nil +} + +func Run(source string) { + s := NewScanner(source) + tokens := s.ScanTokens() + + for _, t := range tokens { + fmt.Println(t) + } +} diff --git a/golox/internal/runner/scanner.go b/golox/internal/runner/scanner.go new file mode 100644 index 0000000..4d114a7 --- /dev/null +++ b/golox/internal/runner/scanner.go @@ -0,0 +1,80 @@ +package runner + +type Scanner struct { + source string + + // State. + tokens []Token + start int + current int + line int +} + +func NewScanner(source string) *Scanner { + return &Scanner{ + source: source, + tokens: []Token{}, + + start: 0, + current: 0, + line: 1, + } +} + +func (s *Scanner) ScanTokens() []Token { + for !s.isAtEnd() { + s.start = s.current + s.scanToken() + } + + s.tokens = append(s.tokens, NewToken(EOF, "", nil, s.line)) + return s.tokens +} + +func (s *Scanner) scanToken() { + c := s.advance() + switch c { + case '(': + s.addToken(LPAREN) + case ')': + s.addToken(RPAREN) + case '{': + s.addToken(LBRACE) + case '}': + s.addToken(RBRACE) + case ',': + s.addToken(COMMA) + case '.': + s.addToken(DOT) + case '-': + s.addToken(MINUS) + case '+': + s.addToken(PLUS) + case ';': + s.addToken(SEMI) + case '*': + s.addToken(STAR) + } +} + +func (s *Scanner) advance() byte { + idx := s.current + s.current += 1 + return s.source[idx] +} + +func (s *Scanner) addToken(typ TokenType) { + s.addTokenWithLiteral(typ, nil) +} + +func (s *Scanner) addTokenWithLiteral(typ TokenType, literal interface{}) { + lexme := s.source[s.start:s.current] + s.tokens = append( + s.tokens, + NewToken(typ, lexme, literal, s.line), + ) +} + +func (s *Scanner) isAtEnd() bool { + return s.current >= len(s.source) +} diff --git a/golox/internal/runner/token.go b/golox/internal/runner/token.go new file mode 100644 index 0000000..eb37604 --- /dev/null +++ b/golox/internal/runner/token.go @@ -0,0 +1,33 @@ +package runner + +import "fmt" + +type Token struct { + Type TokenType + Lexme string + Literal interface{} + Line int +} + +func NewToken( + typ TokenType, + lexme string, + lit interface{}, + line int, +) Token { + return Token{ + Type: typ, + Lexme: lexme, + Literal: lit, + Line: line, + } +} + +func (t Token) String() string { + return fmt.Sprintf( + "%s %s %v", + t.Type, + t.Lexme, + t.Literal, + ) +} diff --git a/golox/internal/runner/token_type.go b/golox/internal/runner/token_type.go new file mode 100644 index 0000000..2f4950f --- /dev/null +++ b/golox/internal/runner/token_type.go @@ -0,0 +1,53 @@ +package runner + +type TokenType string + +const ( + // Single character tokens. + LPAREN TokenType = "LPAREN" + RPAREN TokenType = "RPAREN" + LBRACE TokenType = "LBRACE" + RBRACE TokenType = "RBRACE" + COMMA TokenType = "COMMA" + DOT TokenType = "DOT" + MINUS TokenType = "MINUS" + PLUS TokenType = "PLUS" + SEMI TokenType = "SEMI" + SLASH TokenType = "SLASH" + STAR TokenType = "STAR" + + // One or two character tokens. + BANG TokenType = "BANG" + BANG_EQUAL TokenType = "BANG_EQUAL" + EQUAL TokenType = "EQUAL" + EQUAL_EQUAL TokenType = "EQUAL_EQUAL" + GT TokenType = "GT" + GTE TokenType = "GTE" + LT TokenType = "LT" + LTE TokenType = "LTE" + + // Literals. + IDENT TokenType = "IDENT" + STRING TokenType = "STRING" + NUMBER TokenType = "NUMBER" + + // Keywords + AND TokenType = "AND" + CLASS TokenType = "CLASS" + ELSE TokenType = "ELSE" + FALSE TokenType = "FALSE" + FUN TokenType = "FUN" + FOR TokenType = "FOR" + IF TokenType = "IF" + NIL TokenType = "NIL" + OR TokenType = "OR" + PRINT TokenType = "PRINT" + RETURN TokenType = "RETURN" + SUPER TokenType = "SUPER" + THIS TokenType = "THIS" + TRUE TokenType = "TRUE" + VAR TokenType = "VAR" + WHILE TokenType = "WHILE" + + EOF TokenType = "EOF" +)