blob: a59a38e71527e00dc9b928595b13030363530f7f [file] [log] [blame]
package abigen
import (
"fmt"
"strings"
"unicode/utf8"
)
// Parser is a type that is able to parse a syscalls.abigen file.
type Parser struct {
buf []byte
off int
line, col int
name string
r rune
}
type token rune
const (
tokenEOF token = iota
tokenBad
tokenIdent
tokenNumber
)
// String implements fmt.Stringer on the token type.
func (t token) String() string {
switch t {
case tokenEOF:
return "token(EOF)"
case tokenBad:
return "token(BAD)"
case tokenIdent:
return "token(Ident)"
case tokenNumber:
return "token(Number)"
default:
return "token(" + string(t) + ")"
}
}
func NewParser(buf []byte, fn string) *Parser {
return &Parser{
buf: buf,
line: 1,
name: fn,
}
}
func (p *Parser) Parse() (defs []SysDef, err error) {
if len(p.buf) == 0 {
return nil, nil
}
p.nextRune()
for {
t, lit := p.nextToken()
if t == tokenEOF {
return defs, nil
} else if t == tokenBad {
return nil, p.errorf("bad token: %s", lit)
}
if lit == "syscall" {
def, err := p.parseSyscall()
if err != nil {
return nil, err
}
// The PCI syscalls are DDK-only and temporary. The hypervisor syscalls
// have complex parameter and return types. Exclude all of these.
// helpers for them.
if strings.Contains(def.Name, "_test") || strings.HasPrefix(def.Name, "pci_") || strings.HasPrefix(def.Name, "guest_") || strings.HasPrefix(def.Name, "vcpu_") {
continue
}
defs = append(defs, def)
} else {
return nil, p.errorf("unexpected token: %q (literal %q)", t, lit)
}
}
return defs, nil
}
func (p *Parser) expect(want token) {
t, lit := p.nextToken()
if t != want {
p.panicf("expected %q but got %q (literal %q)", want, t, lit)
}
}
func (p *Parser) parseAttributes() (attr []string, err error) {
for {
p.skipWhitespace()
if p.r == '(' {
return
}
attr = append(attr, p.nextIdent())
p.skipWhitespace()
if p.r == ',' {
p.nextRune()
}
}
}
func (p *Parser) parseArg() (arg SysArg, err error) {
if p.r == ')' {
return
}
arg.Name = p.nextIdent()
p.expect(':')
arg.Type, err = p.parseType()
if err != nil {
return SysArg{}, err
}
p.skipWhitespace()
if isLetter(p.r) {
arg.Constraint = p.nextIdent()
p.skipWhitespace()
}
return
}
func (p *Parser) parseSyscall() (d SysDef, err error) {
d.Name = p.nextIdent()
d.Attrs, err = p.parseAttributes()
if err != nil {
return SysDef{}, err
}
for _, a := range d.Attrs {
if a == "vdsocall" {
d.VDSOCall = true
}
}
p.expect('(')
for {
p.skipWhitespace()
if p.r == ')' {
p.nextRune()
break
}
arg, err := p.parseArg()
if err != nil {
return SysDef{}, err
}
if arg.Name == "" {
break
}
d.Args = append(d.Args, arg)
if p.r == ',' {
p.nextRune()
}
}
p.skipWhitespace()
if p.r != ';' {
kw := p.nextIdent()
if kw != "returns" {
p.panicf("expected \"returns\" but got \"%s\"", kw)
}
p.expect('(')
first := true
for {
p.skipWhitespace()
if p.r == ')' {
p.nextRune()
break
}
if first {
first = false
rtype, err := p.parseType()
if err != nil {
return SysDef{}, err
}
d.Ret = rtype
} else {
arg, err := p.parseArg()
if err != nil {
return SysDef{}, err
}
// Extra return types are returned as pointers.
arg.Type.Array = true
arg.Type.ArraySize = "1"
d.Args = append(d.Args, arg)
}
if p.r == ',' {
p.nextRune()
}
}
}
p.skipWhitespace()
p.expect(';')
return d, nil
}
func (p *Parser) parseType() (t SysType, err error) {
ident := p.nextIdent()
n, ok := sysTypeNames[ident]
if !ok {
return t, p.errorf("unknown type name: %s", ident)
}
t.Name = n
p.skipWhitespace()
if p.r == '[' {
p.nextRune()
t.Array = true
t.ArraySize = p.nextIdent()
p.skipWhitespace()
for p.r == '*' {
p.nextRune()
t.ArraySize += " * " + p.nextIdent()
}
p.expect(']')
}
return t, nil
}
func (p *Parser) nextToken() (t token, lit string) {
p.skipWhitespace()
if isLetter(p.r) {
lit = p.nextIdent()
return tokenIdent, lit
}
if isNumber(p.r) {
lit = p.nextNumber()
return tokenNumber, lit
}
r := p.r
p.nextRune()
switch r {
case -1:
return tokenEOF, ""
case '(', ')', '*', ',', ';', ':', '[', ']':
return token(r), ""
case '#':
p.skipComment()
return p.nextToken()
}
return tokenBad, fmt.Sprintf("%q", r)
}
func (p *Parser) nextIdent() string {
p.skipWhitespace()
if !isLetter(p.r) && !isNumber(p.r) {
p.panicf("expected ident but got %q", p.r)
}
off := p.off - 1
for isLetter(p.r) || isNumber(p.r) {
p.nextRune()
}
r := string(p.buf[off : p.off-1])
if r == "type" {
// "type" is a reserved word in Go.
return "typ"
}
return r
}
func (p *Parser) nextNumber() string {
p.skipWhitespace()
if !isNumber(p.r) {
p.panicf("expected number but got %q", p.r)
}
off := p.off - 1
for isNumber(p.r) {
p.nextRune()
}
return string(p.buf[off : p.off-1])
}
func (p *Parser) skipWhitespace() {
for p.r == ' ' || p.r == '\t' || p.r == '\n' {
p.nextRune()
}
}
func (p *Parser) skipComment() {
// skip until end of line
p.nextRune()
for p.r != '\n' && p.r >= 0 {
p.nextRune()
}
}
func (p *Parser) nextRune() {
if p.off >= len(p.buf) {
p.r = -1
return
}
if p.r == '\n' {
p.line++
p.col = 0
} else {
p.col += utf8.RuneLen(p.r)
}
r, w := utf8.DecodeRune(p.buf[p.off:])
if r == utf8.RuneError && w == 1 {
p.r = -1
p.panicf("input is not UTF-8")
return
}
p.r = r
p.off += w
}
func (p *Parser) panicf(format string, args ...interface{}) {
panic(parsePanic{err: p.errorf(format, args...)})
}
func (p *Parser) errorf(format string, args ...interface{}) error {
return fmt.Errorf("%s:%d:%d: %s", p.name, p.line, p.col, fmt.Sprintf(format, args...))
}
func isLetter(r rune) bool { return 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' || r == '_' }
func isNumber(r rune) bool { return '0' <= r && r <= '9' }
type parsePanic struct {
err error
}
func (pp parsePanic) String() string { return pp.err.Error() }