283 lines
6.6 KiB
Go
283 lines
6.6 KiB
Go
|
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
|
||
|
}
|
||
|
}
|