feat: scanner
This commit is contained in:
1
parser/ast.go
Normal file
1
parser/ast.go
Normal file
@@ -0,0 +1 @@
|
||||
package parser
|
||||
1
parser/parser.go
Normal file
1
parser/parser.go
Normal file
@@ -0,0 +1 @@
|
||||
package parser
|
||||
202
scanner/scanner.go
Normal file
202
scanner/scanner.go
Normal file
@@ -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}
|
||||
}
|
||||
}
|
||||
66
scanner/token.go
Normal file
66
scanner/token.go
Normal file
@@ -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},
|
||||
}
|
||||
1
semantices/draw.go
Normal file
1
semantices/draw.go
Normal file
@@ -0,0 +1 @@
|
||||
package semantices
|
||||
1
semantices/semantices.go
Normal file
1
semantices/semantices.go
Normal file
@@ -0,0 +1 @@
|
||||
package semantices
|
||||
Reference in New Issue
Block a user