jarib/webidl

View on GitHub
lib/webidl/parser/idl.treetop

Summary

Maintainability
Test Coverage
# -*- coding: utf-8 -*-
module WebIDL
  module Parser

    # http://dev.w3.org/2006/webapi/WebIDL/#idl-grammar
    grammar IDL

      rule Definitions
        ws metadef:(eal:ExtendedAttributeList ws d:Definition ws defs:Definitions)? ws <ParseTree::Definitions>
      end

      rule Definition
        CallbackOrInterface
        / Interface
        / Partial
        / Dictionary
        / Exception
        / Enum
        / TypeDef
        / IncludesStatement
        / ImplementsStatement
      end

      rule CallbackOrInterface
        "callback" ws obj:CallbackRestOrInterface { def build(parent) obj.build(parent) end }
      end

      rule CallbackRestOrInterface
        CallbackRest / Interface
      end

      rule Interface
        "interface" ws (mixin:"mixin" ws)? name:identifier ws inherits:Inheritance ws "{" ws members:InterfaceMembers ws "}" ws ";" <ParseTree::Interface>
      end

      rule Partial
        "partial" ws defn:PartialDefinition { def build(parent) defn.build(parent) end }
      end

      rule PartialDefinition
        PartialInterface / PartialDictionary
      end

      # like an interface, but no inheritance
      rule PartialInterface
        "interface" ws name:identifier ws "{" ws members:InterfaceMembers ws "}" ws ";" <ParseTree::PartialInterface>
      end

      rule Inheritance
        (":" ws names:ScopedNameList <ParseTree::Inheritance>)?
      end

      # extract to a generic Members syntax node?
      rule InterfaceMembers
        (eal:ExtendedAttributeList ws member:InterfaceMember ws members:InterfaceMembers ws <ParseTree::InterfaceMembers>)?
      end

      rule InterfaceMember
        Const / AttributeOrOperation
      end

      #   Dictionary  → "dictionary" identifier Inheritance "{" DictionaryMembers "}" ";"
      rule Dictionary
        "dictionary" ws name:identifier ws inherits:Inheritance ws "{" ws members:DictionaryMembers ws "}" ws ";" <ParseTree::Dictionary>
      end

      # Like a dictionary, but no inheritance
      rule PartialDictionary
        "dictionary" ws name:identifier ws "{" ws members:DictionaryMembers ws "}" ws ";" <ParseTree::PartialDictionary>
      end

      # [11]  DictionaryMembers → ExtendedAttributeList DictionaryMember DictionaryMembers
      #  | ε
      rule DictionaryMembers
        (eal:ExtendedAttributeList ws member:DictionaryMember ws members:DictionaryMembers <ParseTree::DictionaryMembers>)?
      end

      rule Required
        "required"
      end

      rule DictionaryMember
        required:Required? ws type:Type ws name:identifier ws default:Default ws ";" <ParseTree::DictionaryMember>
      end

      rule Default
        ("=" ws val:DefaultValue { def build() val.build end })?
      end

      rule DefaultValue
        string / ConstValue
      end

      rule Exception
        "exception" ws name:identifier ws inherits:Inheritance ws "{" ws members:ExceptionMembers ws "}" ws ";" <ParseTree::Exception>
      end

      # extract to a generic Members syntax node?
      rule ExceptionMembers
        (eal:ExtendedAttributeList ws member:ExceptionMember ws members:ExceptionMembers ws <ParseTree::InterfaceMembers>)?
      end

      rule CallbackRest
        name:identifier ws "=" ws return_type:ReturnType ws "(" ws args:ArgumentList ")" ws ";" <ParseTree::Callback>
      end

      rule TypeDef
        "typedef" ws type:Type ws name:identifier ws ";" <ParseTree::TypeDef>
      end

      rule ImplementsStatement
        implementor:ScopedName ws "implements" ws implementee:ScopedName ws ";" <ParseTree::ImplementsStatement>
      end

      rule IncludesStatement
        includer:ScopedName ws "includes" ws includee:ScopedName ws ";" <ParseTree::IncludesStatement>
      end

      rule Const
        "const" ws type:Type ws name:identifier ws "=" ws const_expr:ConstValue ws ";" <ParseTree::Const>
      end

      rule ConstValue
          "null" { def build() nil end }
        / BooleanLiteral
        / FloatLiteral
        / integer
      end

      rule BooleanLiteral
        ("true" / "false") { def build() text_value == "true" end }
      end

      rule FloatLiteral
        float
          / "-" "Infinity" { def build() -1.0/0 end }
          / "Infinity" { def build() 1.0/0 end }
          / "NaN" { def build() Float::NAN end }
      end

      rule Enum
        "enum" ws id:identifier ws "{" ws values:EnumValueList ws "}" ws ";" <ParseTree::Enum>
      end

      rule EnumValueList
        name:string ws
        vals:EnumValues
        ws ","? # trailing commas actually not allowed, but in use
        {
          def build()
            res = [name.build]
            res += vals.build if vals.any?

            res
          end
        }
      end

      rule EnumValues
        ("," ws name:string ws vals:EnumValues {
          def build()
            res = [name.build]
            res += vals.build if vals.any?

            res
          end
        })?
      end

      rule AttributeOrOperation
        StringifierAttributeOrOperation / Attribute / Operation
      end

      rule StringifierAttributeOrOperation
        "stringifier" ws a_or_op:(Attribute / OperationRest / ";") <ParseTree::StringifierAttributeOrOperation>
      end

      rule Attribute
        readonly:ReadOnly ws "attribute" ws type:Type ws name:identifier ws getraises:GetRaises ws setraises:SetRaises ws ";" <ParseTree::Attribute>
      end

      rule ReadOnly
        "readonly"?
      end

      rule GetRaises
        ("getraises" ws list:ExceptionList)?
      end

      rule SetRaises
        ("setraises" ws list:ExceptionList)?
      end

      rule Operation
        static:"static"? specials:(OmittableSpecials / Specials) ws op:OperationRest <ParseTree::Operation>
      end

      rule OmittableSpecials
        omit:"omittable" ws Specials <ParseTree::OmittableSpecials>
      end

      rule Specials
        (first:Special ws rest:Specials <ParseTree::Specials>)?
      end

      rule Special
          "getter"
        / "setter"
        / "creator"
        / "deleter"
        / "legacycaller"
      end

      rule OperationRest
        type:ReturnType ws optional_id:OptionalIdentifier ws "(" ws args:ArgumentList ws ")" ws raises:Raises ws ";" <ParseTree::Operation>
      end

      rule OptionalIdentifier
        identifier?
      end

      rule Raises
        ("raises" ExceptionList)?
      end

      rule ExceptionList
        "(" ScopedNameList ")"
      end

      rule ArgumentList
        (arg:Argument ws args:Arguments <ParseTree::ArgumentList>)?
      end

      rule Arguments
        ("," ws arg:Argument ws args:Arguments <ParseTree::ArgumentList>)?
      end

      rule Argument
        eal:ExtendedAttributeList ws "in"? ws arg:OptionalOrRequiredArgument <ParseTree::Argument>
      end

      rule OptionalOrRequiredArgument
          optional:"optional" ws type:Type ws name:ArgumentName ws default:Default
        / type:Type ws variadic:Ellipsis ws name:ArgumentName
      end


      rule ArgumentName
        ArgumentNameKeyword { def build() text_value end } / identifier
      end

      rule ArgumentNameKeyword
        "attribute"
         / "callback"
         / "const"
         / "creator"
         / "deleter"
         / "dictionary"
         / "enum"
         / "exception"
         / "getter"
         / "implements"
         / "includes"
         / "inherit"
         / "interface"
         / "legacycaller"
         / "partial"
         / "setter"
         / "static"
         / "stringifier"
         / "typedef"
         / "unrestricted"
      end

      rule Ellipsis
        "..."?
      end

      rule ExceptionMember
        Const / ExceptionField
      end

      rule ExceptionField
        type:Type ws id:identifier ws ";" <ParseTree::ExceptionField>
      end

      rule ExtendedAttributeList
        ("[" ws attribute:ExtendedAttribute ws attributes:ExtendedAttributes ws "]" <ParseTree::ExtendedAttributeList>)?
      end

      rule ExtendedAttributes
        ("," ws attribute:ExtendedAttribute ws attributes:ExtendedAttributes <ParseTree::ExtendedAttributeList>)?
      end


      #
      # avoid infinite recursion trouble with the actual spec by using the "more restricted" ones
      #

      rule ExtendedAttribute
          ExtendedAttributeNamedArgList
        / ExtendedAttributeIdent
        / ExtendedAttributeScopedName
        / ExtendedAttributeArgList
        / ExtendedAttributeIdentList
        / ExtendedAttributeNoArg
      end

      rule ExtendedAttributeNoArg
        identifier {
          def build(parent)
            Ast::ExtendedAttribute.new(text_value)
          end
        }
      end

      rule ExtendedAttributeArgList
        name:identifier "(" ws args:ArgumentList ws ")" <ParseTree::ExtendedAttributeArgList>
      end

      rule ExtendedAttributeScopedName
        key:identifier "=" scoped_name:ScopedName <ParseTree::ExtendedAttributeScopedName>
      end

      rule ExtendedAttributeIdent
        key:identifier "=" value:identifier !"::" <ParseTree::ExtendedAttributeIdent>
      end

      rule ExtendedAttributeNamedArgList
        key:identifier "=" value:(name:identifier "(" ws args:ArgumentList ws ")" <ParseTree::ExtendedAttributeArgList>) <ParseTree::ExtendedAttributeNamedArgList>
      end

      rule ExtendedAttributeIdentList
        key:identifier "=" "(" list:IdentifierList ")" <ParseTree::ExtendedAttributeIdentList>
      end

      rule IdentifierList
        identifier Identifiers
      end

      rule Identifiers
        ("," ws IdentifierList)?
      end




    #  rule ExtendedAttribute
    #      "(" ws ExtendedAttributeInner ws ")" ws ExtendedAttributeRest
    #    / "[" ws ExtendedAttributeInner ws "]" ws ExtendedAttributeRest
    #    / "{" ws ExtendedAttributeInner ws "}" ws ExtendedAttributeRest
    #    / Other ws ExtendedAttributeRest
    #  end
    #
    #  rule ExtendedAttributeRest
    #    ExtendedAttribute?
    #  end
    #
    #  rule ExtendedAttributeInner
    #    ( "(" ws ExtendedAttributeInner ws ")" ws ExtendedAttributeInner
    #    / "[" ws ExtendedAttributeInner ws "]" ws ExtendedAttributeInner
    #    / "{" ws ExtendedAttributeInner ws "}" ws ExtendedAttributeInner
    #    / OtherOrComma ExtendedAttributeInner)?
    #  end
    #
      rule Other
          integer
        / float
        / identifier
        / string
        / other
        / "..."
        / ":"
        / "::"
        / ";"
        / "<"
        / "="
        / ">"
        / "?"
        / "false"
        / "object"
        / "true"
        / "any"
        / "attribute"
        / "boolean"
        / "caller"
        / "const"
        / "creator"
        / "deleter"
        / "double"
        / "exception"
        / "float"
        / "getraises"
        / "getter"
        / "implements"
        / "includes"
        / "in"
        / "interface"
        / "long"
        / "module"
        / "octet"
        / "omittable"
        / "optional"
        / "raises"
        / "sequence"
        / "setraises"
        / "setter"
        / "short"
        / "DOMString"
        / "stringifier"
        / "typedef"
        / "unsigned"
        / "void"
      end

      rule OtherOrComma
        Other / ","
      end

      rule Type
        SingleType
        / type:UnionType suffix:TypeSuffix <ParseTree::Type>
      end

      rule SingleType
        NonAnyType
          / type:"any" suffix:TypeSuffixStartingWithArray <ParseTree::Type>
      end

      rule UnionType
        "(" ws UnionMemberType ws "or" ws UnionMemberType ws UnionMemberTypes ws ")"
      end

      rule UnionMemberType
        NonAnyType
          / type:UnionType suffix:TypeSuffix <ParseTree::Type>
          / type:"any" suffix:TypeSuffixStartingWithArray <ParseTree::Type>
      end

      rule UnionMemberTypes
        ("or" ws UnionMemberType ws UnionMemberTypes)?
      end

      rule NonAnyType
        type:PrimitiveType suffix:TypeSuffix <ParseTree::Type>
          # added: cannot be followed by a NonSpace character, since e.g. DOMStringMap, DOMStringList or other identifiers would break parsing
          / type:PromiseType Null { def build(parent); type.build(parent); end }
          / type:"DOMString" suffix:TypeSuffix !NonSpace <ParseTree::Type>
          / "sequence" ws "<" ws type:Type ws ">" null:Null <ParseTree::SequenceType>
          / type:"object" suffix:TypeSuffix <ParseTree::Type>
          / type:"Date" suffix:TypeSuffix <ParseTree::Type>
          / type:identifier suffix:TypeSuffix <ParseTree::Type>
      end

      rule PromiseType
         "Promise" ws "<" ws return_type:ReturnType ws ">" {
            def build(parent)
              Ast::PromiseType.new(return_type.text_value)
            end
         }
      end

      rule ConstType
          PrimitiveType Null
          / identifier Null
      end

      # added: cannot be followed by a NonSpace character, since e.g. DOMStringMap, DOMStringList or other identifiers would break parsing
      rule PrimitiveType
        (UnsignedIntegerType
          / UnrestrictedFloatType
          / "boolean" !NonSpace
          / "byte" !NonSpace
          / "octet" !NonSpace) {
            def build(parent)
              Ast::Type.new(parent, text_value)
            end
          }
      end

      rule Null
        "?"?
      end

      rule TypeSuffix
        (
            array:("[" ws "]") suffix:TypeSuffix <ParseTree::TypeSuffix>
          / null:"?" suffix:TypeSuffixStartingWithArray <ParseTree::TypeSuffix>
        )?
      end

      rule TypeSuffixStartingWithArray
        (array:("[" ws "]") ws suffix:TypeSuffix <ParseTree::TypeSuffix>)?
      end

      rule UnsignedIntegerType
        "unsigned" ws IntegerType / IntegerType
      end

      rule UnrestrictedFloatType
        "unrestricted" ws FloatType
        / FloatType
      end

      rule FloatType
        "float" / "double"
      end

      rule IntegerType
        ("short" / "long" ws OptionalLong)
      end

      rule OptionalLong
        "long"?
      end

      rule Array
        ("[" ws "]")?
      end

      rule NonSpace
        (!' ' .)
      end

      rule ReturnType
        Type / "void" <ParseTree::VoidType>
      end

      rule ScopedNameList
        name:ScopedName ws names:ScopedNames  <ParseTree::ScopedNameList>
      end

      rule ScopedNames
        ("," ScopedName ws ScopedNames)?
      end

      rule ScopedName
        AbsoluteScopedName / RelativeScopedName
      end

      rule AbsoluteScopedName
        "::" name:identifier parts:ScopedNameParts <ParseTree::AbsoluteScopedName>
      end

      rule RelativeScopedName
        name:identifier parts:ScopedNameParts <ParseTree::RelativeScopedName>
      end

      rule ScopedNameParts
        ("::" ws name:identifier parts:ScopedNameParts)?
      end

      rule integer
        (hexint / octint / decint) { def build() Integer(text_value) end} # TODO: check edge cases
      end

      rule hexint
        "-"? "0" [Xx] [0-9A-Fa-f]+
      end

      rule octint
        "-"? "0" [0-7]+
      end

      rule decint
        "-"? [0-9]*
      end

      rule float
        ("-"? ([0-9]+ "." [0-9]* / [0-9]* "." [0-9]+) ([Ee] [+-]? [0-9]+)? / [0-9]+ [Ee] [+-]? [0-9]+) { def build() Float(text_value) end} # TODO: check edge cases
      end

      rule identifier
        [A-Z_a-z] [0-9A-Z_a-z]* { def build(parent = nil) text_value[/^_?(.+)/, 1] end }
      end

      rule string
        # spec only allows double quotes, but single quotes are used in the HTML spec
        double_quoted_string / single_quoted_string
      end

      rule double_quoted_string
        "\"" data:[^"]* "\"" { def build() data.text_value end }
      end

      rule single_quoted_string
        "'" data:[^']* "'" { def build() data.text_value end }
      end

      rule ws
        ([\t\n\r ]* ((line_comment /  block_comment) [\t\n\r ]*)+ / [\t\n\r ]+)?
      end

      rule other
        [^\t\n\r 0-9A-Z_a-z]
      end

      rule line_comment
        "//" (![\n\r] . )*
      end

      rule block_comment
         '/*' (!'*/' . )* '*/'
      end

    end # IDL
  end # Parser
end # WebIDL