1. 12
  1. 2

    Very cool! I peeked at the code, and it looks like a big part of it was adding location info to AST nodes?

    So I would guest that goawk is an AST interpreter? (like the original awk, but unlike mawk I think)

    Is there a runtime cost to coverage mode?

    1. 2

      I wouldn’t say a “big” part of it was adding location info to AST nodes, but yeah, those are needed so that the “coverage injector” can record map to line/column numbers in the report. Basically GoAWK has to execute the __COVER assignments in the coverage-injected code, for example below:

      $ goawk -f prog.awk -covermode set -d
      BEGIN {
          __COVER["3"] = 1
          print "will always run"
          if ((1 + 1) == 2) {
              __COVER["1"] = 1
              print "should run"
          } else {
              __COVER["2"] = 1
              print "won't run"
          }
      }
      

      GoAWK started life as a tree-walking interpreter, but I switched to a byte-code compiler + interpreter a while back. There’s still a runtime cost to coverage as it has to record lines covered in a map, but it’s not on by default, so hopefully not too much of an issue for users.

      1. 2

        Kind of a tangent, but I’m curious: when it was an AST interpreter, how did you implement break / continue / return in Go?

        I do it with exceptions, but Go doesn’t have those. You can just use the normal style of return result, err, or I have seen people polling a global flag. Maybe you can use panic/recover, though I think that would be frowned upon

        1. 2

          When the interpreter walked the AST I used error returns for this: I just had the BreakStmt return a user-defined error errBreak, and then all the loops would check for this error and break, for example WhileStmt. Now that it’s bytecode-compiled I still use error returns, though I can be a bit smarter about turning breaks and continues into jump opcodes most of the time.

          Originally in the AST-walking interpreter I had used panic/recover for this, mainly because it simplified evaluation, but it was significantly faster to use error returns (and you’re right, not idiomatic Go), so I switched to that early on.