The default design is always procedural programming:
    
    
    import std / [strformat, strutils, tables]
    
    proc error(msg: string) = echo msg
    
    proc wantNumberBetween(field, s: string; a, b: int) =
      try:
        let x = parseInt(s)
        if x < a or x > b:
          error(&"{field} of value {s} must be in range {a}..{b}")
      except ValueError:
        error(&"{field} takes a number")
    
    template intField(name: string; a, b: int) {.dirty.} =
      if name in fields:
        wantNumberBetween name, fields[name], a, b
        del fields, name # mark as processed
    
    proc validateDecoderA(fields: var Table[string, string]) =
      intField "abc", 1, 3
      intField "xyz", 5, 6
    
    proc validateDecoderB(fields: var Table[string, string]) =
      # "inheritance" is simply done with a function call
      validateDecoderA(fields)
      intField "B specfiic field", 12, 16
    
    proc noRemainingFields(fields: Table[string, string]) =
      for k, v in pairs(fields):
        error &"unknown field: {k} of value {v}"
    
    proc validate(decoder: string; fields: Table[string, string]) =
      var fullCopy = fields
      case decoder
      of "A": validateDecoderA fullCopy
      of "B": validateDecoderB fullCopy
      noRemainingFields fullCopy
    
    validate "A", {"abc": "34", "unknown": "abc"}.toTable
    
    
    
    Run

That's the entire logic but you will have "hundreds" of entries like `intField 
"abc", 1, 2`. But any solution requires these and by using Nim code you can 
compress the descriptions in ways that are usually outside the realm of custom 
text formats that you interpret at runtime.

Reply via email to