#!/usr/bin/env texlua

--[[ This is cwebindent.lua

Copyright (C) Taco Hoekwater 2010, Donated to the Public Domain

This program cleans up a CWEB file by running 'indent' on the C parts of it.

--]]

function read_modules (f) 
  local file = io.open(f)
  if not file then return nil  end
  local data = file:read('*a')
  file:close()
  if not data then return nil  end
  local webmodules = {}
  local function store_module (a) webmodules[#webmodules+1] = a  end
  local modulestart = lpeg.P("\n") * (lpeg.P("@ ") + lpeg.P("@\n") + lpeg.P("@*"))
  local module = lpeg.C(modulestart * (1 - modulestart)^1) / store_module
  local limbo = lpeg.C((1 - modulestart)^1) / store_module
  local modules = limbo * module^1
  lpeg.match(modules, data)
  return webmodules  
end

function disect_module (m) 
   local a = { ["src"] = m }
   local function sdoc (v) a.doc  = v end
   local function sdef (v) a.adef  = v end
   local function scod (v) a.cod  = v end
   local non_doc = lpeg.P("@d") + lpeg.P("@f") + lpeg.P("@<") + lpeg.P("@(")  + lpeg.P("@c")  + lpeg.P("@h")
   local non_def = lpeg.P("@<") + lpeg.P("@(") + lpeg.P("@c") + lpeg.P("@h")
   local documentation = lpeg.C((1 - non_doc)^0) / sdoc
   local definitions = lpeg.C((1 - non_def)^0) / sdef
   local code = lpeg.C(lpeg.P(1)^0) / scod
   local parts = documentation * definitions * code
   lpeg.match(parts, m)
   return a
end


function append_to (old, new ) 
   local m = old
   for a,b in ipairs (new)  do
--	  io.write(b)
	  m[#m+1] = b
   end
--   io.write('\n')
   return m
end

local allc = io.open("all.c","w")

function parse_module (module) 
  local space  = lpeg.S(" \t\n\r")^0
  local equal  = lpeg.P("=")
  local equals = lpeg.P("==")
  if module.cod and #(module.cod)>0 then
      local function scode (v) module.code  = v end
      local function sname (v) module.name  = v end
      local name_end = lpeg.P("@>")
      local name_start = lpeg.P("@") * lpeg.S("<(")
      local name_body = (1-name_end)^1
      local name =  lpeg.C(name_start * name_body * name_end)  / sname
      local unnamed = lpeg.C(lpeg.P("@c") + lpeg.P("@h") ) / sname
      local body = lpeg.C(lpeg.P(1)^1) / scode
      local pascal = lpeg.P(lpeg.P(name) * space * equal + unnamed)^1 * space * body
      lpeg.match(pascal,module.cod)
      if module.code then
	 local cfile = io.open("temp.c","w")
         if not cfile then
           print ("Cannot open temp file for indent")
           return
         end
         local c = module.code
         c = string.gsub(c,'@<', 'F("@<')
         c = string.gsub(c,'@%.','D("@.')
         c = string.gsub(c,'@:', 'C("@:')
         c = string.gsub(c,'@%^','H("@^')
         c = string.gsub(c,'@=', 'E("@=')
         c = string.gsub(c,'@>', '@>")')
         cfile:write(c .. '/*INDENTDONE*/;');
         allc:write(c);
         cfile:close()
         local ret = os.spawn('indent temp.c')
         if ret>0 then
	    print('indent error: ' .. ret)
            print('===================')
            os.spawn('cat temp.c')
            print('===================')
         else
	   cfile = io.open("temp.c","r")
           module.code = '\n'..cfile:read('*a')
           module.code = string.gsub(module.code, '\n?%s?/%*INDENTDONE%*/;\n',''); -- last semicolon
           module.code = string.gsub(module.code,'F%s+%("@<', '@<') 
           module.code = string.gsub(module.code,'%s?D%s+%("@%.', '\n@.') 
           module.code = string.gsub(module.code,'%s?C%s+%("@:', '\n@:') 
           module.code = string.gsub(module.code,'%s?H%s+%("@^', '\n@^') 
           module.code = string.gsub(module.code,'E%s+%("@=', '@=') 
           module.code = string.gsub(module.code,'@>"%)', '@>') 
           module.code = string.gsub(module.code,'%)\n{', ') {') 
           module.code = string.gsub(module.code,'@> @<', '@>\n@<') 
           module.code = string.gsub(module.code,'@\n#', '@#') 
           module.code = string.gsub(module.code,'\n%s*\n', '\n') 
           cfile:close()
         end
      end
      module.cod = nil
  end
  return
end


function write_cweb(file,module)
   if module.doc then
	 file.write(file, module.doc)
   end
   if module.adef then
	 file.write(file, module.adef)
   end
   if module.code then
      if module.name == "@c" then
	 file.write(file,"@c")
      elseif module.name == "@h" then
	 file.write(file,"@h")
      elseif module.name then
	 file.write(file, module.name .. "=")
      end
      if (string.sub(module.code,1,1) ~= "\n") then
         file.write(file, "\n")
      end
      file.write(file, module.code)
      file.write(file, "\n")
   end
end

function main () 
  local file = arg[1]
  if not file or not lfs.isfile(file) then
     print ("no pascal web file given")
    return 
  end
  local webmodules = read_modules (file) 
  if not webmodules then
     print ("file loading failed")
     return
  end
  io.write('Input CWEB file '.. file .. '\n');
  local cfile = string.gsub(file,'.w$','.w.new')
  io.write('Output file '.. cfile .. '\n');
  local cweb = io.open(cfile,"w")
  if not cweb then
     print ("Cannot open output")
     return
   end

  local n = 0
  io.write('Interpreting ... ')
  for a,_ in pairs(webmodules) do
     webmodules[a] = disect_module(_)
     webmodules[a].nr = a-1 
     parse_module(webmodules[a])
     if webmodules[a].doc and #webmodules[a].doc and string.sub(webmodules[a].doc,1,3) == "\n@*" then
        io.write("*" .. tonumber(n))
      	io.flush()
     end
     n = n + 1;
  end

  io.write('\nWriting the output ... ')
  
  n = 0
  for a,_ in pairs(webmodules) do
     write_cweb(cweb,_)
     if _.doc and #_.doc and string.sub(_.doc,1,3) == "\n@*" then
        io.write("*" .. tonumber(n))
	    io.flush()
     end
     n = n + 1;
  end
  io.close(cweb)
  io.write('\nDone.\n');
end


main()