--[[ Builder Using Global Function Sequence Pattern (Builder08) 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 for the Global Function implementation of the Lair Configuration DSL. This DSL uses Fowler's Function Sequence (351) DSL design pattern overall. The implementation of this builder also uses Fowler's Context Variable (175) pattern. Developed for CSci 658, Software Language Engineering, Fall 2013 1234567890123456789012345678901234567890123456789012345678901234567890 2013-10-15: Adapted from from builder8.rb This module defines Lua global functions and variables, so it does not have an explicit export return statement. According to the Function Sequence DSL design pattern, the functions in this module (except driver function "execute_dsl") define the vocabulary for the DSL. For example, function "item" declares a new item and "uses" specifies that the item uses some resource. The sequence of the calls in the DSL script is important. The "uses" and "provides" calls associate their argument resources with the most recently declared item. The context variable "current_item" always denotes that item. Similarly, the function "acid" declares a new acid and functions "acid_type" and acid_grade" define the type and grade, respectively, of the most recently defined acid. The context variable "current_acid" always denotes that item. Sequencing works well for hierarchical relationships, but it does not work well for nonhierarchical relationships. Consider "depends". It associates its "supplier" argument with the current item before it in the sequence. However, that argument is not defined in the sequence of calls immediately following the depends; it is defined elsewhere in the sequence--perhaps before or perhaps after. So we give its name explicitly. A key difference between an internal DSL and the simple use of a standard API is that the combination of calls in an internal DSL needs to provide a smoothly flowing, linguistic fluency rather than just being just a sequence of method calls. Note: This module uses dynamic compilation and execution of Lua code using the "loadstring" method. An alternative would be to embed the DSL script within a separate module with appropriate header and trailer code and load the module to execute it. Note: The use of global functions makes a DSL implementation difficult to manage as the vocabulary grows larger or more than one DSL is desired. In Lua, this could be mitigated somewhat by using modules to encapsulate the functions and context variables needed. --]] -- 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 = [[ local S = setmetatable({},{ __index = function(t,k) return k end }) ]] 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". 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() if not result then result = end_configuration() end return result end -- "Global" functions and variables for vocabulary of DSL -- Function "configuration" creates a new Configuration object for use -- by the other functions in this module. If the DSL script does not -- begin with a call to this function, then item() calls it. function configuration() config = Configuration:make() end -- Function "item" creates a new Item with identifier "name" and -- stores it in context variable "current_item" to enable the other -- functions to reference the item being constructed. The new item is -- also added to the configuration in the context variable -- "config". (If there is no current configuration, it creates one.) function item(name) if not config then configuration() end current_item = Item:make(name) config:add_item(current_item) end -- Function "uses" declares that the "current_item" uses the argument -- "resource". function uses(resource) current_item:add_usage(resource) end -- Function "acid" creates a new Acid resource and stores it in the -- context variable "current_acid". function acid() current_acid = Acid:make() return current_acid end -- Function "acid_type" declares that the type of the "current_acid" -- is argument "type". function acid_type(type) current_acid:set_type(type) end -- Function "acid_grade" declares that the grade of the "current_acid" -- is argument "grade". function acid_grade(grade) current_acid:set_grade(grade) end -- Function "provides" declares that the "current_item" provides the -- argument "resource". function provides(resource) current_item:add_provision(resource) end -- Function "depends" declares that the "current item" depends upon -- the argument "supplier" item. function depends(supplier) current_item:add_dependency(config:get_item(supplier)) end -- Function "electricity" declares a new Electricity resource. function electricity(power) return Electricity:make(power) end -- Function "end_configuration" returns the Configuration built and -- resets the context variables to the beginning state. This can be -- used directly if "execute_dsl" is not used. function end_configuration() local result = config config, current_item, current_acid = nil, nil, nil return result end