#!/bin/awk -f

# scripts/dfn.awk - process a .dfn file
#
# Copyright (c) 2013-2014 Glenn Randers-Pehrson
#
# This code is released under the libpng license.
# For conditions of distribution and use, see the disclaimer
# and license in png.h

# The output of this script is written to the file given by
# the variable 'out', which should be set on the command line.
# Error messages are printed to stdout and if any are printed
# the script will exit with error code 1.

BEGIN{
   out="/dev/null"       # as a flag
   out_count=0           # count of output lines
   err=0                 # set if an error occurred
   sort=0                # sort the output
   array[""]=""
}

# The output file must be specified before any input:
NR==1 && out == "/dev/null" {
   print "out=output.file must be given on the command line"
   # but continue without setting the error code; this allows the
   # script to be checked easily
}

# Output can be sorted; two lines are recognized
$1 == "PNG_DFN_START_SORT"{
   sort=0+$2
   next
}

$1 ~ /^PNG_DFN_END_SORT/{
   # Do a very simple, slow, sort; notice that blank lines won't be
   # output by this
   for (entry in array) {
      while (array[entry] != "") {
         key = entry
         value = array[key]
         array[key] = ""

         for (alt in array) {
            if (array[alt] != "" && alt < key) {
               array[key] = value
               value = array[alt]
               key = alt
               array[alt] = ""
            }
         }

         print value >out
      }
   }
   sort=0
   next
}

/^[^"]*PNG_DFN *".*"[^"]*$/{
   # A definition line, apparently correctly formatted; extract the
   # definition then replace any doubled "" that remain with a single
   # double quote.  Notice that the original doubled double quotes
   # may have been split by tokenization
   #
   # Sometimes GCC splits the PNG_DFN lines; we know this has happened
   # if the quotes aren't closed and must read another line.  In this
   # case it is essential to reject lines that start with '#' because those
   # are introduced #line directives.
   orig=$0
   line=$0
   lineno=FNR
   if (lineno == "") lineno=NR

   if (sub(/^[^"]*PNG_DFN *"/,"",line) != 1) {
      print "line", lineno ": processing failed:"
      print orig
      err=1
      next
   } else {
      ++out_count
   }

   # Now examine quotes within the value:
   #
   #   @" - delete this and any following spaces
   #   "@ - delete this and any preceding spaces
   #   @' - replace this by a double quote
   #
   # This allows macro substitution by the C compiler thus:
   #
   #   #define first_name John
   #   #define last_name Smith
   #
   #   PNG_DFN"#define name @'@" first_name "@ @" last_name "@@'"
   #
   # Might get C preprocessed to:
   #
   #   PNG_DFN "#define foo @'@" John "@ @" Smith "@@'"
   #
   # Which this script reduces to:
   #
   #   #define name "John Smith"
   #
   while (1) {
      # While there is an @" remove it and the next "@
      if (line ~ /@"/) {
         if (line ~ /@".*"@/) {
            # Do this special case first to avoid swallowing extra spaces
            # before or after the @ stuff:
            if (!sub(/@" *"@/, "", line)) {
               # Ok, do it in pieces - there has to be a non-space between the
               # two.  NOTE: really weird things happen if a leading @" is
               # lost - the code will error out below (I believe).
               if (!sub(/@" */, "", line) || !sub(/ *"@/, "", line)) {
                  print "line", lineno, ": internal error:", orig
                  exit 1
               }
            }
         }

         # There is no matching "@.  Assume a split line
         else while (1) {
            if (getline nextline) {
               # If the line starts with '#' it is a preprocessor line directive
               # from cc -E; skip it:
               if (nextline !~ /^#/) {
                  line = line " " nextline
                  break
               }
            } else {
               # This is end-of-input - probably a missing "@ on the first line:
               print "line", lineno ": unbalanced @\" ... \"@ pair"
               err=1
               next
            }
         }

         # Keep going until all the @" have gone
         continue
      }

      # Attempt to remove a trailing " (not preceded by '@') - if this can
      # be done, stop now; if not assume a split line again
      if (sub(/"[^"]*$/, "", line))
         break

      # Read another line
      while (1) {
         if (getline nextline) {
            if (nextline !~ /^#/) {
               line = line " " nextline
               # Go back to stripping @" "@ pairs
               break
            }
         } else {
            print "line", lineno ": unterminated PNG_DFN string"
            err=1
            next
         }
      }
   }

   # Put any needed double quotes in (at the end, because these would otherwise
   # interfere with the processing above.)
   gsub(/@'/,"\"", line)

   # Remove any trailing spaces (not really required, but for
   # editorial consistency
   sub(/ *$/, "", line)

   # Remove trailing CR
   sub(/
$/, "", line)

   if (sort) {
      if (split(line, parts) < sort) {
         print "line", lineno ": missing sort field:", line
         err=1
      } else
         array[parts[sort]] = line
   }

   else
      print line >out
   next
}

/PNG_DFN/{
   print "line", NR, "incorrectly formatted PNG_DFN line:"
   print $0
   err = 1
}

END{
   if (out_count > 0 || err > 0)
      exit err

   print "no definition lines found"
   exit 1
}
