Skeleton of the rule engine and type system.
authorDavid Given <dg@cowlark.com>
Mon, 6 Jun 2016 18:50:48 +0000 (20:50 +0200)
committerDavid Given <dg@cowlark.com>
Mon, 6 Jun 2016 18:50:48 +0000 (20:50 +0200)
first/ackbuilder.lua

index 1ac3735..301078c 100644 (file)
@@ -8,6 +8,10 @@
 
 local environment = {}
 local rules = {}
+local targets = {}
+local buildfiles = {}
+local globals
+local cwd = "."
 
 local function subenv(old, cb)
        if not old then
@@ -49,37 +53,169 @@ local function emit(...)
        end
 end
 
-local function definerule(name, cb)
-       if rules[name] then
-               error(string.format("rule '%s' is already defined", name))
+local function loadbuildfile(filename)
+       local data, chunk, e
+       data = io.open(filename):read("*a")
+       if not e then
+               local thisglobals = {_G = thisglobals}
+               setmetatable(thisglobals, {__index = globals})
+               chunk, e = loadstring(data, filename, "text", thisglobals)
+       end
+       if e then
+               error(string.format("couldn't load '%s': %s", filename, e))
+       end
+
+       chunk()
+end
+
+local function loadtarget(targetname)
+       if targets[target] then
+               return targets[targetname]
+       end
+
+       local target
+       if not target:find(":") then
+               target = {
+                       outs = {targetname},
+                       is = {
+                               __implicitfile = true
+                       }
+               }
+               targets[targetname] = target
+       else
+               local _, _, filepart, targetpart = targetname:find("^([^:]+):(%w+)$")
+               if not filepart or not targetpart then
+                       error(string.format("malformed target name '%s'", targetname))
+               end
+               if not buildfiles[filepart] then
+                       buildfiles[filepart] = true
+
+                       local oldcwd = cwd
+                       cwd = filepart
+                       loadbuildfile(filepart.."/build.lua")
+                       cwd = oldcwd
+               end
+
+               target = targets[targetname]
+               if not target then
+                       error(string.format("build file '%s' contains no rule '%s'",
+                               filepart, targetpart))
+               end
+       end
+
+       return target
+end
+
+local typeconverters = {
+       targets = function(propname, i)
+               if (type(i) == "string") then
+                       i = {i}
+               elseif (type(i) ~= "table") then
+                       error(string.format("property '%s' must be a target list", propname))
+               end
+
+               local o = {}
+               for _, s in ipairs(i) do
+                       if (type(s) ~= "string") then
+                               error(string.format("member of target list '%s' is not a string", propname))
+                       end
+
+                       if s:find("^//") then
+                               s = s:gsub("^//", "")
+                       elseif s:find("^[/]") then
+                               s = concatpath(cwd, s)
+                       end
+                       o[#o+1] = s
+               end
+       end,
+
+       strings = function(propname, i)
+               if (type(i) == "string") then
+                       i = {i}
+               elseif (type(i) ~= "table") then
+                       error(string.format("property '%s' must be a string list", propname))
+               end
+
+               return i
+       end,
+
+       string = function(propname, i)
+               if (type(i) ~= "string") then
+                       error(string.format("property '%s' must be a string", propname))
+               end
+       end,
+}
+       
+local function definerule(rulename, types, cb)
+       if rules[rulename] then
+               error(string.format("rule '%s' is already defined", rulename))
+       end
+
+       types.name = { type="string" }
+
+       for propname, typespec in pairs(types) do
+               if not typeconverters[typespec.type] then
+                       error(string.format("property '%s' has unrecognised type '%s'",
+                               propname, typespec.type))
+               end
+       end
+
+       rules[rulename] = function(e)
+               local args = {}
+               for propname, typespec in pairs(types) do
+                       if not e[propname] and not typespec.optional then
+                               error(string.format("missing mandatory property '%s'", propname))
+                       end
+
+                       args[propname] = typeconverters[typespec.type](propname, e[propname])
+                       e[propname] = nil
+               end
+
+               local propname, _ = next(e)
+               if propname then
+                       error(string.format("don't know what to do with property '%s'", propname))
+               end
        end
 end
 
+-----------------------------------------------------------------------------
+--                              DEFAULT RULES                              --
+-----------------------------------------------------------------------------
+
+definerule("simplerule",
+       {
+               ins = { type="targets" },
+               outs = { type="strings" },
+       },
+       function (e)
+       end
+)
+
 -----------------------------------------------------------------------------
 --                               MAIN PROGRAM                              --
 -----------------------------------------------------------------------------
 
-local globals = {
+globals = {
        asstring = asstring,
        definerule = definerule,
        emit = emit,
        environment = environment,
 }
-setmetatable(globals, {__index = _G})
+setmetatable(globals,
+       {
+               __index = function(self, k)
+                       local rule = rules[k]
+                       if rule then
+                               return rule
+                       else
+                               return _G[k]
+                       end
+               end
+       }
+)
 
 emit("hide=@\n")
 for _, file in ipairs({...}) do
-       local data, chunk, e
-       data = io.open(file):read("*a")
-       if not e then
-               local thisglobals = {_G = thisglobals}
-               setmetatable(thisglobals, {__index = globals})
-               chunk, e = loadstring(data, file, "text", thisglobals)
-       end
-       if e then
-               error(string.format("couldn't load '%s': %s", file, e))
-       end
-
-       local _, e = pcall(chunk)
+       loadbuildfile(file)
 end