TDP007 — Konstruktion av datorspråk — Institutionen för datavetenskap, Linköpings universitet Ola Leifler & Peter Dalenius {olale,petda}@ida.liu.se Acknowledgments Much material from these slides comes from Brian Amberg at the university of Freiburg (http://ruby.brian-amberg.de/course/ ). Ola Leifler TDDB27 Licence c Original version copyright 2004-2006 Brian Schroeder. TDP007 course copyright c 2007-2008 Ola Leifler. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". Part I Domain-specific languages Ola Leifler TDDB27 Overview ◮ What? Language for a small domain ◮ Why? Efficient representation ◮ When? Awkward to write/read Ola Leifler TDDB27 Overview ◮ What? Language for a small domain ◮ Why? Efficient representation ◮ When? Awkward to write/read How? Using Ruby of course! Ola Leifler TDDB27 Custom dispatch catching errors ◮ const_missing When constants (names) cannot resolve to references, this method is used ◮ method_missing when undefined methods are called, method_missing is used Ola Leifler TDDB27 Configuration of a gearbox Consider the following gearbox configuration: 1 /ω 1 ωin ut 2 /ω 2 ωin ut ... Ola Leifler = = 33/15 28/20 TDDB27 In Ruby class Configuration 5 class Configuration 6 7 8 9 10 11 12 attr_reader :logger def initialize(filename="config.rb") @filename=filename @logger=Logger.new(STDOUT) instance_eval(File.new(filename).read()) end 13 14 15 16 17 18 19 20 21 def method_missing(method_name,arg) @@var="#{method_name}" class << self class_eval { attr_accessor(@@var) } end logger.debug "added accessor #{method_name}" instance_eval("self.#{method_name}=#{arg}") end 22 23 end Ola Leifler TDDB27 The gearbox configuration 1 2 3 # F&#246;rh&#229;llandet mellan mellanaxeln och drivaxeln z_ut 27.0 z_in 19.0 4 5 6 7 8 # 1:ans v&#228;xel z1_ut 33.0 z1_in 15.0 z1_ratio (z1_ut/z1_in)*(z_ut/z_in) 9 10 11 12 13 # 2:ans v&#228;xel z2_ut 28.0 z2_in 20.0 z2_ratio (z2_ut/z2_in)*(z_ut/z_in) 14 15 16 17 18 # 3:ans v&#228;xel z3_ut 23.0 z3_in 24.0 z3_ratio (z3_ut/z3_in)*(z_ut/z_in) 19 20 21 22 23 # 4:ans v&#228;xel z4_ut 19.0 z4_in 27.0 z4_ratio (z4_ut/z4_in)*(z_ut/z_in) Ola Leifler TDDB27 Claimant class Consider the following example: 3 4 5 6 7 8 9 class Claimant def initialize(name, gender, age) @name = name @gender = gender @age = age @benefits = 0 end 10 11 attr_reader :name,:gender,:age,:benefits 12 13 14 15 def retired? @gender == :male && @age >= 65 || @gender == :female && @age >= 60 end 16 17 18 19 def to_s @name end 20 21 22 23 def display print "#{@name}\t#{gender}\t#{age}\n" end Ola Leifler TDDB27 claimants Now we add some claimants: 46 47 48 49 alice = Claimant.new("Alice", :female, 50) bill = Claimant.new("Bill", :male, 40) carol = Claimant.new("Carol", :female, 63) dave = Claimant.new("Dave", :male, 70) 50 51 claimants = [alice, bill, carol, dave] Ola Leifler TDDB27 Now, some queries Query examples: 54 55 56 57 58 59 60 puts "All claimants:" All claimants: » nil claimants.each { |c| c.display } Alice female 50 Bill male 40 Carol female 63 Dave male 70 » [#<Claimant:0x108d65c @age=50, @gender=:female, @benefits=0, @name=" 61 62 63 64 65 66 67 puts » nil puts "Retired:" Retired: » nil (claimants.select { |c| c.retired?}).each { |c| c.display } Carol female 63 Dave male 70 » [#<Claimant:0x108798c @age=63, @gender=:female, @benefits=0, @name=" 68 69 70 71 72 73 74 puts » nil puts "Men:" Men: » nil (claimants.select { |c| c.gender == :male}).each { |c| c.display } Bill male 40 Dave male 70 » [#<Claimant:0x108a894 @age=40, @gender=:male, @benefits=0, @name="B 75 Ola Leifler TDDB27 Some more queries Query examples: 76 77 78 79 80 81 puts » nil puts "Women:" Women: » nil (claimants.select { |c| c.gender == :female}).each { |c| c.display } Alice female 50 Carol female 63 » [#<Claimant:0x108d65c @age=50, @gender=:female, @benefits=0, @name=" 82 83 84 85 86 87 88 puts » nil puts "Not retired:" Not retired: » nil (claimants.select { |c| not c.retired?}).each { |c| c.display } Alice female 50 Bill male 40 » [#<Claimant:0x108d65c @age=50, @gender=:female, @benefits=0, @name="A 89 90 91 92 93 94 puts » nil puts "Retired men:" Retired men: » nil ((claimants.select { |c| c.gender == :male}).select { |c| c.retired?}).each { |c| c.display } Dave male 70 » [#<Claimant:0x1084bd8 @age=70, @gender=:male, @benefits=0, @name="D Ola Leifler TDDB27 Same queries, now rewritten using HOM Query examples using higher-order message passing: 96 97 98 99 100 101 102 puts "All claimants:" All claimants: » nil claimants.do.display Alice female 50 Bill male 40 Carol female 63 Dave male 70 » nil 103 104 105 106 107 108 109 puts » nil puts "Retired:" Retired: » nil claimants.that.are.retired?.do.display Carol female 63 Dave male 70 » nil 110 111 112 113 114 115 116 puts » nil puts "Men:" Men: » nil (claimants.that.have.gender == :male).do.display Bill male 40 Dave male 70 » nil Ola Leifler TDDB27 Same queries, now rewritten using HOM Query examples using higher-order message passing: 117 118 119 120 121 122 123 puts » nil puts "Women:" Women: » nil (claimants.that.have.gender == :female).do.display Alice female 50 Carol female 63 » nil 124 125 126 127 128 129 130 puts » nil puts "Not retired:" Not retired: » nil claimants.that.are_not.retired?.do.display Alice female 50 Bill male 40 » nil 131 132 133 134 135 136 puts » nil puts "Retired men:" Retired men: » nil (claimants.that.have.gender == :male).that.are.retired?.do.display Dave male 70 » nil Ola Leifler TDDB27 Higher-order message passing We extend Enumerable 132 133 134 135 module Enumerable def do return HOM::Do.new(self) end 136 137 138 139 def that return HOM::That.new(self) end 140 141 142 143 def all return HOM::All.new(self) end 144 145 146 147 def any return HOM::Any.new(self) end Ola Leifler TDDB27 Higher-order message passing We create wrapper classes That, All, Any 105 106 107 108 109 class That < Collator def apply(&block) return @receiver.select(&block) end end 110 111 112 113 114 115 class All < Collator def apply(&block) return @receiver.all?(&block) end end 116 117 118 119 120 121 class Any < Collator def apply(&block) return @receiver.any?(&block) end end Ola Leifler TDDB27 Higher-order message passing Such objects respond to messages are, are_not and have 87 88 89 90 class Collator def initialize(receiver) @receiver = receiver end 91 def are return HOM::Are.new(self) end 92 93 94 95 def are_not return HOM::AreNot.new(self) end 96 97 98 99 100 101 102 103 def have return HOM::Have.new(self) end end Ola Leifler TDDB27 Higher-order message passing Selectors Are, AreNot, and Have 27 28 29 30 31 class Are < HigherOrderMessage def method_missing(id, *args) return @handler.apply {|e| e.__send__(id,*args)} end end 32 33 34 35 36 37 class AreNot < HigherOrderMessage def method_missing(id, *args) return @handler.apply {|e| not e.__send__(id,*args)} end end 38 39 40 41 42 43 class Have < HigherOrderMessage def method_missing(id, *args) return ResultMatcher.new(@handler,id,args) end end Ola Leifler TDDB27 Logic programming example Example of logic programming as a DSL 1 2 3 + auckland.by_car_to(hamilton) + hamilton.by_car_to(raglan) + valmont.by_car_to(metz) 4 5 + metz.by_train_to(paris) 6 7 8 + paris.by_plane_to(los_angeles) + los_angeles.by_plane_to(auckland) 9 10 11 12 + X.direct_to(Y).if { X.by_car_to(Y) } + X.direct_to(Y).if { X.by_train_to(Y) } + X.direct_to(Y).if { X.by_plane_to(Y) } 13 14 15 + X.travel_to(Y).if { X.direct_to(Y) } + X.travel_to(Y).if { X.direct_to(Z) & Z.travel_to(Y) } 16 17 18 19 + X.direct_to(Y,H).if { X.by_car_to(Y) & H.bind_to(car) } + X.direct_to(Y,H).if { X.by_train_to(Y) & H.bind_to(train) } + X.direct_to(Y,H).if { X.by_plane_to(Y) & H.bind_to(plane) } 20 21 22 + X.travel_to(Y,P).if { X.direct_to(Y,H) & P.bind_to(H.go(X,Y)) } + X.travel_to(Y,P).if { X.direct_to(Z,H) & Z.travel_to(Y,P1) & P.bind_to(H.go(X,Z,P1)) } Ola Leifler TDDB27 Part II Language Parsing Ola Leifler TDDB27 Grammar Example grammar for arithmetic language expression = ["+"|"-"] term {("+"|"-") term} . term = factor {("*"|"/") factor} . factor = ident | number | "(" expression ")" . Grammar Ola Leifler TDDB27 Example Parsing an arithmetic expression 1 Ola Leifler + ( 2 * 3 ) TDDB27 Dice Language 1 class DiceRoller 2 3 4 5 6 7 def initialize @diceParser = Parser.new("dice roller") do token(/\s+/) token(/\d+/) {|m| m.to_i } token(/./) {|m| m } 8 9 10 11 12 13 start :expr do match(:expr, ’+’, :term) {|a, _, b| a + b } match(:expr, ’-’, :term) {|a, _, b| a - b } match(:term) end 14 15 16 17 18 19 Ola Leifler rule :term do match(:term, ’*’, :dice) {|a, _, b| a * b } match(:term, ’/’, :dice) {|a, _, b| a / b } match(:dice) end TDDB27 GNU Free Documentation License The GNU Free Documentation License as applicable to this document can be found at: http://www.gnu.org/copyleft/fdl.html Ola Leifler TDDB27