diff --git a/main.go b/main.go index 353e791..f07bfd6 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,8 @@ import ( "strings" ) +var hasError = false + func main() { if len(os.Args) > 2 { fmt.Println("Usage: lox [script]") @@ -25,13 +27,15 @@ func parseFile(path string) { os.Exit(1) } - scanner := NewScanner() - scanner.Parse(string(file)) + run(string(file)) + + if hasError { + os.Exit(1) + } } func parsePrompt() { reader := bufio.NewReader(os.Stdin) - scanner := NewScanner() for { fmt.Print("> ") @@ -42,6 +46,27 @@ func parsePrompt() { return } - scanner.Parse(text) + run(text) + + hasError = false } } + +func run(source string) { + scanner := NewScanner(source) + tokens := scanner.ScanTokens() + + for _, token := range tokens { + fmt.Println(token.String()) + } +} + +// fuck is just the error function, but collides with a builtin so whatever +func fuck(line int, msg string) { + report(line, "", msg) +} + +func report(line int, where string, msg string) { + fmt.Printf("[line %d] Error %s: %s\n", line, where, msg) + hasError = true +} diff --git a/scanner.go b/scanner.go index 9180bf5..e4bda65 100644 --- a/scanner.go +++ b/scanner.go @@ -1,21 +1,157 @@ package main -import ( - "fmt" -) - type Scanner struct { - tokens []Token + source string + tokens []Token + start int + current int + line int } -func (s *Scanner) GetTokens() []Token { +func (s *Scanner) ScanTokens() []Token { + for !s.isAtEnd() { + s.start = s.current + s.scanToken() + } + + s.tokens = append(s.tokens, Token{ + Type: EOF, + Lexeme: "", + Literal: "", + Line: s.line, + }) return s.tokens } -func (s *Scanner) Parse(source string) { - fmt.Println(source) +func (s *Scanner) scanToken() { + c := s.advance() + switch c { + case "(": + s.addToken(LeftBrace) + break + case ")": + s.addToken(RightBrace) + break + case "{": + s.addToken(LeftBrace) + break + case "}": + s.addToken(RightBrace) + break + case ",": + s.addToken(Comma) + break + case ".": + s.addToken(Dot) + break + case "-": + s.addToken(Minus) + break + case "+": + s.addToken(Plus) + break + case ";": + s.addToken(Semicolon) + break + case "*": + s.addToken(Star) + break + case "!": + if s.match("=") { + s.addToken(BangEqual) + } else { + s.addToken(Bang) + } + break + case "=": + if s.match("=") { + s.addToken(EqualEqual) + } else { + s.addToken(Equal) + } + break + case "<": + if s.match("=") { + s.addToken(LessEqual) + } else { + s.addToken(Less) + } + break + case ">": + if s.match("=") { + s.addToken(GreaterEqual) + } else { + s.addToken(Greater) + } + break + case "/": + if s.match("/") { + for s.peek() != "\n" && !s.isAtEnd() { + s.advance() + } + } else { + s.addToken(Slash) + } + break + case " ": + case "\t": + case "\r": + // ignore whitespace + break + case "\n": + s.line += 1 + break + default: + fuck(s.line, "Unexpected Char.") + break + } } -func NewScanner() *Scanner { - return &Scanner{} +func (s *Scanner) advance() string { + c := string(s.source[s.current]) + s.current += 1 + return c +} + +func (s *Scanner) peek() string { + if s.isAtEnd() { + return "" + } + return string(s.source[s.current]) +} + +func (s *Scanner) match(expected string) bool { + if s.isAtEnd() { + return false + } + if string(s.source[s.current]) != expected { + return false + } + s.current += 1 + return true +} + +func (s *Scanner) addToken(token int) { + s.tokens = append(s.tokens, Token{ + Type: token, + Lexeme: s.source[s.start:s.current], + Literal: "", + Line: s.line, + }) + //fmt.Printf("[%d %d %d] ", s.line, s.current, s.start) + //fmt.Println(s.tokens[len(s.tokens)-1]) +} + +func (s *Scanner) isAtEnd() bool { + return s.current >= len(s.source) +} + +func NewScanner(source string) *Scanner { + return &Scanner{ + source: source, + tokens: []Token{}, + start: 0, + current: 0, + line: 0, + } } diff --git a/scripts/example.lox b/scripts/example.lox index b2527ad..fee65cb 100644 --- a/scripts/example.lox +++ b/scripts/example.lox @@ -1 +1,3 @@ -print "Hello, World!"; +// this is a comment +(( )){} // grouping stuff +!*+-/ =<> <= == // operators diff --git a/scripts/helloworld.lox b/scripts/helloworld.lox new file mode 100644 index 0000000..b2527ad --- /dev/null +++ b/scripts/helloworld.lox @@ -0,0 +1 @@ +print "Hello, World!"; diff --git a/tokens.go b/tokens.go index ba02770..a22268b 100644 --- a/tokens.go +++ b/tokens.go @@ -1,5 +1,66 @@ package main +import ( + "fmt" +) + +const ( + // Single Char tokens + LeftParen = iota + RightParen + LeftBrace + RightBrace + Comma + Dot + Minus + Plus + Semicolon + Slash + Star + + // One or Two Char tokens + Bang + BangEqual + Equal + EqualEqual + Greater + GreaterEqual + Less + LessEqual + + // Literals + Identifier + String + Number + + // Keywords + And + Class + Else + False + Fun + For + If + Nah + Or + Print + Return + Super + This + True + Var + While + + EOF +) + type Token struct { - token string + Type int + Lexeme string + Literal string + Line int +} + +func (t Token) String() string { + return fmt.Sprintf("%d %s %s", t.Type, t.Lexeme, t.Literal) }