Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
576 lines (508 sloc) 17.6 KB
/*
* Copyright 2017 Workiva
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
{
package parser
import (
"fmt"
"regexp"
"strconv"
"strings"
)
var (
identifier = regexp.MustCompile("^[A-Za-z]+[A-Za-z0-9]")
prefixVariable = regexp.MustCompile("{\\w*}")
defaultPrefix = &ScopePrefix{String: "", Variables: make([]string, 0)}
)
type statementWrapper struct {
comment []string
statement interface{}
}
type exception *Struct
type union *Struct
func newScopePrefix(prefix string) (*ScopePrefix, error) {
variables := []string{}
for _, variable := range prefixVariable.FindAllString(prefix, -1) {
variable = variable[1 : len(variable)-1]
if len(variable) == 0 || !identifier.MatchString(variable) {
return nil, fmt.Errorf("parser: invalid prefix variable '%s'", variable)
}
variables = append(variables, variable)
}
return &ScopePrefix{String: prefix, Variables: variables}, nil
}
func toIfaceSlice(v interface{}) []interface{} {
if v == nil {
return nil
}
return v.([]interface{})
}
func ifaceSliceToString(v interface{}) string {
ifs := toIfaceSlice(v)
b := make([]byte, len(ifs))
for i, v := range ifs {
b[i] = v.([]uint8)[0]
}
return string(b)
}
func rawCommentToDocStr(raw string) []string {
rawLines := strings.Split(raw, "\n")
comment := make([]string, len(rawLines))
for i, line := range rawLines {
comment[i] = strings.TrimLeft(line, "* ")
}
return comment
}
// toStruct converts a union to a struct with all fields optional.
func unionToStruct(u union) *Struct {
st := (*Struct)(u)
for _, f := range st.Fields {
f.Modifier = Optional
}
return st
}
// toAnnotations converts an interface{} to an Annotation slice.
func toAnnotations(v interface{}) Annotations {
if v == nil {
return nil
}
return Annotations(v.([]*Annotation))
}
}
///////////////////////////////////////////////////////////////////////////////
// TOP-LEVEL //
///////////////////////////////////////////////////////////////////////////////
Grammar <- __ statements:( Statement __ )* (EOF / SyntaxError) {
stmts := toIfaceSlice(statements)
frugal := &Frugal{
Scopes: []*Scope{},
ParsedIncludes: make(map[string]*Frugal),
Includes: []*Include{},
Namespaces: []*Namespace{},
Typedefs: []*TypeDef{},
Constants: []*Constant{},
Enums: []*Enum{},
Structs: []*Struct{},
Exceptions: []*Struct{},
Unions: []*Struct{},
Services: []*Service{},
typedefIndex: make(map[string]*TypeDef),
namespaceIndex: make(map[string]*Namespace),
}
for _, st := range stmts {
wrapper := st.([]interface{})[0].(*statementWrapper)
switch v := wrapper.statement.(type) {
case *Namespace:
frugal.Namespaces = append(frugal.Namespaces, v)
frugal.namespaceIndex[v.Scope] = v
case *Constant:
v.Comment = wrapper.comment
frugal.Constants = append(frugal.Constants, v)
case *Enum:
v.Comment = wrapper.comment
frugal.Enums = append(frugal.Enums, v)
case *TypeDef:
v.Comment = wrapper.comment
frugal.Typedefs = append(frugal.Typedefs, v)
frugal.typedefIndex[v.Name] = v
case *Struct:
v.Type = StructTypeStruct
v.Comment = wrapper.comment
frugal.Structs = append(frugal.Structs, v)
case exception:
strct := (*Struct)(v)
strct.Type = StructTypeException
strct.Comment = wrapper.comment
frugal.Exceptions = append(frugal.Exceptions, strct)
case union:
strct := unionToStruct(v)
strct.Type = StructTypeUnion
strct.Comment = wrapper.comment
frugal.Unions = append(frugal.Unions, strct)
case *Service:
v.Comment = wrapper.comment
v.Frugal = frugal
frugal.Services = append(frugal.Services, v)
case *Include:
frugal.Includes = append(frugal.Includes, v)
case *Scope:
v.Comment = wrapper.comment
v.Frugal = frugal
frugal.Scopes = append(frugal.Scopes, v)
default:
return nil, fmt.Errorf("parser: unknown value %#v", v)
}
}
return frugal, nil
}
SyntaxError <- . {
return nil, errors.New("parser: syntax error")
}
Statement <- docstr:(DocString __)? statement:FrugalStatement {
wrapper := &statementWrapper{statement: statement}
if docstr != nil {
raw := docstr.([]interface{})[0].(string)
wrapper.comment = rawCommentToDocStr(raw)
}
return wrapper, nil
}
///////////////////////////////////////////////////////////////////////////////
// THRIFT //
///////////////////////////////////////////////////////////////////////////////
FrugalStatement <- Include / Namespace / Const / Enum / TypeDef / Struct / Exception / Union / Service / Scope
Include <- "include" _ file:Literal _ annotations:TypeAnnotations? EOS {
name := filepath.Base(file.(string))
if ix := strings.LastIndex(name, "."); ix > 0 {
name = name[:ix]
}
return &Include{
Name: name,
Value: file.(string),
Annotations: toAnnotations(annotations),
}, nil
}
Namespace <- "namespace" _ scope:[*a-z.-]+ _ ns:Identifier _ annotations:TypeAnnotations? EOS {
return &Namespace{
Scope: ifaceSliceToString(scope),
Value: string(ns.(Identifier)),
Annotations: toAnnotations(annotations),
}, nil
}
Const <- "const" _ typ:FieldType _ name:Identifier _ "=" _ value:ConstValue _ annotations:TypeAnnotations? EOS {
return &Constant{
Name: string(name.(Identifier)),
Type: typ.(*Type),
Value: value,
Annotations: toAnnotations(annotations),
}, nil
}
Enum <- "enum" _ name:Identifier __ '{' __ values:(EnumValue __)* '}' _ annotations:TypeAnnotations? EOS {
vs := toIfaceSlice(values)
en := &Enum{
Name: string(name.(Identifier)),
Values: make([]*EnumValue, len(vs)),
Annotations: toAnnotations(annotations),
}
// Assigns numbers in order. This will behave badly if some values are
// defined and other are not, but I think that's ok since that's a silly
// thing to do.
next := 0
for idx, v := range vs {
ev := v.([]interface{})[0].(*EnumValue)
if ev.Value < 0 {
ev.Value = next
}
if ev.Value >= next {
next = ev.Value + 1
}
en.Values[idx] = ev
}
return en, nil
}
EnumValue <- docstr:(DocString __)? name:Identifier _ value:('=' _ IntConstant)? _ annotations:TypeAnnotations? ListSeparator? {
ev := &EnumValue{
Name: string(name.(Identifier)),
Value: -1,
Annotations: toAnnotations(annotations),
}
if docstr != nil {
raw := docstr.([]interface{})[0].(string)
ev.Comment = rawCommentToDocStr(raw)
}
if value != nil {
ev.Value = int(value.([]interface{})[2].(int64))
}
return ev, nil
}
TypeDef <- "typedef" _ typ:FieldType _ name:Identifier _ annotations:TypeAnnotations? EOS {
return &TypeDef{
Name: string(name.(Identifier)),
Type: typ.(*Type),
Annotations: toAnnotations(annotations),
}, nil
}
Struct <- "struct" _ st:StructLike { return st.(*Struct), nil }
Exception <- "exception" _ st:StructLike { return exception(st.(*Struct)), nil }
Union <- "union" _ st:StructLike { return union(st.(*Struct)), nil }
StructLike <- name:Identifier __ '{' __ fields:FieldList '}' _ annotations:TypeAnnotations? EOS {
st := &Struct{
Name: string(name.(Identifier)),
Annotations: toAnnotations(annotations),
}
if fields != nil {
st.Fields = fields.([]*Field)
}
return st, nil
}
FieldList <- fields:(Field __)* {
fs := fields.([]interface{})
flds := make([]*Field, len(fs))
for i, f := range fs {
flds[i] = f.([]interface{})[0].(*Field)
}
return flds, nil
}
Field <- docstr:(DocString __)? id:IntConstant _ ':' _ mod:FieldModifier? _ typ:FieldType _ name:Identifier __ def:('=' _ ConstValue)? _ annotations:TypeAnnotations? ListSeparator? {
f := &Field{
ID: int(id.(int64)),
Name: string(name.(Identifier)),
Type: typ.(*Type),
Annotations: toAnnotations(annotations),
}
if docstr != nil {
raw := docstr.([]interface{})[0].(string)
f.Comment = rawCommentToDocStr(raw)
}
if mod != nil {
f.Modifier = mod.(FieldModifier)
} else {
f.Modifier = Default
}
if def != nil {
f.Default = def.([]interface{})[2]
}
return f, nil
}
FieldModifier <- ("required" / "optional") {
if bytes.Equal(c.text, []byte("required")) {
return Required, nil
} else {
return Optional, nil
}
}
Service <- "service" _ name:Identifier _ extends:("extends" __ Identifier __)? __ '{' __ methods:(Function __)* ('}' / EndOfServiceError) _ annotations:TypeAnnotations? EOS {
ms := methods.([]interface{})
svc := &Service{
Name: string(name.(Identifier)),
Methods: make([]*Method, len(ms)),
Annotations: toAnnotations(annotations),
}
if extends != nil {
svc.Extends = string(extends.([]interface{})[2].(Identifier))
}
for i, m := range ms {
mt := m.([]interface{})[0].(*Method)
svc.Methods[i] = mt
}
return svc, nil
}
EndOfServiceError <- . {
return nil, errors.New("parser: expected end of service")
}
Function <- docstr:(DocString __)? oneway:("oneway" __)? typ:FunctionType __ name:Identifier _ '(' __ arguments:FieldList ')' __ exceptions:Throws? _ annotations:TypeAnnotations? ListSeparator? {
m := &Method{
Name: string(name.(Identifier)),
Annotations: toAnnotations(annotations),
}
if docstr != nil {
raw := docstr.([]interface{})[0].(string)
m.Comment = rawCommentToDocStr(raw)
}
t := typ.(*Type)
if t.Name != "void" {
m.ReturnType = t
}
if oneway != nil {
m.Oneway = true
}
if arguments != nil {
m.Arguments = arguments.([]*Field)
}
if exceptions != nil {
m.Exceptions = exceptions.([]*Field)
for _, e := range m.Exceptions {
e.Modifier = Optional
}
}
return m, nil
}
FunctionType <- typ:("void" / FieldType) {
if t, ok := typ.(*Type); ok {
return t, nil
}
return &Type{Name: string(c.text)}, nil
}
Throws <- "throws" __ '(' __ exceptions:FieldList ')' {
return exceptions, nil
}
FieldType <- typ:(BaseType / ContainerType / Identifier) {
if t, ok := typ.(Identifier); ok {
return &Type{Name: string(t)}, nil
}
return typ, nil
}
BaseType <- name:BaseTypeName _ annotations:TypeAnnotations? {
return &Type{
Name: name.(string),
Annotations: toAnnotations(annotations),
}, nil
}
BaseTypeName <- ("bool" / "byte" / "i16" / "i32" / "i64" / "double" / "string" / "binary" ) {
return string(c.text), nil
}
ContainerType <- typ:(MapType / SetType / ListType) {
return typ, nil
}
MapType <- CppType? "map<" WS key:FieldType WS "," WS value:FieldType WS ">" _ annotations:TypeAnnotations? {
return &Type{
Name: "map",
KeyType: key.(*Type),
ValueType: value.(*Type),
Annotations: toAnnotations(annotations),
}, nil
}
SetType <- CppType? "set<" WS typ:FieldType WS ">" _ annotations:TypeAnnotations? {
return &Type{
Name: "set",
ValueType: typ.(*Type),
Annotations: toAnnotations(annotations),
}, nil
}
ListType <- "list<" WS typ:FieldType WS ">" _ annotations:TypeAnnotations? {
return &Type{
Name: "list",
ValueType: typ.(*Type),
Annotations: toAnnotations(annotations),
}, nil
}
CppType <- "cpp_type" cppType:Literal {
return cppType, nil
}
ConstValue <- Literal / BoolConstant / DoubleConstant / IntConstant / ConstMap / ConstList / Identifier
TypeAnnotations <- '(' __ annotations:TypeAnnotation* ')' {
var anns []*Annotation
for _, ann := range annotations.([]interface{}) {
anns = append(anns, ann.(*Annotation))
}
return anns, nil
}
TypeAnnotation <- name:Identifier _ value:('=' __ value:Literal { return value, nil })? ListSeparator? __ {
var optValue string
if value != nil {
optValue = value.(string)
}
return &Annotation{
Name: string(name.(Identifier)),
Value: optValue,
}, nil
}
BoolConstant <- ("true" / "false") {
return string(c.text) == "true", nil
}
IntConstant <- [-+]? Digit+ {
return strconv.ParseInt(string(c.text), 10, 64)
}
DoubleConstant <- [+-]? Digit* '.' Digit* ( ['Ee'] IntConstant )? {
return strconv.ParseFloat(string(c.text), 64)
}
ConstList <- '[' __ values:(ConstValue __ ListSeparator? __)* __ ']' {
valueSlice := values.([]interface{})
vs := make([]interface{}, len(valueSlice))
for i, v := range valueSlice {
vs[i] = v.([]interface{})[0]
}
return vs, nil
}
ConstMap <- '{' __ values:(ConstValue __ ':' __ ConstValue __ (',' / &'}') __)* '}' {
if values == nil {
return nil, nil
}
vals := values.([]interface{})
kvs := make([]KeyValue, len(vals))
for i, kv := range vals {
v := kv.([]interface{})
kvs[i] = KeyValue{
Key: v[0],
Value: v[4],
}
}
return kvs, nil
}
///////////////////////////////////////////////////////////////////////////////
// FRUGAL //
///////////////////////////////////////////////////////////////////////////////
Scope <- docstr:(DocString __)? "scope" __ name:Identifier __ prefix:Prefix? __ '{' __ operations:(Operation __)* ('}' / EndOfScopeError) _ annotations:TypeAnnotations? EOS {
ops := operations.([]interface{})
scope := &Scope{
Name: string(name.(Identifier)),
Operations: make([]*Operation, len(ops)),
Prefix: defaultPrefix,
Annotations: toAnnotations(annotations),
}
if docstr != nil {
raw := docstr.([]interface{})[0].(string)
scope.Comment = rawCommentToDocStr(raw)
}
if prefix != nil {
scope.Prefix = prefix.(*ScopePrefix)
}
for i, o := range ops {
op := o.([]interface{})[0].(*Operation)
scope.Operations[i] = op
}
return scope, nil
}
EndOfScopeError <- . {
return nil, errors.New("parser: expected end of scope")
}
Prefix <- "prefix" __ PrefixToken ('.' PrefixToken)* {
prefix := strings.TrimSpace(strings.TrimPrefix(string(c.text), "prefix"))
return newScopePrefix(prefix)
}
PrefixToken <- ('{' PrefixWord '}') / PrefixWord
PrefixWord <- [^\r\n\t\f .{}]+
Operation <- docstr:(DocString __)? name:Identifier _ ':' __ typ:FieldType _ annotations:TypeAnnotations? ListSeparator? {
o := &Operation{
Name: string(name.(Identifier)),
Type: typ.(*Type),
Annotations: toAnnotations(annotations),
}
if docstr != nil {
raw := docstr.([]interface{})[0].(string)
o.Comment = rawCommentToDocStr(raw)
}
return o, nil
}
///////////////////////////////////////////////////////////////////////////////
// GENERAL //
///////////////////////////////////////////////////////////////////////////////
Literal <- (('"' (`\"` / [^"])* '"') / ('\'' (`\'` / [^'])* '\'')) {
if len(c.text) != 0 && c.text[0] == '\'' {
intermediate := strings.Replace(string(c.text[1:len(c.text)-1]), `\'`, `'`, -1)
return strconv.Unquote(`"` + strings.Replace(intermediate, `"`, `\"`, -1) + `"`)
}
return strconv.Unquote(string(c.text))
}
Identifier <- (Letter / '_')+ (Letter / Digit / [._])* {
return Identifier(string(c.text)), nil
}
ListSeparator <- [,;]
Letter <- [A-Za-z]
Digit <- [0-9]
SourceChar <- .
DocString <- "/**@" ( !"*/" SourceChar )* "*/" {
comment := string(c.text)
comment = strings.TrimPrefix(comment, "/**@")
comment = strings.TrimSuffix(comment, "*/")
return strings.TrimSpace(comment), nil
}
Comment <- MultiLineComment / SingleLineComment
MultiLineComment <- !DocString "/*" ( !"*/" SourceChar )* "*/"
MultiLineCommentNoLineTerminator <- !DocString "/*" ( !( "*/" / EOL ) SourceChar )* "*/"
SingleLineComment <- ("//" ( !EOL SourceChar )*) / ("#" ( !EOL SourceChar )*)
__ <- ( Whitespace / EOL / Comment )*
_ <- ( Whitespace / MultiLineCommentNoLineTerminator )*
WS <- Whitespace*
Whitespace <- [ \t\r]
EOL <- '\n'
EOS <- __ ';' / _ SingleLineComment? EOL / __ EOF
EOF <- !.