aboutsummaryrefslogtreecommitdiff
path: root/test/unit/preprocess.moon
blob: cb734da2f753e4487eb4ca541a094814ae2424df (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
-- helps managing loading different headers into the LuaJIT ffi. Untested on
-- windows, will probably need quite a bit of adjustment to run there.

ffi = require("ffi")

ccs = {}

env_cc = os.getenv("CC")
if env_cc
  table.insert(ccs, {path: "/usr/bin/env #{env_cc}", type: "gcc"})

if ffi.os == "Windows"
  table.insert(ccs, {path: "cl", type: "msvc"})

table.insert(ccs, {path: "/usr/bin/env cc", type: "gcc"})
table.insert(ccs, {path: "/usr/bin/env gcc", type: "gcc"})
table.insert(ccs, {path: "/usr/bin/env gcc-4.9", type: "gcc"})
table.insert(ccs, {path: "/usr/bin/env gcc-4.8", type: "gcc"})
table.insert(ccs, {path: "/usr/bin/env gcc-4.7", type: "gcc"})
table.insert(ccs, {path: "/usr/bin/env clang", type: "clang"})
table.insert(ccs, {path: "/usr/bin/env icc", type: "gcc"})

quote_me = '[^%w%+%-%=%@%_%/]' -- complement (needn't quote)
shell_quote = (str) ->
  if string.find(str, quote_me) or str == '' then
    "'" .. string.gsub(str, "'", [['"'"']]) .. "'"
  else
    str

-- parse Makefile format dependencies into a Lua table
parse_make_deps = (deps) ->
  -- remove line breaks and line concatenators
  deps = deps\gsub("\n", "")\gsub("\\", "")

  -- remove the Makefile "target:" element
  deps = deps\gsub(".+:", "")

  -- remove redundant spaces
  deps = deps\gsub("  +", " ")

  -- split according to token (space in this case)
  headers = {}
  for token in deps\gmatch("[^%s]+")
    -- headers[token] = true
    headers[#headers + 1] = token

  -- resolve path redirections (..) to normalize all paths
  for i, v in ipairs(headers)
    -- double dots (..)
    headers[i] = v\gsub("/[^/%s]+/%.%.", "")

    -- single dot (.)
    headers[i] = v\gsub("%./", "")

  headers

-- will produce a string that represents a meta C header file that includes
-- all the passed in headers. I.e.:
--
-- headerize({"stdio.h", "math.h", true}
-- produces:
-- #include <stdio.h>
-- #include <math.h>
--
-- headerize({"vim.h", "memory.h", false}
-- produces:
-- #include "vim.h"
-- #include "memory.h"
headerize = (headers, global) ->
  pre = '"'
  post = pre
  if global
    pre = "<"
    post = ">"

  formatted = ["#include #{pre}#{hdr}#{post}" for hdr in *headers]
  table.concat(formatted, "\n")

class Gcc
  -- preprocessor flags that will hopefully make the compiler produce C
  -- declarations that the LuaJIT ffi understands.
  @@preprocessor_extra_flags = {
   '-D "aligned(ARGS)="',
   '-D "__attribute__(ARGS)="',
   '-D "__asm(ARGS)="',
   '-D "__asm__(ARGS)="',
   '-D "__inline__="',
   '-D "EXTERN=extern"',
   '-D "INIT(...)="',
   '-D_GNU_SOURCE',
   '-DINCLUDE_GENERATED_DECLARATIONS'
  }

  new: (path) =>
    @path = path

  add_to_include_path: (...) =>
      paths = {...}
      for path in *paths
          directive = '-I ' .. '"' .. path .. '"'
          @@preprocessor_extra_flags[#@@preprocessor_extra_flags + 1] = directive

  -- returns a list of the headers files upon which this file relies
  dependencies: (hdr) =>
    out = io.popen("#{@path} -M #{hdr} 2>&1")
    deps = out\read("*a")
    out\close!

    if deps
      parse_make_deps(deps)
    else
      nil

  -- returns a stream representing a preprocessed form of the passed-in
  -- headers. Don't forget to close the stream by calling the close() method
  -- on it.
  preprocess_stream: (...) =>
    paths = {...}
    -- create pseudo-header
    pseudoheader = headerize(paths, false)
    defines = table.concat(@@preprocessor_extra_flags, ' ')
    cmd = ("echo $hdr | #{@path} #{defines} -std=c99 -P -E -")\gsub('$hdr', shell_quote(pseudoheader))
    -- lfs = require("lfs")
    -- print("CWD: #{lfs.currentdir!}")
    -- print("CMD: #{cmd}")
    -- io.stderr\write("CWD: #{lfs.currentdir!}\n")
    -- io.stderr\write("CMD: #{cmd}\n")
    io.popen(cmd)

class Clang extends Gcc
class Msvc extends Gcc

type_to_class = {
  "gcc": Gcc,
  "clang": Clang,
  "msvc": Msvc
}

find_best_cc = (ccs) ->
  for _, meta in pairs(ccs)
    version = io.popen("#{meta.path} -v 2>&1")
    version\close!
    if version
      return type_to_class[meta.type](meta.path)
  nil

-- find the best cc. If os.exec causes problems on windows (like popping up
-- a console window) we might consider using something like this:
-- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec
cc = nil
if cc == nil
  cc = find_best_cc(ccs)

return {
  includes: (hdr) -> cc\dependencies(hdr)
  preprocess_stream: (...) -> cc\preprocess_stream(...)
  add_to_include_path: (...) -> cc\add_to_include_path(...)
}