#!/usr/bin/gawk -f

############################################################################
#
# Script that removes preprocessor checks on the kernel version. Somewhat
# related to the v4l-scripts-gentree.pl script.
#
# Copyright (C) 2008 Bart Van Assche <bart.vanassche@gmail.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, version 2
# of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
############################################################################

# Usage:
# * Specify the kernel version code as follows: -v kernel_version=...
# * Provide the patch to be processed to stdin.
#
# The output of this script will be a patch that is specialized for the
# specified kernel version.


# Convert a kernel version in the x.y.z format into numeric form, just like
# the KERNEL_VERSION() macro.

function version_code(kver) {
  match(kver, "([0-9]+).([0-9]+).([0-9]+)", array)
  return 65536*array[1] + 256*array[2] + array[3]
}


# Evaluate a preprocessor statement via repeated substitutions.
# Mathematicians call this algorithm 'term rewriting'.
# Note: the order in which the substitutions appear below is important --
# it is the same order as the order of operators in C.

function evaluate(stmnt) {
  gsub(" *\\/\\*[^*]*\\*\\/ *", "", stmnt)

  gsub("^+ *# *", "+#", stmnt)

  if (match(stmnt, "^+#ifdef (.*)$", arg))
  {
    stmnt = "+#if defined(" arg[1] ")"
  }

  if (match(stmnt, "^+#ifndef (.*)$", arg))
  {
    stmnt = "+#if ! defined(" arg[1] ")"
  }

  gsub("LINUX_VERSION_CODE", LINUX_VERSION_CODE, stmnt)

  gsub("defined\\(INSIDE_KERNEL_TREE\\)", "1", stmnt)

  if (RHEL_MAJOR == "")
    gsub("defined\\(RHEL_MAJOR\\)", "0", stmnt)
  else
  {
    gsub("defined\\(RHEL_MAJOR\\)", "1", stmnt)
    gsub("RHEL_MAJOR", RHEL_MAJOR, stmnt)
  }

  if (RHEL_MINOR == "")
    gsub("defined\\(RHEL_MINOR\\)", "0", stmnt)
  else
  {
    gsub("defined\\(RHEL_MINOR\\)", "1", stmnt)
    gsub("RHEL_MINOR", RHEL_MINOR, stmnt)
  }

  if (RHEL_MAJOR == "" || RHEL_MINOR == "")
    gsub("defined\\(RHEL_RELEASE_CODE\\)", "0", stmnt)
  else
  {
    gsub("defined\\(RHEL_RELEASE_CODE\\)", "1", stmnt)
    gsub("RHEL_RELEASE_CODE", RHEL_MAJOR * 256 + RHEL_MINOR, stmnt)
  }

  do
  {
    last_stmnt = stmnt

    pattern = "! *([0-9]+)"
    while (match(stmnt, pattern, op) != 0)
    {
      sub(pattern, op[1] == 0, stmnt)
    }
    
    pattern="KERNEL_VERSION\\(([0-9]+) *, *([0-9]+) *, *([0-9]+) *\\)"
    while (match(stmnt, pattern, op) != 0)
    {
      sub(pattern, op[1] * 65536 + op[2] * 256 + op[3], stmnt)
    }
  
    pattern="([0-9]+) *(<|<=|>|>=|==) *([0-9]+)"
    while (match(stmnt, pattern, op) != 0)
    {
      result="error"
      if      (op[2] == "<" ) result = op[1] <  op[3]
      else if (op[2] == "<=") result = op[1] <= op[3]
      else if (op[2] == ">" ) result = op[1] >  op[3]
      else if (op[2] == ">=") result = op[1] >= op[3]
      else if (op[2] == "==") result = op[1] == op[3]
      sub(pattern, result, stmnt)
    }
  
    pattern="([0-9]+) *\\&\\& *([0-9]+)"
    while (match(stmnt, pattern, op) != 0)
    {
      sub(pattern, (op[1] != 0) && (op[2] != 0), stmnt)
    }
  
    pattern="([0-9]+) *\\|\\| *([0-9]+)"
    while (match(stmnt, pattern, op) != 0)
    {
      sub(pattern, (op[1] != 0) || (op[2] != 0), stmnt)
    }
  
    pattern="\\(([0-9]+)\\)"
    while (match(stmnt, pattern, op) != 0)
    {
      sub(pattern, op[1], stmnt)
    }
  } while (stmnt != last_stmnt)

  return stmnt
}


