...
Tawesoft Logo

Source file src/tawesoft.co.uk/go/glcaps/parse.go

Documentation: src/tawesoft.co.uk/go/glcaps/parse.go

     1  package glcaps
     2  
     3  import (
     4      "fmt"
     5      "reflect"
     6      "strings"
     7      
     8      "tawesoft.co.uk/go/operator"
     9  )
    10  
    11  // parseAtom parses the next (space-delimited) word in the string starting at character offset, returning a slice to
    12  // the first word and an offset to one-past-the-end of the word, or an error.
    13  func parseAtom(tag string, offset int) (atom string, next int) {
    14      
    15      // the first index that isn't a space
    16      var i = offset
    17      for (i < len(tag)) && (tag[i] == ' ') { i++ }
    18      
    19      // the last index that isn't a space
    20      var j = i
    21      for (j < len(tag)) && (tag[j] != ' ') { j++ }
    22      
    23      if j - i == 0 {
    24          return "", -1
    25      }
    26      
    27      return tag[i:j], j
    28  }
    29  
    30  // parseCommand2 performs the common task of parsing two commands at once and combining their errors
    31  func parseCommand2(tag string, offset int) (c1 command, c2 command, next int, _err error) {
    32      var ac, ao, ae = parseCommand(tag, offset)
    33      if ae != nil { return c1, c2, 0, ae }
    34      
    35      var bc, bo, be = parseCommand(tag, ao)
    36      if be != nil { return c1, c2, 0, be }
    37      
    38      return ac, bc, bo, nil
    39  }
    40  
    41  // parseBinaryBooleanCopmmand performs the common task of parsing a command that is a function with two boolean
    42  // arguments and returns a boolean (e.g. AND, OR)
    43  func parseBinaryBooleanCommand(tag string, offset int, f func(bool, bool) bool) (c command, next int, _err error) {
    44      var c1, c2, o, e = parseCommand2(tag, offset)
    45      if e != nil { return c, 0, e }
    46      return commandBinaryBoolean{c1, c2, f}, o, nil
    47  }
    48  
    49  // parseCCompareCommand performs the common task of parsing a command that is a function with two arguments
    50  // and returns a boolean (e.g. LessThan)
    51  func parseCompareCommand(
    52      tag string,
    53      offset int,
    54      fi func(int, int) bool,
    55      ff func(float32, float32) bool,
    56      fs func(string, string) bool,
    57  ) (c command, next int, _err error) {
    58      var c1, c2, o, e = parseCommand2(tag, offset)
    59      if e != nil { return c, 0, e }
    60      return commandCompare{c1, c2, fi, ff, fs}, o, nil
    61  }
    62  
    63  // parseCommand parses and/or/not/ext/GetIntegerv/GetFloatv/if/eq/neq/lt/lte/gt/gte/value commands and returns an
    64  // offset to the end of the parsed command.
    65  func parseCommand(tag string, _offset int) (c command, next int, err error) {
    66      var start, offset = parseAtom(tag, _offset)
    67      if offset < 0 { return c, 0, fmt.Errorf("expected command") }
    68      
    69      switch start {
    70          case "and": return parseBinaryBooleanCommand(tag, offset, operator.Bool.Binary.And)
    71          case "or":  return parseBinaryBooleanCommand(tag, offset, operator.Bool.Binary.Or)
    72          
    73          case "eq":  return parseCompareCommand(tag, offset, operator.Int.Binary.Eq,  operator.Float32.Binary.Eq,  operationStringEq)
    74          case "neq": return parseCompareCommand(tag, offset, operator.Int.Binary.Eq,  operator.Float32.Binary.Neq, operationStringNeq)
    75          case "lt":  return parseCompareCommand(tag, offset, operator.Int.Binary.Lt,  operator.Float32.Binary.Lt,  nil)
    76          case "lte": return parseCompareCommand(tag, offset, operator.Int.Binary.Lte, operator.Float32.Binary.Lte, nil)
    77          case "gt":  return parseCompareCommand(tag, offset, operator.Int.Binary.Gt,  operator.Float32.Binary.Gt,  nil)
    78          case "gte": return parseCompareCommand(tag, offset, operator.Int.Binary.Gte, operator.Float32.Binary.Gte, nil)
    79  
    80          case "if":
    81              var ac, ao, ae = parseCommand(tag, offset)
    82              if ae != nil { return c, 0, ae }
    83              
    84              var bc, bo, be = parseCommand(tag, ao)
    85              if be != nil { return c, 0, be }
    86              
    87              var cc, co, ce = parseCommand(tag, bo)
    88              if ce != nil { return c, 0, ce }
    89              
    90              return commandIf{ac, bc, cc}, co, nil
    91          
    92          case "not":
    93              var c1, o, e = parseCommand(tag, offset)
    94              if e != nil { return c, 0, e }
    95              return commandNot{c1}, o, nil
    96              
    97          case "ext":
    98              var c1, o = parseAtom(tag, offset)
    99              if o < 0 { return c, 0, fmt.Errorf("expected name after ext") }
   100              return commandExt{c1}, o, nil
   101          
   102          case "GetString":
   103              var c1, o = parseAtom(tag, offset)
   104              if o < 0 { return c, 0, fmt.Errorf("expected name after GetString") }
   105              return commandGetString{c1}, o, nil
   106              
   107          case "GetIntegerv":
   108              var c1, o = parseAtom(tag, offset)
   109              if o < 0 { return c, 0, fmt.Errorf("expected name after GetIntegerv") }
   110              return commandGetIntegerv{c1}, o, nil
   111          
   112          case "GetFloatv":
   113              var c1, o = parseAtom(tag, offset)
   114              if o < 0 { return c, 0, fmt.Errorf("expected name after GetFloatv") }
   115              return commandGetFloatv{c1}, o, nil
   116              
   117          default:
   118              return commandValue{start}, offset, nil
   119      }
   120  }
   121  
   122  // parseParts splits a string on the first semicolon: for "a; b" it returns "a", " b".
   123  func parseParts(s string) (string, string) {
   124      var offset = strings.IndexByte(s, ';')
   125      if offset < 0 {
   126          return s[0: len(s)], s[0:0]
   127      } else {
   128          return s[0: offset], s[offset + 1:]
   129      }
   130  }
   131  
   132  func parseCompareRequirement(
   133      tag string,
   134      offset int,
   135      symbol string,
   136      fi func(int, int) bool,
   137      ff func(float32, float32) bool,
   138      fs func(string, string) bool,
   139  ) (r requirement, next int, _err error) {
   140      var r1, o = parseAtom(tag, offset)
   141      if o < 0 { return r, 0, fmt.Errorf("expected constant after comparison") }
   142      return requirementComparison{
   143          constant:   r1,
   144          symbol:     symbol,
   145          operationi: fi,
   146          operationf: ff,
   147          operations: fs,
   148      }, o, nil
   149  }
   150  
   151  // parseCommand parses and/or/not/ext/GetIntegerv/GetFloatv/if/eq/neq/lt/lte/gt/gte/value commands and returns an
   152  // offset to the end of the parsed command.
   153  func parseRequirement(tag string, _offset int) (r requirement, next int, err error) {
   154      var start, offset = parseAtom(tag, _offset)
   155      if offset < 0 { return r, -1, nil }
   156      
   157      switch start {
   158          case "required":
   159              return requirementRequired{}, offset, nil
   160          
   161          case "eq":  return parseCompareRequirement(tag, offset, "=",  operator.Int.Binary.Eq,  operator.Float32.Binary.Eq,  operationStringEq)
   162          case "neq": return parseCompareRequirement(tag, offset, "!=", operator.Int.Binary.Neq, operator.Float32.Binary.Neq, operationStringNeq)
   163          case "lt":  return parseCompareRequirement(tag, offset, "<",  operator.Int.Binary.Lt,  operator.Float32.Binary.Lt,  nil)
   164          case "lte": return parseCompareRequirement(tag, offset, "<=", operator.Int.Binary.Lte, operator.Float32.Binary.Lte, nil)
   165          case "gt":  return parseCompareRequirement(tag, offset, ">",  operator.Int.Binary.Gt,  operator.Float32.Binary.Gt,  nil)
   166          case "gte": return parseCompareRequirement(tag, offset, ">=", operator.Int.Binary.Gte, operator.Float32.Binary.Gte, nil)
   167          
   168          default:
   169              return r, 0, fmt.Errorf("unknown requirement: '%s'", start)
   170      }
   171  }
   172  
   173  func parseRequirements(t string) (requirements []requirement, err error) {
   174      var offset int
   175      var r requirement
   176      requirements = make([]requirement, 0)
   177      
   178      for {
   179          r, offset, err = parseRequirement(t, offset)
   180          if err != nil { return requirements, err }
   181          if offset < 0  { return requirements, nil }
   182      
   183          requirements = append(requirements, r)
   184      }
   185  }
   186  
   187  // parseTag parses the command and requirements clauses of a tag
   188  func parseTag(t string) (tag, error) {
   189      var left, right = parseParts(t)
   190      
   191      var command, index, err = parseCommand(left, 0)
   192      if err != nil { return tag{}, err }
   193      
   194      if index < len(left) && strings.TrimSpace(left[index + 1:]) != "" {
   195          return tag{}, fmt.Errorf("unexpected trailing string after end of command: '%s'", left[index:])
   196      }
   197      
   198      var requirements, rerr = parseRequirements(right)
   199      if rerr != nil { return tag{}, rerr }
   200  
   201      return tag{
   202          command: command,
   203          requirements: requirements,
   204      }, nil
   205  }
   206  
   207  func checkBoolRequirements(field reflect.StructField, result bool, rs []requirement) (errors Errors) {
   208      for _, r := range rs {
   209          var err = r.evalBool(field.Name, result)
   210          if err == nil { continue }
   211          
   212          errors.append(Error{
   213              Field: field.Name,
   214              Tag:   field.Tag.Get("glcaps"),
   215              Requirement: r,
   216              Message: err.Error(),
   217          })
   218      }
   219      
   220      return errors
   221  }
   222  
   223  func checkIntRequirements(field reflect.StructField, result int, rs []requirement)  (errors Errors) {
   224      for _, r := range rs {
   225          var err = r.evalInt(field.Name, result)
   226          if err == nil { continue }
   227          
   228          errors.append(Error{
   229              Field: field.Name,
   230              Tag:   field.Tag.Get("glcaps"),
   231              Requirement: r,
   232              Message: err.Error(),
   233          })
   234      }
   235      
   236      return errors
   237  }
   238  
   239  func checkFloatRequirements(field reflect.StructField, result float32, rs []requirement) (errors Errors) {
   240      for _, r := range rs {
   241          var err = r.evalFloat(field.Name, result)
   242          if err == nil { continue }
   243          
   244          errors.append(Error{
   245              Field: field.Name,
   246              Tag:   field.Tag.Get("glcaps"),
   247              Requirement: r,
   248              Message: err.Error(),
   249          })
   250      }
   251      
   252      return errors
   253  }
   254  
   255  func checkStringRequirements(field reflect.StructField, result string, rs []requirement) (errors Errors) {
   256      for _, r := range rs {
   257          var err = r.evalString(field.Name, result)
   258          if err == nil { continue }
   259          
   260          errors.append(Error{
   261              Field: field.Name,
   262              Tag:   field.Tag.Get("glcaps"),
   263              Requirement: r,
   264              Message: err.Error(),
   265          })
   266      }
   267      
   268      return errors
   269  }
   270  
   271  func parseStructField(binding *Binding, extensions []string, field reflect.StructField, setter reflect.Value, value interface{}) (errors Errors) {
   272  
   273      var parse = func(field reflect.StructField) (_tag tag, ok bool) {
   274          // fmt.Printf("got float32 %s %s %s\n", field.Name, field.Tag, field.Tag.Get("glcaps"))
   275          var glcapstag, exists = field.Tag.Lookup("glcaps")
   276          if !exists { return tag{}, false }
   277          
   278          var t, err = parseTag(glcapstag)
   279          if err != nil {
   280              errors.append(Error{
   281                  Field: field.Name,
   282                  Tag:   glcapstag,
   283                  Message: fmt.Sprintf("tag parse error: %v", err),
   284              })
   285              return t, false
   286          }
   287          
   288          return t, true
   289      }
   290      
   291      var kind = field.Type.Kind()
   292      
   293      if kind == reflect.Struct {
   294          errors.append(parseStruct(binding, extensions, setter)...)
   295      } else {
   296          var t, ok = parse(field)
   297          if ok {
   298              switch kind {
   299                  case reflect.Bool:
   300                      var result = t.command.evalBool(binding, extensions)
   301                      errors.append(checkBoolRequirements(field, result, t.requirements)...)
   302                      setter.SetBool(result)
   303                      
   304                  case reflect.Int:
   305                      var result = t.command.evalInt(binding, extensions)
   306                      errors.append(checkIntRequirements(field, result, t.requirements)...)
   307                      setter.SetInt(int64(result))
   308                      
   309                  case reflect.Float32: fallthrough
   310                  case reflect.Float64:
   311                      var result = t.command.evalFloat(binding, extensions)
   312                      errors.append(checkFloatRequirements(field, result, t.requirements)...)
   313                      setter.SetFloat(float64(result))
   314  
   315                  case reflect.String:
   316                      var result = t.command.evalString(binding, extensions)
   317                      errors.append(checkStringRequirements(field, result, t.requirements)...)
   318                      setter.SetString(result)
   319              }
   320          }
   321      }
   322      
   323      return errors
   324  }
   325  
   326  func parseStruct(binding *Binding, extensions []string, s reflect.Value) (errors Errors) {
   327      
   328      if s.Kind() != reflect.Struct {
   329          panic("target must be a struct or pointer to struct")
   330      }
   331      
   332      for i := 0; i < s.NumField(); i++ {
   333          errors.append(parseStructField(binding, extensions, s.Type().Field(i), s.Field(i), s.Field(i).Interface())...)
   334      }
   335      
   336      return errors
   337  }
   338  
   339  // Parse parses a struct and parses struct tag annotations to identify the required OpenGL information. It fills the
   340  // target struct with the results, and returns zero or more Errors if any defined requirements are not met. It also
   341  // returns a sorted string list of all supported OpenGL extensions.
   342  //
   343  // The struct tag key is `glcaps`. The struct tag syntax is a space-separated list of commands, optionally followed
   344  // by a colon and a space-separated list of requirements.
   345  //
   346  // Commands:
   347  //
   348  //    and command1 command2          - return true if command1 and command2 are true
   349  //    or  command1 command2          - return true if either command1 or command2 are true
   350  //    not command                    - return the boolean opposite of a command
   351  //    ext GL_EXT_name                - return true if the given extension is supported
   352  //    GetIntegerv GL_name            - lookup and return an integer value
   353  //    GetFloatv GL_name              - lookup and return a float value
   354  //    if command1 command2 command3  - if command1 is true, return the result of command2 otherwise return command3
   355  //    eq|neq|lt|lte|gt|gte command1 command2 - return true if command1 ==/!=/</<=/>/>= command2 respectively
   356  //    value                          - a value literal (e.g. true, false, 123, 1.23, 128KiB)
   357  //
   358  // Requirements:
   359  //
   360  //    required                       - generate an error if the result is not true
   361  //    eq|neq|lt|lte|gt|gte value     - generate an error if the command is not ==, !=, <, <=, >, >= value respectively
   362  //
   363  func Parse(binding *Binding, target interface{}) (extensions Extensions, errors Errors) {
   364      extensions = binding.QueryExtensions()
   365      return extensions, parseStruct(binding, extensions, reflect.ValueOf(target).Elem())
   366  }
   367  
   368  

View as plain text