--[[ Builder for Nested Closures DSL (Builder03) Lair Configuration DSL Case Study H. Conrad Cunningham, Professor Computer and Information Science University of Mississippi See the comments in the Semantic Model Module. This Lua module is based on Fowler's builder program that uses the Nested Closures (403) DSL design pattern to implement the Lair Configuration DSL. Developed for CSCI 658, Software Language Engineering, Fall 2013 1234567890123456789012345678901234567890123456789012345678901234567890 2013-10-15: Adapted from from Fowler's builder3.rb and builder14.lua In the Nested Closures pattern, closures (i.e., unevaluated functions) are passed in the argument lists of function calls. (In Ruby, these are called "blocks" and are optionally attached to methods calls; inside the methods, these are an optional final parameter of the call.) The closures enable DSL element to be nested inside the closures, thus providing a natural structuring mechanism. The builder implementation then calls a closure with appropriate arguments as needed during the processing. --]] -- Load class support module local cs = require "class_support" -- Local names for functions in class_support module local makeClass, isInstanceOf, readOnly = cs.makeClass, cs.isInstanceOf, cs.readOnly -- Load semantic model module local sm = require "model" -- Define local names for convenience local Configuration, Item, S = sm.Configuration, sm.Item, sm.S local Resource, Electricity, Acid = sm.Resource, sm.Electricity, sm.Acid -- Variables "header_code" and "trailer_code" hold boilerplate code -- strings that are concatenated in front of and behind, respectively, -- the DSL script being executed by "execute_dsl". local header_code = [[ -- Load builder module local eb = require "builder03" -- Define local names for convenience local ConfigurationBuilder = eb.ConfigurationBuilder local Resources = eb.Resources local S = eb.S return -- a ConfigurationBuilder object ]] local trailer_code = "" -- Function "execute_dsl" loads a DSL script from a string, compiles -- it, executes it, and then returns the resulting Configuration -- object. The functions in this module use the context variables -- "config", "current_item", and "current_acid". local function execute_dsl(scriptFile) -- read dsl script file into string t local f = assert(io.open(scriptFile,"r")) local t = f:read("*a") f:close() -- combine with needed header, load, and execute script local script = header_code .. t .. trailer_code local dsl = loadstring(script) -- compiled in GLOBAL environment local result = dsl() return result:get_source() end -- CONCRETE CLASS ItemBuilder prototype local ItemBuilder = makeClass(S.ItemBuilder) function ItemBuilder:make(name,config) return ItemBuilder:new { _source = Item:make(name), _config = config } end function ItemBuilder:get_source() return self._source end function ItemBuilder:get_config() return self._config end function ItemBuilder:uses(arg,clo) if clo then clo(arg) end -- execute closure self:get_source():add_usage(arg) end function ItemBuilder:provides(arg) if clo then clo(arg) end -- execute closure self:get_source():add_provision(arg) end function ItemBuilder:depends_on(arg) self:get_source():add_dependency(self:get_config():get_item(arg)) end -- Define "showValues" method needed for default "toString" function ItemBuilder:showValues() return { self:get_source():toString(), self:get_config():toString() } end -- CONCRETE CLASS ConfigurationBuilder prototype local ConfigurationBuilder = makeClass(S.ConfigurationBuilder) function ConfigurationBuilder:make() return ConfigurationBuilder:new { _source = Configuration:make() } end function ConfigurationBuilder:get_source() return self._source end function ConfigurationBuilder:start(clo) local newObj = ConfigurationBuilder:make() clo(newObj) -- execute closure return newObj end function ConfigurationBuilder:item(name,clo) local i = ItemBuilder:make(name,self:get_source()) if clo then clo(i) end -- execute closure self:get_source():add_item(i:get_source()) end -- Define "showValues" method needed for default "toString" function ConfigurationBuilder:showValues() return { self:get_source():toString() } end -- CONCRETE CLASS Resources prototype -- Different from "Resource" class in model -- Used only to hold "class" methods in prototype object local Resources = makeClass(S.Resources) function Resources:make() return Resources:new {} end function Resources:electricity(power) return Electricity:make(power) end function Resources:acid() return Acid:make() end -- Define "showValues" method needed for default "toString" function Resources:showValues() return { } end -- MODULE EXPORT return { execute_dsl = execute_dsl, ConfigurationBuilder = ConfigurationBuilder, ItemBuilder = ItemBuilder, Resources = Resources, S = S }