nasin/internal/style/tss/parse.go

283 lines
6.6 KiB
Go
Raw Normal View History

2024-07-28 23:50:51 -06:00
package tss
import "io"
import "strconv"
import "git.tebibyte.media/sashakoshka/goparse"
type Sheet struct {
Variables map[string] ValueList
Rules []Rule
}
type Rule struct {
Selector Selector
Attrs map[string] []ValueList
}
type Selector struct {
Package string
Object string
Tags []string
}
type ValueList []Value
type Value interface {
value ()
}
func (ValueNumber) value () { }
type ValueNumber int
func (ValueColor) value () { }
type ValueColor uint32
func (ValueString) value () { }
type ValueString string
func (ValueKeyword) value () { }
type ValueKeyword string
func (ValueVariable) value () { }
type ValueVariable string
type parser struct {
parse.Parser
sheet Sheet
lexer parse.Lexer
}
func newParser (lexer parse.Lexer) *parser {
return &parser {
sheet: Sheet {
Variables: make(map[string] ValueList),
},
Parser: parse.Parser {
Lexer: lexer,
TokenNames: tokenNames,
},
}
}
func Parse (lexer parse.Lexer) (Sheet, error) {
parser := newParser(lexer)
err := parser.parse()
if err == io.EOF { err = nil }
if err != nil { return Sheet { }, err }
return parser.sheet, nil
}
func (this *parser) parse () error {
err := this.Next()
if err != nil { return err }
for this.Token.Kind != parse.EOF {
err = this.parseTopLevel()
if err != nil { return err }
}
return nil
}
func (this *parser) parseTopLevel () error {
err := this.ExpectDesc("variable or rule", Dollar, Ident, Star)
if err != nil { return err }
if this.EOF() { return nil }
pos := this.Pos()
switch this.Kind() {
case Dollar:
name, variable, err := this.parseVariable()
if err != nil { return err }
if _, exists := this.sheet.Variables[name]; exists {
return parse.Errorf(pos, "variable %s already declared", name)
}
this.sheet.Variables[name] = variable
case Ident, Star:
rule, err := this.parseRule()
if err != nil { return err }
this.sheet.Rules = append(this.sheet.Rules, rule)
}
return nil
}
func (this *parser) parseVariable () (string, ValueList, error) {
err := this.Expect(Dollar)
if err != nil { return "", nil, err }
err = this.ExpectNext(Ident)
if err != nil { return "", nil, err }
err = this.ExpectNext(Equals)
if err != nil { return "", nil, err }
name := this.Value()
this.Next()
values, err := this.parseValueList()
if err != nil { return "", nil, err }
err = this.Expect(Semicolon)
if err != nil { return "", nil, err }
return name, values, this.Next()
}
func (this *parser) parseRule () (Rule, error) {
rule := Rule {
Attrs: make(map[string] []ValueList),
}
selector, err := this.parseSelector()
if err != nil { return Rule { }, err }
rule.Selector = selector
err = this.Expect(LBracket)
if err != nil { return Rule { }, err }
for {
pos := this.Pos()
name, attr, err := this.parseAttr()
if err != nil { break }
_, exists := rule.Attrs[name]
if !exists {
return Rule { }, parse.Errorf (
pos,
"attribute %s already declared in this rule",
name)
}
rule.Attrs[name] = attr
}
err = this.Expect(LBracket)
if err != nil { return Rule { }, err }
return rule, this.Next()
}
func (this *parser) parseSelector () (Selector, error) {
selector := Selector { }
// package
err := this.ExpectDesc("selector", Ident, Star)
if err != nil { return Selector { }, err }
if this.Is(Ident) {
selector.Package = this.Value()
}
err = this.ExpectNext(Dot)
if err != nil { return Selector { }, err }
// object
err = this.ExpectNext(Ident, Star)
if err != nil { return Selector { }, err }
if this.Is(Ident) {
selector.Object = this.Value()
}
// tags
err = this.ExpectNext(LBrace)
if err == nil {
this.Next()
for {
err := this.Expect(Ident, String, RBrace)
if err != nil { return Selector { }, err }
if this.Is(RBrace) { break }
selector.Tags = append(selector.Tags, this.Value())
err = this.ExpectNext(Comma, RBrace)
if err != nil { return Selector { }, err }
}
}
return selector, this.Next()
}
func (this *parser) parseAttr () (string, []ValueList, error) {
err := this.ExpectDesc("attr", Ident)
if err != nil { return "", nil, err }
name := this.Value()
err = this.ExpectNext(Colon)
if err != nil { return "", nil, err }
attr := []ValueList { }
this.Next()
for {
err := this.ExpectDesc (
"value, comma, or semicolon",
Number, Color, String, Ident, Dollar, Comma, Semicolon)
if err != nil { return "", nil, err }
if this.Is(Semicolon) { break }
valueList, err := this.parseValueList()
if err != nil { return "", nil, err }
attr = append(attr, valueList)
err = this.ExpectNext(Comma, Semicolon)
if err != nil { return "", nil, err }
}
return name, attr, this.Next()
}
func (this *parser) parseValueList () (ValueList, error) {
list := ValueList { }
for {
err := this.ExpectDesc (
"value",
Number, Color, String, Ident, Dollar)
if err != nil { break }
switch this.Kind() {
case Number:
number, err := strconv.Atoi(this.Value())
if err != nil { return nil, err }
list = append(list, ValueNumber(number))
case Color:
color, ok := parseColor([]rune(this.Value()))
if !ok {
return nil, parse.Errorf (
this.Pos(),
"malformed color literal")
}
list = append(list, ValueColor(color))
case String:
list = append(list, ValueString(this.Value()))
case Ident:
list = append(list, ValueKeyword(this.Value()))
case Dollar:
err := this.ExpectNext(Ident)
if err != nil { return nil, err }
list = append(list, ValueVariable(this.Value()))
}
}
return list, this.Next()
}
func parseColor (runes []rune) (uint32, bool) {
digits := make([]uint32, len(runes))
for index, run := range runes {
digit := hexDigit(run)
if digit < 0 { return 0, false }
digits[index] = uint32(digit)
}
switch len(runes) {
case 3:
return digits[0] << 28 | digits[0] << 24 |
digits[1] << 20 | digits[1] << 16 |
digits[2] << 12 | digits[2] << 8 | 0xFF, true
case 6:
return digits[0] << 28 | digits[1] << 24 |
digits[2] << 20 | digits[3] << 16 |
digits[4] << 12 | digits[5] << 8 | 0xFF, true
case 4:
return digits[0] << 28 | digits[0] << 24 |
digits[1] << 20 | digits[1] << 16 |
digits[2] << 12 | digits[2] << 8 |
digits[3] << 4 | digits[3] << 0, true
case 8:
return digits[0] << 28 | digits[1] << 24 |
digits[2] << 20 | digits[3] << 16 |
digits[4] << 12 | digits[5] << 8 |
digits[6] << 4 | digits[7] << 0, true
default: return 0, false
}
}
func hexDigit (digit rune) int {
switch {
case digit >= '0' && digit <= '9': return int(digit - '0')
case digit >= 'a' && digit <= 'f': return int(digit - 'a') + 10
case digit >= 'A' && digit <= 'F': return int(digit - 'A') + 10
default: return -1
}
}