# Evaluate ! stmnt
function invert(stmnt) {
  sub("^+#if ", "+#if ! ", stmnt)
  return evaluate(stmnt)
}


# Handle #if or #elif

function handle_if()
{
  # Only act on preprocessor conditional expressions with regard to the Linux
  # kernel version, and do not interpret other expressions.
  if ($0 ~ "LINUX_VERSION_CODE"    \
      || $0 ~ "INSIDE_KERNEL_TREE" \
      || $0 ~ "RHEL_MAJOR"         \
      || $0 ~ "RHEL_MINOR"         \
      || $0 ~ "RHEL_RELEASE_CODE")
  {
    #print $0 " -> " evaluated
    $0 = evaluated
  }
  else
  {
    evaluated = "+#if undecided"
  }
  #printf "%s -> %s\n", $0, evaluated
  if (evaluated ~ "^+#if")
  {
    if_stmnt[if_nesting_level] = evaluated
  }
  else
  {
    sub("^+#elif ",
        sprintf("+#if ! %d \\&\\& ", decision[if_nesting_level]),
        evaluated)
    evaluated = evaluate(evaluated)
  }
  decision[if_nesting_level] = evaluated
  matching_if = if_stmnt[if_nesting_level]
}


# Decide whether or not to print the preprocessor statement $0.

function process_preprocessor_statement() {
  last_if_nesting_level = if_nesting_level
  evaluated = evaluate($0)
  condition = 1
  if (evaluated ~ "^+#if")
  {
    if_nesting_level++
    handle_if()
  }
  else if (evaluated ~ "^+#elif")
  {
    handle_if()
  }
  else if (evaluated ~ "^+#else")
  {
    matching_if = if_stmnt[if_nesting_level]
    decision[if_nesting_level] = invert(decision[if_nesting_level])
  }
  else if (evaluated ~ "^+#endif")
  {
    matching_if = if_stmnt[if_nesting_level]
    if_nesting_level--
  }
  else
  {
    condition = 0
  }
  if (condition)
  {
    output = 1
    for (i = if_nesting_level; i >= 0; i--)
    {
      output = output && decision[i] != "+#if 0"
    }
  }
  if (output && (! condition || condition && matching_if !~ "^+#if [01]"))
  {
    line[lines++]=$0
  }
  else
  {
    lines_deleted++
  }
}

function dump_lines() {
  if (h[0] != "")
    printf "@@ -%d,%d +%d,%d @@%s\n",h[1],h[2],h[3],h[4]-lines_deleted,h[5]
  for (i = 0; i < lines; i++)
    print line[i]
  lines = 0
  lines_deleted = 0
}

BEGIN {
  # Verify arguments.
  if (kernel_version == "")
  {
    printf "Error: kernel_version was not specified.\n"
    exit 1
  }
  LINUX_VERSION_CODE = version_code(kernel_version)
  if (LINUX_VERSION_CODE < 2*65536 || LINUX_VERSION_CODE > 3*65536)
  {
    printf "Error: kernel version (%s) is out of range.\n", kernel_version
    exit 1
  }

  # Variable initialization.
  lines = 0
  lines_deleted = 0
  output = 1
  if_nesting_level = -1
}


{
  # If the line currently being processed is a hunk header, print all lines
  # that were stored in the array line[] since the last hunk header was read.
  if (match($0, "^@@ -([0-9]*),([0-9]*) \\+([0-9]*),([0-9]*) @@(.*)$"))
  {
    /* print h[1], h[2], h[3], h[4], h[5] */
    dump_lines()
    match($0, "^@@ -([0-9]*),([0-9]*) \\+([0-9]*),([0-9]*) @@(.*)$", h)
  }
  else if (match($0, "^+ *#"))
  {
    process_preprocessor_statement()
  }
  else if (output)
  {
    # Store the line that was just read.
    line[lines++]=$0
  }
  else
  {
    # Discard the last read line.
    lines_deleted++
  }
}

END {
  # Dump processed contents of the last read hunk.
  dump_lines()
}
