summaryrefslogtreecommitdiff
path: root/lib/lace/compiler.lua
blob: bd5767162f9ff9faed61f297041cc0588d385b48 (plain)
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
-- lib/lace/compiler.lua
--
-- Lua Access Control Engine - Ruleset compiler
--
-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org>
--
-- For licence terms, see COPYING
--

local lex = require "lace.lex"
local builtin = require "lace.builtin"

local function _error(str, words)
   return false, { msg = str, words = words }
end

local function _fake_loader(ctx, name)
   return _error("Ruleset not found: " .. name, {1})
end

local function _fake_command(ctx)
   return _error("Command is disabled by context")
end

local function _loader(ctx)
   -- We know the context is a table with a .lace table, so retrieve
   -- the loader function.  If it's absent, return a loader which
   -- fails due to no loader being present.
   return ctx[".lace"].loader or _fake_loader
end

local function _command(ctx, name)
   -- While we know .lace is present, there's no guarantee they added
   -- any commands
   local cmdtab = ctx[".lace"].commands or {}
   local cfn = cmdtab[name]
   if cfn == nil then
      cfn = builtin.commands[name]
   elseif cfn == false then
      cfn = _fake_command
   end
   return cfn
end

local function _normalise_error(ctx, err)
   -- For now, just return the string
   return err.msg
end

local function _setposition(context, ruleset, linenr)
   context[".lace"].source = (ruleset or {}).content
   context[".lace"].linenr = linenr
end

local function compile_one_line(compcontext, line)
   -- The line is a rule, so we don't need to think about that.  The
   -- first entry is a command.
   local cmdname = line.content[1].str
   local cmdfn = _command(compcontext, cmdname)
   if type(cmdfn) ~= "function" then
      return _error("Unknown command: " .. cmdname, {1})
   end

   local args = {}
   for i = 1, #line.content do
      args[i] = line.content[i].str
   end

   return cmdfn(compcontext, unpack(args))
end

local function internal_compile_ruleset(compcontext, sourcename, content)
   assert(type(compcontext) == "table", "Compilation context must be a table")
   assert(type(compcontext[".lace"]) == "table", "Compilation context must contain a .lace table")
   assert(type(sourcename) == "string", "Source name must be a string")
   assert(content == nil or type(content) == "string", "Content must be nil or a string")
   if not content then
      -- No content supplied, try and load it.
      sourcename, content = _loader(compcontext)(compcontext, sourcename)
      if type(sourcename) ~= "string" then
	 return false, _normalise_error(compcontext, content)
      end
   end

   -- We have some content, let's lex it.
   -- We cannot fail to lex a string, it's not possible in our API
   local lexed_content = lex.string(content, sourcename)

   -- Now define the basis ruleset
   local ruleset = {
      content = lexed_content,
      rules = {},
   }
   
   for i = 1, #lexed_content.lines do
      local line = lexed_content.lines[i]
      if line.type == "rule" then
	 -- worth trying to parse a rule
	 _setposition(compcontext, ruleset, i)
	 local rule, msg = compile_one_line(compcontext, line)
	 if type(rule) ~= "table" then
	    return rule, (rule == nil) and msg or _normalise_error(compcontext, msg)
	 end
	 rule.linenr = i
	 ruleset.rules[#ruleset.rules+1] = rule
      end
   end

   _setposition(compcontext)
   return ruleset
end

local function compile_ruleset(ctx, src, cnt)
   local ok, ret, msg = xpcall(function() 
				  return internal_compile_ruleset(ctx, src, cnt) 
			       end, debug.traceback)
   if not ok then
      return nil, ret
   end
   return ret, msg
end

return {
   compile = compile_ruleset,
   error = _error,
}