--[[ Lair Language LPEG Parser (Buildelpeg1) Lair Configuration DSL Case Study H. Conrad Cunningham, Professor Computer and Information Science University of Mississippi Developed for CSCI 658, Software Language Engineering, Fall 2013 1234567890123456789012345678901234567890123456789012345678901234567890 2013-10-20: --]] -- 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". --[[ Builder for Expression Builder with Method Chaining (Builder14) 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 Expression Builder (343), Method Chaining (373), and Nested Function (357) DSL design patterns to implement the Lair Configuration DSL. Developed for CSCI 658, Software Language Engineering, Fall 2013 1234567890123456789012345678901234567890123456789012345678901234567890 2013-10-15: Adapted from from builder14.rb 2013-10-16: Added comment about Nested Function pattern To overcome the problem of modifying the Semantic Domain classes (or using global functions), this version constructs the DSL using a parallel set of Expression Builder classes. These classes construct the semantic domain objects. Although it usually takes more code, an Expression Builder gives the DSL designer more freedom in the choice of language elements. Note: The strings returned from the "toString" functions are not well formatted. --]] -- 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". b local header_code = [[ -- Load builder module local eb = require "builder14" -- Define local names for convenience local ConfigurationBuilder = eb.ConfigurationBuilder local Resources = eb.Resources local S = eb.S ]] 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 end -- CONCRETE CLASS ItemBuilder prototype local ItemBuilder = makeClass(S.ItemBuilder) function ItemBuilder:make(parent,arg) return ItemBuilder:new { _parent = parent, _subject = Item:make(arg) } end function ItemBuilder:get_parent() return self._parent end function ItemBuilder:get_subject() return self._subject end -- New "item" so bounce back to parent ConfigurationBuilder function ItemBuilder:item(arg) return self:get_parent():item(arg) end function ItemBuilder:provides(arg) self:get_subject():add_provision(arg:get_subject()) return self end function ItemBuilder:uses(arg) self:get_subject():add_usage(arg:get_subject()) return self end function ItemBuilder:configuration() return self:get_parent():get_subject() -- return Configuration end function ItemBuilder:depends_on(arg) self:get_subject(): add_dependency(self:configuration():get_item(arg)) return self end -- Define "showValues" method needed for default "toString" function ItemBuilder:showValues() return { self:get_parent():toString(), self:get_subject():toString() } end -- CONCRETE CLASS ConfigurationBuilder prototype local ConfigurationBuilder = makeClass(S.ConfigurationBuilder) function ConfigurationBuilder:make() return ConfigurationBuilder:new { _subject = Configuration:make() } end function ConfigurationBuilder:get_subject() return self._subject end -- Fowler's Ruby code had both a class and an instance method named -- "item". In the Lua class model used, there is no clear distinction -- between the two. So method "item" serves both roles. It is -- structured as an instance method, but it checks whether its first -- argument is the ConfigurationBuilder prototype object. If so, it -- behaves as a class method. Otherwise, it behaves as an instance -- method for an object of the class. function ConfigurationBuilder:item(arg) if self == ConfigurationBuilder then -- class method call local builder = ConfigurationBuilder:make() return builder:item(arg) else -- instance method call local result = ItemBuilder:make(self,arg) self:get_subject():add_item(result:get_subject()) return result -- returns ItemBuilder new ItemBuilder object end end -- Define "showValues" method needed for default "toString" function ConfigurationBuilder:showValues() return { self:get_subject():toString() } end -- CONCRETE CLASS AcidBuilder prototype local AcidBuilder = makeClass(S.AcidBuilder) function AcidBuilder:make() return AcidBuilder:new { _subject = Acid:make() } end function AcidBuilder:get_subject() return self._subject end function AcidBuilder:type(arg) self:get_subject():set_type(arg) return self end function AcidBuilder:grade(arg) self:get_subject():set_grade(arg) return self end -- Define "showValues" method needed for default "toString" function AcidBuilder:showValues() return { self:get_subject():toString() } end -- CONCRETE CLASS ElectricityBuilder prototype local ElectricityBuilder = makeClass(S.ElectricityBuilder) function ElectricityBuilder:make(power) return ElectricityBuilder:new { _subject = Electricity:make(power) } end function ElectricityBuilder:get_subject() return self._subject end -- Define "showValues" method needed for default "toString" function ElectricityBuilder:showValues() return { self:get_subject():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 ElectricityBuilder:make(power) end function Resources:acid() return AcidBuilder: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, ElectricityBuilder = ElectricityBuilder, AcidBuilder = AcidBuilder, S = S }