commit 43aac965590de7a4d16e25c5213661cbe11bcdcf Author: xkm Date: Wed Dec 3 09:57:17 2025 +0800 feat: scanner diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..93596e5 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module sfdl + +go 1.25.4 diff --git a/main.go b/main.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/main.go @@ -0,0 +1 @@ +package main diff --git a/parser/ast.go b/parser/ast.go new file mode 100644 index 0000000..0bfe2c2 --- /dev/null +++ b/parser/ast.go @@ -0,0 +1 @@ +package parser diff --git a/parser/parser.go b/parser/parser.go new file mode 100644 index 0000000..0bfe2c2 --- /dev/null +++ b/parser/parser.go @@ -0,0 +1 @@ +package parser diff --git a/scanner/scanner.go b/scanner/scanner.go new file mode 100644 index 0000000..282047b --- /dev/null +++ b/scanner/scanner.go @@ -0,0 +1,202 @@ +package scanner + +import ( + "os" + "unicode" +) + +const EOF rune = 0 + +type Scanner struct { + src []rune + pos int +} + +func NewScannerFromFile(filename string) (*Scanner, error) { + data, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + src := []rune(string(data)) + for idx := range src { + src[idx] = unicode.ToUpper(src[idx]) + } + return &Scanner{ + src: src, + pos: 0, + }, nil +} + +func NewScannerFromString(data string) *Scanner { + src := []rune(data) + for idx := range src { + src[idx] = unicode.ToUpper(src[idx]) + } + return &Scanner{ + src: src, + pos: 0, + } +} + +// get now char, no pos++ +func (s *Scanner) peek() rune { + if s.pos >= len(s.src) { + return EOF + } + return s.src[s.pos] +} + +// get next char, no pos++ +func (s *Scanner) peekNext() rune { + if s.pos+1 >= len(s.src) { + return EOF + } + return s.src[s.pos+1] +} + +// get now char, pos++ +func (s *Scanner) next() rune { + if s.pos >= len(s.src) { + return EOF + } + ch := s.src[s.pos] + s.pos++ + return ch +} + +func (s *Scanner) skipSpacesAndComments() { + for { + c := s.peek() + n := s.peekNext() + if c == EOF { + return + } + if (c == '/' && n == '/') || (c == '-' && n == '-') { + for { + ch := s.next() + if ch == '\n' || ch == '\r' || ch == EOF { + break + } + } + continue + } + if !unicode.IsSpace(c) { + break + } + s.next() + } +} + +func (s *Scanner) GetToken() Token { + s.skipSpacesAndComments() + ch := s.peek() + if ch == EOF { + return Token{Type: NONTOKEN} + } + + // is number? + if unicode.IsDigit(ch) { + return s.scanNumber() + } + + // is id / keyword? + if unicode.IsLetter(ch) { + return s.scanIdentOrKeyword() + } + + // is operator? + switch ch { + case '+': + s.next() + return Token{Type: PLUS, Lexeme: "+"} + case '-': + s.next() + return Token{Type: MINUS, Lexeme: "-"} + case '*': + s.next() + if s.peek() == '*' { + s.next() + return Token{Type: POWER, Lexeme: "**"} + } + return Token{Type: MUL, Lexeme: "*"} + case '/': + s.next() + return Token{Type: DIV, Lexeme: "/"} + case '(': + s.next() + return Token{Type: L_BRACKET, Lexeme: "("} + case ')': + s.next() + return Token{Type: R_BRACKET, Lexeme: ")"} + case ',': + s.next() + return Token{Type: COMMA, Lexeme: ","} + case ';': + s.next() + return Token{Type: SEMICO, Lexeme: ";"} + default: + s.next() + return Token{Type: ERRTOKEN, Lexeme: string(ch)} + } +} + +// digit+("."digit*)? +func (s *Scanner) scanNumber() Token { + haveDot := false + mask := 0.1 + token := Token{Type: CONST_ID, Lexeme: "", Value: 0.0} + for { + ch := s.peek() + if unicode.IsDigit(ch) { + s.next() + if haveDot { + token.Value += float64(ch-'0') * mask + mask *= 0.1 + } else { + token.Value *= 10 + token.Value += float64(ch - '0') + } + token.Lexeme += string(ch) + continue + } + + if ch == '.' { + s.next() + if haveDot { + return Token{Type: ERRTOKEN, Lexeme: token.Lexeme + string(ch)} + } + haveDot = true + token.Lexeme += string(ch) + continue + } + + if unicode.IsLetter(ch) { + s.next() + return Token{Type: ERRTOKEN, Lexeme: token.Lexeme + string(ch)} + } + return token + } +} + +// letter(letter|digit)* +func (s *Scanner) scanIdentOrKeyword() Token { + var lexeme string + for { + ch := s.peek() + if ch == EOF || unicode.IsSpace(ch) { + break + } + if unicode.IsDigit(ch) || unicode.IsLetter(ch) { + lexeme += string(ch) + s.next() + continue + } + break + } + token, ok := keywords[lexeme] + if ok { + return token + } else { + return Token{Type: ERRTOKEN, Lexeme: lexeme} + } +} diff --git a/scanner/token.go b/scanner/token.go new file mode 100644 index 0000000..1d2f2bd --- /dev/null +++ b/scanner/token.go @@ -0,0 +1,66 @@ +package scanner + +import "math" + +type TokenType int + +const ( + // reserved word + ORIGIN TokenType = iota + ROT + SCALE + IS + FOR + FROM + TO + STEP + DRAW + COLOR + // parameter + T + // sparator + SEMICO + L_BRACKET + R_BRACKET + COMMA + // operator + PLUS + MINUS + MUL + DIV + POWER + // other + FUNC + CONST_ID + NONTOKEN + ERRTOKEN +) + +type Token struct { + Type TokenType + Lexeme string + Value float64 + Func func(float64) float64 +} + +var keywords = map[string]Token{ + "ORIGIN": {Type: ORIGIN, Lexeme: "ORIGIN"}, + "ROT": {Type: ROT, Lexeme: "ROT"}, + "SCALE": {Type: SCALE, Lexeme: "SCALE"}, + "IS": {Type: IS, Lexeme: "IS"}, + "FOR": {Type: FOR, Lexeme: "FOR"}, + "FROM": {Type: FROM, Lexeme: "FROM"}, + "TO": {Type: TO, Lexeme: "TO"}, + "STEP": {Type: STEP, Lexeme: "STEP"}, + "DRAW": {Type: DRAW, Lexeme: "DRAW"}, + "COLOR": {Type: COLOR, Lexeme: "COLOR"}, + "T": {Type: T, Lexeme: "T"}, + "PI": {Type: CONST_ID, Lexeme: "PI", Value: math.Pi}, + "E": {Type: CONST_ID, Lexeme: "E", Value: math.E}, + "SIN": {Type: FUNC, Lexeme: "SIN", Func: math.Sin}, + "COS": {Type: FUNC, Lexeme: "COS", Func: math.Cos}, + "TAN": {Type: FUNC, Lexeme: "TAN", Func: math.Tan}, + "LN": {Type: FUNC, Lexeme: "LN", Func: math.Log}, + "EXP": {Type: FUNC, Lexeme: "EXP", Func: math.Exp}, + "SQRT": {Type: FUNC, Lexeme: "SQRT", Func: math.Sqrt}, +} diff --git a/semantices/draw.go b/semantices/draw.go new file mode 100644 index 0000000..9123ae2 --- /dev/null +++ b/semantices/draw.go @@ -0,0 +1 @@ +package semantices diff --git a/semantices/semantices.go b/semantices/semantices.go new file mode 100644 index 0000000..9123ae2 --- /dev/null +++ b/semantices/semantices.go @@ -0,0 +1 @@ +package semantices