Compiler/Preprocessor: Difference between revisions

Added Wren
m (Fix mangled task markup)
(Added Wren)
Line 249:
/* Use area, height, and width */
area = 6 * 5;
</pre>
 
=={{header|Wren}}==
{{libheader|Wren-ioutil}}
{{libheader|Wren-str}}
{{libheader|Wren-pattern}}
{{libheader|Wren-seq}}
A fairly naive solution compared to the complexities of a modern C pre-processor.
 
I've made the following simplifying assumptions:
 
1. Macro parameters in a macro definition will always be separated from other tokens by at least one space.
 
2. Macros will not occur in string literals, nor closing parentheses within macro argument strings.
<lang ecmascript>import "os" for Process
import "./ioutil" for FileUtil, File
import "./str" for Char
import "./pattern" for Pattern
import "./seq" for Lst, Stack
 
var isIdentChar = Fn.new { |c| Char.isAsciiAlphaNum(c) || c == "_" }
 
var isIdent = Fn.new { |s|
if (s == "") return false
if (Char.isDigit(s[0])) return false
return s.all { |c| isIdentChar.call(c) }
}
 
var clargs = Process.arguments
if (clargs.count != 1) {
System.print("Please pass exactly one command line argument, the file name to preprocess.")
return
}
var fileName = clargs[0]
var macros = []
var comments = []
var used = []
var includes = Stack.new()
var lines = FileUtil.readLines(fileName)
var i = 0
while (i < lines.count) {
var line = lines[i].trim()
if (line == "" || !line.startsWith("#")) {
i = i + 1
} else if (line.startsWith("#include")) {
var fname = line[8..-1].trimStart()
if (fname.count < 3 || fname[0] != "\"" || fname[-1] != "\"") {
Fiber.abort("'#include' directive must be followed by a non-empty string.")
}
var lines2 = FileUtil.readLines(fname[1..-2])
if (includes.count == 5) {
Fiber.abort("Can't have more than 5 active 'include' files.")
} else {
includes.push([fname, i + lines2.count - 1])
comments.add("/* Include Header %(fname) */")
}
lines = lines[0...i] + lines2 + lines[i+1..-1]
} else if (line.startsWith("#define")) {
line = line[7..-1].trimStart()
if (line == "") Fiber.abort("Missing macro name.")
var name = ""
var j = 0
while (j < line.count) {
var c = line[j]
if (isIdentChar.call(c)) name = name + c else break
j = j + 1
}
if (name == "") Fiber.abort("Missing macro name.")
if (!isIdent.call(name)) Fiber.abort("Macro name is not a valid identifier.")
if (macros.any { |macro| macro[0] == name }) Fiber.abort("Macro '%(name)' cannot be redefined.")
if (j == line.count) Fiber.abort("Missing macro definition.")
var paramStr = ""
var params = null
if (line[j] == "(") {
j = j + 1
var k = line.indexOf(")", j)
if (k == -1) Fiber.abort("Missing ')' in macro parameter list.")
if (k == j) {
params = []
} else {
paramStr = line[j...k]
params = paramStr.split(",")
params = params.map { |param| param.trim() }.toList
if (!params.all { |param| isIdent.call(param) }) {
Fiber.abort("Macro parameter is not a valid identifier.")
}
}
j = k + 1
}
if (j == line.count) Fiber.abort("Missing macro definition.")
var defn = line[j..-1].trimStart()
macros.add([name, params, defn])
if (params == null) {
comments.add("/* Define %(name) as %(defn) */")
} else {
comments.add("/* Define %(name)(%(params.toString[1...-1])) as %(defn) */")
}
lines.removeAt(i)
} else {
Fiber.abort("Unknown directive.")
}
while (includes.count > 0 && i >= includes.peek()[1]) {
comments.add("/* End %(includes.pop()[0]) */")
}
}
var src = lines.where { |line| line != "" }.join("\n")
for (macro in macros) {
var name = macro[0]
var params = macro[1]
var defn = macro[2]
var p
if (params == null) {
p = Pattern.new("/X[%(name)]~/X")
} else if (params.count == 0) {
p = Pattern.new("[/#%(name)()/#]")
} else if (params.count > 0) {
p = Pattern.new("[/#%(name)(+1^))/#]")
}
var m = null
while (m = p.find(src)) {
var span = m.captures[0].span
if (params == null || params.count == 0) {
src = src[0...span[0]] + defn + src[span[1]+1..-1]
used.add(name)
} else {
var argStr = m.captures[0].text
var ix1 = argStr.indexOf("(") + 1
var ix2 = argStr.indexOf(")") - 1
argStr = argStr[ix1..ix2]
var args = argStr.split(",")
if (args.count == params.count) {
var temp = " " + defn + " "
for (i in 0...args.count) {
temp = temp.replace(" " + params[i] + " ", " " + args[i].trim() + " ")
}
src = src[0...span[0]] + temp.trim() + src[span[1]+1..-1]
used.add(name)
}
}
}
}
while (includes.count > 0) {
comments.add("/* End %(includes.pop()[0]) */")
}
used = Lst.distinct(used)
if (used.count > 0) {
var temp = (used.count == 1) ? used[0] : used[0..-2].join(", ") + " and " + used[-1]
comments.add("/* Used %(temp) */")
}
comments = comments.join("\n")
 
// write to terminal
System.print(comments)
System.print(src)
 
// write to a file
File.create("output_from_" + fileName) { |file|
file.writeBytes(comments)
file.writeBytes("\n")
file.writeBytes(src)
file.writeBytes("\n")
}</lang>
 
{{out}}
Using the example files;
<pre>
/* Include Header "Header.h" */
/* Define area(h, w]) as h * w */
/* End "Header.h" */
/* Define width as 5 */
/* Define height as 6 */
/* Used area, width and height */
area = 6 * 5;
</pre>
 
Or adding another header file to make the example slightly more interesting:
<pre>
~~ Header.h ~~
#define area(h, w) h * w
#include "Header2.h"
 
~~ Header2.h ~~
#define depth 7
#define volume(h, w, d) h * w * d
 
~~ Source.t ~~
#include "Header.h"
#define width 5
#define height 6
area = #area(height, width)#;
volume = #volume(height, width, depth)#;
</pre>
{{out}}
<pre>
/* Include Header "Header.h" */
/* Define area(h, w) as h * w */
/* Include Header "Header2.h" */
/* Define depth as 7 */
/* Define volume(h, w, d) as h * w * d */
/* End "Header2.h" */
/* End "Header.h" */
/* Define width as 5 */
/* Define height as 6 */
/* Used area, depth, volume, width and height */
area = 6 * 5;
volume = 6 * 5 * 7;
</pre>
9,483

edits