--[[ Builder for Class Method Function Sequence (Builder11) 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 Class Method Function Sequence implementation of the Lair Configuration DSL. The implementation of this builder also uses Fowler's Method Chaining (373) and Nested Function (357) DSL design patterns. Developed for CSCI 658, Software Language Engineering, Fall 2013 1234567890123456789012345678901234567890123456789012345678901234567890 2013-10-15: Adapted from from builder11.rb 2013-10-16: Added comments about Nested Function pattern This module applies the Function Sequence pattern similarly to the global function version, except that the various vocabulary functions and context variables are class methods and constants of the various classes in the Semantic Model. The Method Chaining pattern means that methods are designed to be applied in long, but fluent, chains of method calls. To enable method chaining, most method must return values. Procedure methods usually return the "self" object. The Nested Function pattern means that function calls appear as arguments of other function calls in the DSL. In eagerly evaluated languages (such as Lua and most other languages we use), the arguments of a function call are evaluated before the body of the function is executed. This feature can be exploited in building a hierarchy of objects. Calls in the argument list can build the needed lower level objects. The disadvantage of this Class Method version is that the actual Semantic Domain classes must be modified to add the DSL vocabulary methods. Note: The "toString" method for Configuration has not been modified to return the value of the new attribute added to its prototype object. --]] -- Load class support module local cs = require "class_support" -- 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 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 ]] 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 Configuration.getCurrent() end -- Augment the Semantic Domain classes to add the DSL vocabulary and -- context variables. -- Add class methods "load", "item", and "getCurrent" and class -- variable "current" to Configuration. (These are methods and -- attributes of the prototype object for class Configuration.) function Configuration.load() Configuration.current = Configuration:make() end function Configuration.item(arg) if not Configuration.current then Configuration.load() end local new_item = Item:make(arg) Configuration.current:add_item(new_item) return new_item -- for method chaining end function Configuration.getCurrent() return Configuration.current end -- Add instance methods "uses", "provides" and "depends_on" to class Item. function Item:uses(arg) self:add_usage(arg) return self -- for method chaining end function Item:provides(arg) self:add_provision(arg) return self -- for method chaining end function Item:depends_on(arg) self:add_dependency(Configuration.getCurrent():get_item(arg)) return self -- for method chaining end -- Add class methods "electricity" and "acid" to Resource. function Resource.electricity(power) return Electricity:make(power) end function Resource.acid() return Acid:make() end -- REDEFINE existing Acid objects' setter methods "set_type" and -- "set_grade" to return "self" and thus enable method chaining. -- These methods only add the return of "self" to the existing -- "set_type" and "set_grade" methods, so I do not add code to save -- and restore old definitions. In general, one should save and -- restore the old method definitions. function Acid.set_type(self,arg) self._type = arg return self -- for method chaining end function Acid.set_grade(self,arg) self._grade = arg return self -- for method chaining end -- MODULE EXPORT return { execute_dsl = execute_dsl }