scottkit 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
 - data/GPL-2 +339 -0
 - data/Makefile +5 -0
 - data/README +75 -0
 - data/Rakefile +58 -0
 - data/VERSION +1 -0
 - data/bin/scottkit +96 -0
 - data/bin/scottkit.rb +96 -0
 - data/data/.gitignore +1 -0
 - data/data/adams/.gitignore +2 -0
 - data/data/adams/AdamsGames.zip +0 -0
 - data/data/adams/Makefile +18 -0
 - data/data/crystal/crystal.map +112 -0
 - data/data/crystal/crystal.sck +598 -0
 - data/data/crystal/crystal.solution +82 -0
 - data/data/dan-and-matt.sck +180 -0
 - data/data/dan-and-matt.solution +32 -0
 - data/data/howarth/.gitignore +2 -0
 - data/data/howarth/Makefile +14 -0
 - data/data/howarth/mysterious.tar.gz +0 -0
 - data/data/test/Makefile +18 -0
 - data/data/test/adams/Makefile +13 -0
 - data/data/test/adams/adv01.solution +186 -0
 - data/data/test/adams/adv01.transcript +869 -0
 - data/data/test/adams/adv01.transcript.md5 +1 -0
 - data/data/test/adams/adv02.solution +225 -0
 - data/data/test/adams/adv02.transcript +970 -0
 - data/data/test/adams/adv02.transcript.md5 +1 -0
 - data/data/test/adams/adv04.solution +187 -0
 - data/data/test/adams/adv04.transcript +876 -0
 - data/data/test/adams/adv04.transcript.md5 +1 -0
 - data/data/test/crystal.decompile +628 -0
 - data/data/test/crystal.sao +373 -0
 - data/data/test/crystal.save-file +62 -0
 - data/data/test/crystal.save-script +41 -0
 - data/data/test/crystal.sck +598 -0
 - data/data/test/crystal.solution +82 -0
 - data/data/test/crystal.transcript +413 -0
 - data/data/test/t6.pretty-print +225 -0
 - data/data/test/t7.sao +110 -0
 - data/data/test/t7.solution +28 -0
 - data/data/test/t7.transcript +147 -0
 - data/data/tutorial/t1.sck +5 -0
 - data/data/tutorial/t2.sck +14 -0
 - data/data/tutorial/t3.sck +38 -0
 - data/data/tutorial/t4.sck +62 -0
 - data/data/tutorial/t5.sck +87 -0
 - data/data/tutorial/t6.sck +119 -0
 - data/data/tutorial/t7.sck +135 -0
 - data/lib/scottkit/compile.rb +661 -0
 - data/lib/scottkit/decompile.rb +175 -0
 - data/lib/scottkit/game.rb +409 -0
 - data/lib/scottkit/play.rb +474 -0
 - data/notes/Definition +147 -0
 - data/notes/Definition.saved-game +9 -0
 - data/notes/Definition.scottfree-1.14 +142 -0
 - data/notes/adventureland-maze +20 -0
 - data/notes/continue-action +51 -0
 - data/test/test_canonicalise.rb +94 -0
 - data/test/test_compile.rb +54 -0
 - data/test/test_decompile.rb +13 -0
 - data/test/test_play.rb +20 -0
 - data/test/test_playadams.rb +36 -0
 - data/test/test_save.rb +31 -0
 - data/test/withio.rb +15 -0
 - data/test/withio_test.rb +31 -0
 - metadata +118 -0
 
| 
         @@ -0,0 +1,661 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # -*- coding: utf-8 -*-
         
     | 
| 
      
 2 
     | 
    
         
            +
            # Test with: cd /usr/local/src/mike/scott/scottkit && ruby1.9 -I lib bin/scottkit -c data/tutorial/t7.sck
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require 'pp'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module ScottKit
         
     | 
| 
      
 7 
     | 
    
         
            +
              class Game
         
     | 
| 
      
 8 
     | 
    
         
            +
                class Compiler
         
     | 
| 
      
 9 
     | 
    
         
            +
                  private
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  # Creates a new compiler for the specified game, set up ready to
         
     | 
| 
      
 12 
     | 
    
         
            +
                  # compile the game in the specified file.
         
     | 
| 
      
 13 
     | 
    
         
            +
                  #
         
     | 
| 
      
 14 
     | 
    
         
            +
                  # The input file may be specified either as a filename or a
         
     | 
| 
      
 15 
     | 
    
         
            +
                  # filehandle, or both.  If both are given, then the filename is
         
     | 
| 
      
 16 
     | 
    
         
            +
                  # used only in reporting to help locate errors.  _Some_ value
         
     | 
| 
      
 17 
     | 
    
         
            +
                  # must be given for the filename: an empty string is OK.
         
     | 
| 
      
 18 
     | 
    
         
            +
                  #
         
     | 
| 
      
 19 
     | 
    
         
            +
                  # (In case you're wondering, the main reason this has to be
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # passed a Game object is that the behaviour of compile is
         
     | 
| 
      
 21 
     | 
    
         
            +
                  # influenced by the game's options.)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  #
         
     | 
| 
      
 23 
     | 
    
         
            +
                  def initialize(game, filename, fh = nil)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @game = game
         
     | 
| 
      
 25 
     | 
    
         
            +
                    @lexer = Lexer.new(game, filename, fh)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  # Compiles the specified game-source file, writing the resulting
         
     | 
| 
      
 29 
     | 
    
         
            +
                  # object file to stdout, whence it should be redirected into a
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # file so that it can be played.  Yes, this API is sucky: it
         
     | 
| 
      
 31 
     | 
    
         
            +
                  # would be better if we had a simple compile method that builds
         
     | 
| 
      
 32 
     | 
    
         
            +
                  # the game in memory in a form that can by played, and which can
         
     | 
| 
      
 33 
     | 
    
         
            +
                  # then also be saved as an object file by some other method --
         
     | 
| 
      
 34 
     | 
    
         
            +
                  # but that would have been more work for little gain.
         
     | 
| 
      
 35 
     | 
    
         
            +
                  #
         
     | 
| 
      
 36 
     | 
    
         
            +
                  def compile_to_stdout
         
     | 
| 
      
 37 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 38 
     | 
    
         
            +
                      tree = parse
         
     | 
| 
      
 39 
     | 
    
         
            +
                      generate_code(tree)
         
     | 
| 
      
 40 
     | 
    
         
            +
                      true
         
     | 
| 
      
 41 
     | 
    
         
            +
                    rescue
         
     | 
| 
      
 42 
     | 
    
         
            +
                      return false if String($!) == "syntax error"
         
     | 
| 
      
 43 
     | 
    
         
            +
                      raise
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  def parse
         
     | 
| 
      
 48 
     | 
    
         
            +
                    ident, version, unknown1, unknown2, start, treasury, maxload,
         
     | 
| 
      
 49 
     | 
    
         
            +
                    wordlen, lighttime, lightsource =
         
     | 
| 
      
 50 
     | 
    
         
            +
                      nil, nil, nil, nil, nil, nil, nil, nil, nil, nil
         
     | 
| 
      
 51 
     | 
    
         
            +
                    rooms = [ CRoom.new(nil, nil, {}) ]
         
     | 
| 
      
 52 
     | 
    
         
            +
                    items, actions, verbgroups, noungroups = [], [], [], []
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                    while peek != :eof
         
     | 
| 
      
 55 
     | 
    
         
            +
                      case peek
         
     | 
| 
      
 56 
     | 
    
         
            +
                      when :ident then skip; ident = match :symbol
         
     | 
| 
      
 57 
     | 
    
         
            +
                      when :version then skip; version = match :symbol
         
     | 
| 
      
 58 
     | 
    
         
            +
                      when :unknown1 then skip; unknown1 = match :symbol
         
     | 
| 
      
 59 
     | 
    
         
            +
                      when :unknown2 then skip; unknown2 = match :symbol
         
     | 
| 
      
 60 
     | 
    
         
            +
                      when :start then skip; start = match :symbol
         
     | 
| 
      
 61 
     | 
    
         
            +
                      when :treasury then skip; treasury = match :symbol
         
     | 
| 
      
 62 
     | 
    
         
            +
                      when :maxload then skip; maxload = match :symbol
         
     | 
| 
      
 63 
     | 
    
         
            +
                      when :wordlen then skip; wordlen = match :symbol
         
     | 
| 
      
 64 
     | 
    
         
            +
                      when :lighttime then skip; lighttime = match :symbol
         
     | 
| 
      
 65 
     | 
    
         
            +
                      when :lightsource then skip; lightsource = match :symbol
         
     | 
| 
      
 66 
     | 
    
         
            +
                      when :room then rooms << parse_room
         
     | 
| 
      
 67 
     | 
    
         
            +
                      when :item then items << parse_item(rooms.size-1)
         
     | 
| 
      
 68 
     | 
    
         
            +
                      when :action then actions << parse_action
         
     | 
| 
      
 69 
     | 
    
         
            +
                      when :occur then actions << parse_occur
         
     | 
| 
      
 70 
     | 
    
         
            +
                      when :verbgroup then verbgroups << parse_wordgroup
         
     | 
| 
      
 71 
     | 
    
         
            +
                      when :noungroup then noungroups << parse_wordgroup
         
     | 
| 
      
 72 
     | 
    
         
            +
                      else match nil, "new directive"
         
     | 
| 
      
 73 
     | 
    
         
            +
                      end
         
     | 
| 
      
 74 
     | 
    
         
            +
                    end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                    if peek != :eof
         
     | 
| 
      
 77 
     | 
    
         
            +
                      error "additional text remains after parsing"
         
     | 
| 
      
 78 
     | 
    
         
            +
                    end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                    CGame.new(ident, version, unknown1, unknown2, start, treasury,
         
     | 
| 
      
 81 
     | 
    
         
            +
                              maxload, wordlen, lighttime, lightsource, rooms,
         
     | 
| 
      
 82 
     | 
    
         
            +
                              items, actions, verbgroups, noungroups)
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  def parse_room
         
     | 
| 
      
 86 
     | 
    
         
            +
                    match :room
         
     | 
| 
      
 87 
     | 
    
         
            +
                    name = match :symbol
         
     | 
| 
      
 88 
     | 
    
         
            +
                    desc = match :symbol
         
     | 
| 
      
 89 
     | 
    
         
            +
                    exits = {}
         
     | 
| 
      
 90 
     | 
    
         
            +
                    while peek == :exit
         
     | 
| 
      
 91 
     | 
    
         
            +
                      match :exit
         
     | 
| 
      
 92 
     | 
    
         
            +
                      direction = match :direction
         
     | 
| 
      
 93 
     | 
    
         
            +
                      dest = match :symbol
         
     | 
| 
      
 94 
     | 
    
         
            +
                      exits[direction] = dest
         
     | 
| 
      
 95 
     | 
    
         
            +
                    end
         
     | 
| 
      
 96 
     | 
    
         
            +
                    CRoom.new(name, desc, exits)
         
     | 
| 
      
 97 
     | 
    
         
            +
                  end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                  def parse_item(last_room)
         
     | 
| 
      
 100 
     | 
    
         
            +
                    match :item
         
     | 
| 
      
 101 
     | 
    
         
            +
                    name = match :symbol
         
     | 
| 
      
 102 
     | 
    
         
            +
                    desc = match :symbol
         
     | 
| 
      
 103 
     | 
    
         
            +
                    called, where = nil, nil
         
     | 
| 
      
 104 
     | 
    
         
            +
                    while true
         
     | 
| 
      
 105 
     | 
    
         
            +
                      case peek
         
     | 
| 
      
 106 
     | 
    
         
            +
                      when :called then match :called; called = match :symbol
         
     | 
| 
      
 107 
     | 
    
         
            +
                      when :at then match :at; where = match :symbol
         
     | 
| 
      
 108 
     | 
    
         
            +
                      when :nowhere then match :nowhere; where = ROOM_NOWHERE
         
     | 
| 
      
 109 
     | 
    
         
            +
                      when :carried then match :carried; where = ROOM_CARRIED
         
     | 
| 
      
 110 
     | 
    
         
            +
                      else break
         
     | 
| 
      
 111 
     | 
    
         
            +
                      end
         
     | 
| 
      
 112 
     | 
    
         
            +
                    end
         
     | 
| 
      
 113 
     | 
    
         
            +
                    CItem.new(name, desc, called, where ? where : last_room)
         
     | 
| 
      
 114 
     | 
    
         
            +
                  end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                  def parse_action
         
     | 
| 
      
 117 
     | 
    
         
            +
                    match :action
         
     | 
| 
      
 118 
     | 
    
         
            +
                    verb = match :symbol
         
     | 
| 
      
 119 
     | 
    
         
            +
                    if peek == :when
         
     | 
| 
      
 120 
     | 
    
         
            +
                      noun = nil
         
     | 
| 
      
 121 
     | 
    
         
            +
                    elsif peek == ":" # to terminate single-word actions if no conditions
         
     | 
| 
      
 122 
     | 
    
         
            +
                      skip
         
     | 
| 
      
 123 
     | 
    
         
            +
                      noun = nil
         
     | 
| 
      
 124 
     | 
    
         
            +
                    else
         
     | 
| 
      
 125 
     | 
    
         
            +
                      noun = match :symbol
         
     | 
| 
      
 126 
     | 
    
         
            +
                      skip if peek == ":" # optional
         
     | 
| 
      
 127 
     | 
    
         
            +
                    end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                    conds, instructions, comment = parse_actionbody
         
     | 
| 
      
 130 
     | 
    
         
            +
                    CAction.new(verb, noun, conds, instructions, comment)
         
     | 
| 
      
 131 
     | 
    
         
            +
                  end
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                  def parse_occur
         
     | 
| 
      
 134 
     | 
    
         
            +
                    match :occur
         
     | 
| 
      
 135 
     | 
    
         
            +
                    if peek == :percent
         
     | 
| 
      
 136 
     | 
    
         
            +
                      chance = match :percent
         
     | 
| 
      
 137 
     | 
    
         
            +
                      skip if peek == ":" # optional
         
     | 
| 
      
 138 
     | 
    
         
            +
                    else
         
     | 
| 
      
 139 
     | 
    
         
            +
                      chance = nil
         
     | 
| 
      
 140 
     | 
    
         
            +
                    end
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                    conds, instructions, comment = parse_actionbody
         
     | 
| 
      
 143 
     | 
    
         
            +
                    CAction.new(nil, chance, conds, instructions, comment)        
         
     | 
| 
      
 144 
     | 
    
         
            +
                  end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                  def parse_actionbody
         
     | 
| 
      
 147 
     | 
    
         
            +
                    conds = []
         
     | 
| 
      
 148 
     | 
    
         
            +
                    while peek == :when || peek == :and
         
     | 
| 
      
 149 
     | 
    
         
            +
                      skip
         
     | 
| 
      
 150 
     | 
    
         
            +
                      op = match :symbol
         
     | 
| 
      
 151 
     | 
    
         
            +
                      type = Condition::OPStotype[op] or
         
     | 
| 
      
 152 
     | 
    
         
            +
                        error "unknown condition op '#{op}'"
         
     | 
| 
      
 153 
     | 
    
         
            +
                      case type
         
     | 
| 
      
 154 
     | 
    
         
            +
                      when :NONE then val = 0 # Any numeric value is fine here
         
     | 
| 
      
 155 
     | 
    
         
            +
                      when :number then val = match :symbol
         
     | 
| 
      
 156 
     | 
    
         
            +
                      when :room then val = match :symbol
         
     | 
| 
      
 157 
     | 
    
         
            +
                      when :item then val = match :symbol
         
     | 
| 
      
 158 
     | 
    
         
            +
                      else error "condition op '#{op}' has unknown type '#{type}'"
         
     | 
| 
      
 159 
     | 
    
         
            +
                      end
         
     | 
| 
      
 160 
     | 
    
         
            +
                      conds << [ op, val ]
         
     | 
| 
      
 161 
     | 
    
         
            +
                    end
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                    instructions = []
         
     | 
| 
      
 164 
     | 
    
         
            +
                    while peek == :symbol
         
     | 
| 
      
 165 
     | 
    
         
            +
                      op = match :symbol
         
     | 
| 
      
 166 
     | 
    
         
            +
                      if op == "print"
         
     | 
| 
      
 167 
     | 
    
         
            +
                        val = [ match(:symbol) ]
         
     | 
| 
      
 168 
     | 
    
         
            +
                      else
         
     | 
| 
      
 169 
     | 
    
         
            +
                        type = Instruction::OPStotype[op] or
         
     | 
| 
      
 170 
     | 
    
         
            +
                          error "unknown instruction op '#{op}'"
         
     | 
| 
      
 171 
     | 
    
         
            +
                        case type
         
     | 
| 
      
 172 
     | 
    
         
            +
                        when :NONE then val = []
         
     | 
| 
      
 173 
     | 
    
         
            +
                        when :number then val = [ match(:symbol) ]
         
     | 
| 
      
 174 
     | 
    
         
            +
                        when :room then val = [ match(:symbol) ]
         
     | 
| 
      
 175 
     | 
    
         
            +
                        when :item then val = [ match(:symbol) ]
         
     | 
| 
      
 176 
     | 
    
         
            +
                        when :item_item then val = [ match(:symbol), match(:symbol) ]
         
     | 
| 
      
 177 
     | 
    
         
            +
                        when :item_room then val = [ match(:symbol), match(:symbol) ]
         
     | 
| 
      
 178 
     | 
    
         
            +
                        else error "instruction op '#{op}' has unknown type '#{type}'"
         
     | 
| 
      
 179 
     | 
    
         
            +
                        end
         
     | 
| 
      
 180 
     | 
    
         
            +
                      end
         
     | 
| 
      
 181 
     | 
    
         
            +
                      instructions << [ op, val ]
         
     | 
| 
      
 182 
     | 
    
         
            +
                    end
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                    comment = nil
         
     | 
| 
      
 185 
     | 
    
         
            +
                    if peek == :comment
         
     | 
| 
      
 186 
     | 
    
         
            +
                      skip
         
     | 
| 
      
 187 
     | 
    
         
            +
                      comment = match :symbol
         
     | 
| 
      
 188 
     | 
    
         
            +
                    end
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
                    [ conds, instructions, comment ]
         
     | 
| 
      
 191 
     | 
    
         
            +
                  end
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
                  def parse_wordgroup
         
     | 
| 
      
 194 
     | 
    
         
            +
                    skip
         
     | 
| 
      
 195 
     | 
    
         
            +
                    words = []
         
     | 
| 
      
 196 
     | 
    
         
            +
                    while peek == :symbol
         
     | 
| 
      
 197 
     | 
    
         
            +
                      words << match(:symbol)
         
     | 
| 
      
 198 
     | 
    
         
            +
                    end
         
     | 
| 
      
 199 
     | 
    
         
            +
                    words
         
     | 
| 
      
 200 
     | 
    
         
            +
                  end
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
                  # Skip over the current token
         
     | 
| 
      
 203 
     | 
    
         
            +
                  def skip; match peek; end
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
                  # Delegators through to the lexer class: just to keep parser source terse
         
     | 
| 
      
 206 
     | 
    
         
            +
                  def peek; @lexer.peek; end
         
     | 
| 
      
 207 
     | 
    
         
            +
                  def match(token, estr = nil); @lexer.match token, estr; end
         
     | 
| 
      
 208 
     | 
    
         
            +
                  def error(str); @lexer.error str; end
         
     | 
| 
      
 209 
     | 
    
         
            +
             
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                  def generate_code(tree)
         
     | 
| 
      
 212 
     | 
    
         
            +
                    @had_errors = false
         
     | 
| 
      
 213 
     | 
    
         
            +
                    rooms = tree.rooms
         
     | 
| 
      
 214 
     | 
    
         
            +
                    items = tree.items
         
     | 
| 
      
 215 
     | 
    
         
            +
             
     | 
| 
      
 216 
     | 
    
         
            +
                    if tree.lightsource then
         
     | 
| 
      
 217 
     | 
    
         
            +
                      # The light-source is always item #9, so swap as necessary
         
     | 
| 
      
 218 
     | 
    
         
            +
                      lindex = items.index { |x| x.name == tree.lightsource } or
         
     | 
| 
      
 219 
     | 
    
         
            +
                        gerror "lightsource '#{tree.lightsource}' does not exist"
         
     | 
| 
      
 220 
     | 
    
         
            +
                      if (lindex != ITEM_LAMP)
         
     | 
| 
      
 221 
     | 
    
         
            +
                        items << CItem.new(nil, "", nil, 0) while items.size <= ITEM_LAMP
         
     | 
| 
      
 222 
     | 
    
         
            +
                        items[lindex], items[ITEM_LAMP] = items[ITEM_LAMP], items[lindex]
         
     | 
| 
      
 223 
     | 
    
         
            +
                      end
         
     | 
| 
      
 224 
     | 
    
         
            +
                    end
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
                    # Make name->index maps for rooms and items
         
     | 
| 
      
 227 
     | 
    
         
            +
                    roommap = { "_ROOM0" => 0 }
         
     | 
| 
      
 228 
     | 
    
         
            +
                    itemmap = {}
         
     | 
| 
      
 229 
     | 
    
         
            +
                    rooms.each.with_index { |room, index| roommap[room.name] = index }
         
     | 
| 
      
 230 
     | 
    
         
            +
                    items.each.with_index { |item, index| itemmap[item.name] = index }
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
                    startindex, treasuryindex = [ [ tree.start, "start" ],
         
     | 
| 
      
 233 
     | 
    
         
            +
                                                  [ tree.treasury, "treasury" ]
         
     | 
| 
      
 234 
     | 
    
         
            +
                                                  ].map {
         
     | 
| 
      
 235 
     | 
    
         
            +
                      |ref| loc, caption = *ref
         
     | 
| 
      
 236 
     | 
    
         
            +
                      !loc ? 1 : roommap[loc] or
         
     | 
| 
      
 237 
     | 
    
         
            +
                        gerror "#{caption} room '#{loc}' does not exist"
         
     | 
| 
      
 238 
     | 
    
         
            +
                    }
         
     | 
| 
      
 239 
     | 
    
         
            +
             
     | 
| 
      
 240 
     | 
    
         
            +
                    # Resolve room names in exits
         
     | 
| 
      
 241 
     | 
    
         
            +
                    rooms.each do |room|
         
     | 
| 
      
 242 
     | 
    
         
            +
                      room.exits.each do |dir, dest|
         
     | 
| 
      
 243 
     | 
    
         
            +
                        room.exits[dir] = roommap[dest] or
         
     | 
| 
      
 244 
     | 
    
         
            +
                          gerror "'#{dest}' (#{dir} from #{room.name}) does not exist"
         
     | 
| 
      
 245 
     | 
    
         
            +
                      end
         
     | 
| 
      
 246 
     | 
    
         
            +
                    end
         
     | 
| 
      
 247 
     | 
    
         
            +
             
     | 
| 
      
 248 
     | 
    
         
            +
                    # Resolve room names in item locations
         
     | 
| 
      
 249 
     | 
    
         
            +
                    items.each do |item|
         
     | 
| 
      
 250 
     | 
    
         
            +
                      if item.where.class == String
         
     | 
| 
      
 251 
     | 
    
         
            +
                        num = room_by_name(item.where, roommap) or
         
     | 
| 
      
 252 
     | 
    
         
            +
                          gerror "location '#{item.where}' for #{item.desc}) does not exist"
         
     | 
| 
      
 253 
     | 
    
         
            +
                        item.where = num
         
     | 
| 
      
 254 
     | 
    
         
            +
                      end
         
     | 
| 
      
 255 
     | 
    
         
            +
                    end
         
     | 
| 
      
 256 
     | 
    
         
            +
             
     | 
| 
      
 257 
     | 
    
         
            +
                    # Map each verb and noun to group of all its synonyms
         
     | 
| 
      
 258 
     | 
    
         
            +
                    @wordlen = Integer(tree.wordlen ||= 3)
         
     | 
| 
      
 259 
     | 
    
         
            +
                    verbtogroup, nountogroup = [ tree.verbgroups,
         
     | 
| 
      
 260 
     | 
    
         
            +
                                                 tree.noungroups ].map { |groups|
         
     | 
| 
      
 261 
     | 
    
         
            +
                      groups = groups.map { |list| list.map { |word| word.upcase[0, @wordlen] } }
         
     | 
| 
      
 262 
     | 
    
         
            +
                      res = {}
         
     | 
| 
      
 263 
     | 
    
         
            +
                      groups.each do |list|
         
     | 
| 
      
 264 
     | 
    
         
            +
                        list.each { |word| res[word] = list }
         
     | 
| 
      
 265 
     | 
    
         
            +
                      end
         
     | 
| 
      
 266 
     | 
    
         
            +
                      res
         
     | 
| 
      
 267 
     | 
    
         
            +
                    }
         
     | 
| 
      
 268 
     | 
    
         
            +
             
     | 
| 
      
 269 
     | 
    
         
            +
                    # Compile vocabulary, including synonyms.
         
     | 
| 
      
 270 
     | 
    
         
            +
                    verbs = [ "AUT" ]
         
     | 
| 
      
 271 
     | 
    
         
            +
                    nouns = [ "ANY" ]
         
     | 
| 
      
 272 
     | 
    
         
            +
                    verbmap, nounmap = {}, {}
         
     | 
| 
      
 273 
     | 
    
         
            +
             
     | 
| 
      
 274 
     | 
    
         
            +
                    # Verb 1 is GO, verb 10 is GET, verb 18 is DROP (always).
         
     | 
| 
      
 275 
     | 
    
         
            +
                    [ ["go", 1], ["get", 10], ["drop", 18] ]. each do |pair|
         
     | 
| 
      
 276 
     | 
    
         
            +
                      insert_word(verbtogroup, verbs, verbmap, *pair)
         
     | 
| 
      
 277 
     | 
    
         
            +
                    end
         
     | 
| 
      
 278 
     | 
    
         
            +
                    # Nouns 1-6 are directions: no synonyms possible
         
     | 
| 
      
 279 
     | 
    
         
            +
                    0.upto(5).each do |i|
         
     | 
| 
      
 280 
     | 
    
         
            +
                      insert_word(nountogroup, nouns, nounmap, @game.dirname(i), i+1)
         
     | 
| 
      
 281 
     | 
    
         
            +
                    end
         
     | 
| 
      
 282 
     | 
    
         
            +
             
     | 
| 
      
 283 
     | 
    
         
            +
             
     | 
| 
      
 284 
     | 
    
         
            +
                    # Messages from actions will be accumulated here
         
     | 
| 
      
 285 
     | 
    
         
            +
                    messages = [ "" ]       # Maps message-number to message
         
     | 
| 
      
 286 
     | 
    
         
            +
                    messagemap = {}         # Maps message to message-number
         
     | 
| 
      
 287 
     | 
    
         
            +
             
     | 
| 
      
 288 
     | 
    
         
            +
                    # Instructions must not exceed four per batch
         
     | 
| 
      
 289 
     | 
    
         
            +
                    actions = []
         
     | 
| 
      
 290 
     | 
    
         
            +
                    tree.actions.each do |action|
         
     | 
| 
      
 291 
     | 
    
         
            +
                      ins, acc = action.instructions, []
         
     | 
| 
      
 292 
     | 
    
         
            +
                      while ins.size > 4
         
     | 
| 
      
 293 
     | 
    
         
            +
                        acc.concat ins.shift(3)
         
     | 
| 
      
 294 
     | 
    
         
            +
                        acc.push [ "continue" ]
         
     | 
| 
      
 295 
     | 
    
         
            +
                      end
         
     | 
| 
      
 296 
     | 
    
         
            +
                      acc.concat ins
         
     | 
| 
      
 297 
     | 
    
         
            +
                      acc.push [0] while acc.size % 4 != 0
         
     | 
| 
      
 298 
     | 
    
         
            +
             
     | 
| 
      
 299 
     | 
    
         
            +
                      # We now have batches of four instructions; each but the
         
     | 
| 
      
 300 
     | 
    
         
            +
                      # first must be placed in a new action.
         
     | 
| 
      
 301 
     | 
    
         
            +
                      action.instructions = acc.shift(4)
         
     | 
| 
      
 302 
     | 
    
         
            +
                      actions << action
         
     | 
| 
      
 303 
     | 
    
         
            +
                      actions << CAction.new(nil, 0, [], acc.shift(4), "cont", []) while
         
     | 
| 
      
 304 
     | 
    
         
            +
                        acc.count != 0
         
     | 
| 
      
 305 
     | 
    
         
            +
                    end
         
     | 
| 
      
 306 
     | 
    
         
            +
             
     | 
| 
      
 307 
     | 
    
         
            +
                    # Resolve room and item names in actions and occurrences
         
     | 
| 
      
 308 
     | 
    
         
            +
                    actions.each do |action|
         
     | 
| 
      
 309 
     | 
    
         
            +
                      if action.verb
         
     | 
| 
      
 310 
     | 
    
         
            +
                        # Actual actions
         
     | 
| 
      
 311 
     | 
    
         
            +
                        action.verb = insert_word(verbtogroup, verbs, verbmap, action.verb)
         
     | 
| 
      
 312 
     | 
    
         
            +
                        if action.noun
         
     | 
| 
      
 313 
     | 
    
         
            +
                          action.noun = insert_word(nountogroup, nouns, nounmap,
         
     | 
| 
      
 314 
     | 
    
         
            +
                                                    action.noun)
         
     | 
| 
      
 315 
     | 
    
         
            +
                        else
         
     | 
| 
      
 316 
     | 
    
         
            +
                          action.noun = 0
         
     | 
| 
      
 317 
     | 
    
         
            +
                        end
         
     | 
| 
      
 318 
     | 
    
         
            +
                      else
         
     | 
| 
      
 319 
     | 
    
         
            +
                        # Occurrences
         
     | 
| 
      
 320 
     | 
    
         
            +
                        action.verb = 0
         
     | 
| 
      
 321 
     | 
    
         
            +
                        action.noun = Integer(action.noun || 100)
         
     | 
| 
      
 322 
     | 
    
         
            +
                      end
         
     | 
| 
      
 323 
     | 
    
         
            +
             
     | 
| 
      
 324 
     | 
    
         
            +
                      action.conds.each do |cond|
         
     | 
| 
      
 325 
     | 
    
         
            +
                        op, arg = cond[0], cond[1]
         
     | 
| 
      
 326 
     | 
    
         
            +
                        opcode = Condition::OPStoindex[op]
         
     | 
| 
      
 327 
     | 
    
         
            +
                        type = Condition::OPStotype[op]
         
     | 
| 
      
 328 
     | 
    
         
            +
                        raise "impossible unknown condition op '#{op}'" if
         
     | 
| 
      
 329 
     | 
    
         
            +
                          !opcode || !type
         
     | 
| 
      
 330 
     | 
    
         
            +
                        cond[0] = opcode
         
     | 
| 
      
 331 
     | 
    
         
            +
                        case type
         
     | 
| 
      
 332 
     | 
    
         
            +
                        when :NONE then # nothing to do
         
     | 
| 
      
 333 
     | 
    
         
            +
                        when :number then cond[1] = Integer(cond[1])
         
     | 
| 
      
 334 
     | 
    
         
            +
                        when :room then cond[1] = roommap[arg] or
         
     | 
| 
      
 335 
     | 
    
         
            +
                            gerror "unknown room in condition '#{arg}'"
         
     | 
| 
      
 336 
     | 
    
         
            +
                        when :item then cond[1] = itemmap[arg] or
         
     | 
| 
      
 337 
     | 
    
         
            +
                            gerror "unknown item in condition '#{arg}'"
         
     | 
| 
      
 338 
     | 
    
         
            +
                        else gerror "condition op '#{op}' has unknown type '#{type}'"
         
     | 
| 
      
 339 
     | 
    
         
            +
                        end
         
     | 
| 
      
 340 
     | 
    
         
            +
                      end
         
     | 
| 
      
 341 
     | 
    
         
            +
             
     | 
| 
      
 342 
     | 
    
         
            +
                      gathered_args = []
         
     | 
| 
      
 343 
     | 
    
         
            +
                      action.instructions.each do |ins|
         
     | 
| 
      
 344 
     | 
    
         
            +
                        op, args = ins[0], ins[1]
         
     | 
| 
      
 345 
     | 
    
         
            +
                        arg0, arg1 = *args
         
     | 
| 
      
 346 
     | 
    
         
            +
                        if op == 0
         
     | 
| 
      
 347 
     | 
    
         
            +
                          next
         
     | 
| 
      
 348 
     | 
    
         
            +
                        elsif op == "print"
         
     | 
| 
      
 349 
     | 
    
         
            +
                          if !(msgno = messagemap[arg0])
         
     | 
| 
      
 350 
     | 
    
         
            +
                            messages << arg0
         
     | 
| 
      
 351 
     | 
    
         
            +
                            msgno = messagemap[arg0] = messages.size-1
         
     | 
| 
      
 352 
     | 
    
         
            +
                          end
         
     | 
| 
      
 353 
     | 
    
         
            +
                          ins[0] = msgno <= 51 ? msgno : msgno+50
         
     | 
| 
      
 354 
     | 
    
         
            +
                          next
         
     | 
| 
      
 355 
     | 
    
         
            +
                        end
         
     | 
| 
      
 356 
     | 
    
         
            +
                        opcode = Instruction::OPStoindex[op]
         
     | 
| 
      
 357 
     | 
    
         
            +
                        type = Instruction::OPStotype[op]
         
     | 
| 
      
 358 
     | 
    
         
            +
                        raise "impossible unknown instruction op '#{op}'" if
         
     | 
| 
      
 359 
     | 
    
         
            +
                          !opcode || !type
         
     | 
| 
      
 360 
     | 
    
         
            +
                        ins[0] = opcode
         
     | 
| 
      
 361 
     | 
    
         
            +
                        case type
         
     | 
| 
      
 362 
     | 
    
         
            +
                        when :NONE then # nothing to do
         
     | 
| 
      
 363 
     | 
    
         
            +
                        when :number then gathered_args << Integer(arg0)
         
     | 
| 
      
 364 
     | 
    
         
            +
                        when :room then gathered_args << (roommap[arg0] or
         
     | 
| 
      
 365 
     | 
    
         
            +
                            gerror "unknown room in instruction '#{arg0}'")
         
     | 
| 
      
 366 
     | 
    
         
            +
                        when :item then gathered_args << (itemmap[arg0] or
         
     | 
| 
      
 367 
     | 
    
         
            +
                            gerror "unknown item in instruction '#{arg0}'")
         
     | 
| 
      
 368 
     | 
    
         
            +
                        when :item_item then gathered_args << (itemmap[arg0] or
         
     | 
| 
      
 369 
     | 
    
         
            +
                            gerror "unknown item in instruction '#{arg0}'")
         
     | 
| 
      
 370 
     | 
    
         
            +
                          gathered_args << (itemmap[arg1] or
         
     | 
| 
      
 371 
     | 
    
         
            +
                            gerror "unknown item in instruction '#{arg1}'")
         
     | 
| 
      
 372 
     | 
    
         
            +
                        when :item_room then gathered_args << (itemmap[arg0] or
         
     | 
| 
      
 373 
     | 
    
         
            +
                            gerror "unknown item in instruction '#{arg0}'")
         
     | 
| 
      
 374 
     | 
    
         
            +
                          gathered_args << (roommap[arg1] or
         
     | 
| 
      
 375 
     | 
    
         
            +
                            gerror "unknown room in instruction '#{arg1}'")
         
     | 
| 
      
 376 
     | 
    
         
            +
                        else gerror "instruction op '#{op}' has unknown type '#{type}'"
         
     | 
| 
      
 377 
     | 
    
         
            +
                        end
         
     | 
| 
      
 378 
     | 
    
         
            +
                      end
         
     | 
| 
      
 379 
     | 
    
         
            +
                      action.gathered_args = gathered_args
         
     | 
| 
      
 380 
     | 
    
         
            +
                    end
         
     | 
| 
      
 381 
     | 
    
         
            +
             
     | 
| 
      
 382 
     | 
    
         
            +
                    # Add auto-get names of items to vocabulary
         
     | 
| 
      
 383 
     | 
    
         
            +
                    items.each do |item|
         
     | 
| 
      
 384 
     | 
    
         
            +
                      insert_word(nountogroup, nouns, nounmap, item.called) if item.called
         
     | 
| 
      
 385 
     | 
    
         
            +
                    end
         
     | 
| 
      
 386 
     | 
    
         
            +
             
     | 
| 
      
 387 
     | 
    
         
            +
                    1.upto([ verbs.size-1, nouns.size-1 ].max) do |i|
         
     | 
| 
      
 388 
     | 
    
         
            +
                      verbs[i] = "" if !verbs[i]
         
     | 
| 
      
 389 
     | 
    
         
            +
                      nouns[i] = "" if !nouns[i]
         
     | 
| 
      
 390 
     | 
    
         
            +
                    end
         
     | 
| 
      
 391 
     | 
    
         
            +
             
     | 
| 
      
 392 
     | 
    
         
            +
                    return if @had_errors
         
     | 
| 
      
 393 
     | 
    
         
            +
             
     | 
| 
      
 394 
     | 
    
         
            +
                    # Write header
         
     | 
| 
      
 395 
     | 
    
         
            +
                    puts tree.unknown1 || 0
         
     | 
| 
      
 396 
     | 
    
         
            +
                    puts items.size-1
         
     | 
| 
      
 397 
     | 
    
         
            +
                    puts actions.size-1
         
     | 
| 
      
 398 
     | 
    
         
            +
                    puts verbs.size-1
         
     | 
| 
      
 399 
     | 
    
         
            +
                    puts rooms.size-1
         
     | 
| 
      
 400 
     | 
    
         
            +
                    puts tree.maxload || -1
         
     | 
| 
      
 401 
     | 
    
         
            +
                    puts startindex
         
     | 
| 
      
 402 
     | 
    
         
            +
                    puts items.select { |x| x.desc[0] == "*" }.count
         
     | 
| 
      
 403 
     | 
    
         
            +
                    puts tree.wordlen
         
     | 
| 
      
 404 
     | 
    
         
            +
                    puts tree.lighttime || -1
         
     | 
| 
      
 405 
     | 
    
         
            +
                    puts messages.size-1
         
     | 
| 
      
 406 
     | 
    
         
            +
                    puts treasuryindex
         
     | 
| 
      
 407 
     | 
    
         
            +
                    puts # Blank line
         
     | 
| 
      
 408 
     | 
    
         
            +
             
     | 
| 
      
 409 
     | 
    
         
            +
                    # Actions
         
     | 
| 
      
 410 
     | 
    
         
            +
                    actions.each do |action|
         
     | 
| 
      
 411 
     | 
    
         
            +
                      print 150*action.verb + action.noun, " "
         
     | 
| 
      
 412 
     | 
    
         
            +
             
     | 
| 
      
 413 
     | 
    
         
            +
                      print action.conds.map { |x| String(x[0] + 20 * x[1]) + " " }.join
         
     | 
| 
      
 414 
     | 
    
         
            +
                      print action.gathered_args.map { |x| String(20*x) + " " }.join
         
     | 
| 
      
 415 
     | 
    
         
            +
                      nconds = action.conds.size + action.gathered_args.size
         
     | 
| 
      
 416 
     | 
    
         
            +
                      raise "condition has #{nconds} conditions" if nconds > 5
         
     | 
| 
      
 417 
     | 
    
         
            +
                      (5-nconds).times { print "0 " }
         
     | 
| 
      
 418 
     | 
    
         
            +
             
     | 
| 
      
 419 
     | 
    
         
            +
                      ins = action.instructions.map { |x| x[0] }
         
     | 
| 
      
 420 
     | 
    
         
            +
                      (4-ins.count).times { ins << 0 }
         
     | 
| 
      
 421 
     | 
    
         
            +
                      puts "#{150*ins[0] + ins[1]} #{150*ins[2] + ins[3]}\n"
         
     | 
| 
      
 422 
     | 
    
         
            +
                    end
         
     | 
| 
      
 423 
     | 
    
         
            +
                    puts # Blank line
         
     | 
| 
      
 424 
     | 
    
         
            +
             
     | 
| 
      
 425 
     | 
    
         
            +
                    # Vocab
         
     | 
| 
      
 426 
     | 
    
         
            +
                    verbs.each.with_index do |verb, i|
         
     | 
| 
      
 427 
     | 
    
         
            +
                      puts "\"#{verb}\" \"#{nouns[i]}\""
         
     | 
| 
      
 428 
     | 
    
         
            +
                    end
         
     | 
| 
      
 429 
     | 
    
         
            +
                    puts # Blank line
         
     | 
| 
      
 430 
     | 
    
         
            +
             
     | 
| 
      
 431 
     | 
    
         
            +
                    # Rooms
         
     | 
| 
      
 432 
     | 
    
         
            +
                    rooms.each do |room|
         
     | 
| 
      
 433 
     | 
    
         
            +
                      0.upto(5).each do |i|
         
     | 
| 
      
 434 
     | 
    
         
            +
                        exit = room.exits[@game.dirname(i)]
         
     | 
| 
      
 435 
     | 
    
         
            +
                        print(exit ? exit : 0, " ")
         
     | 
| 
      
 436 
     | 
    
         
            +
                      end
         
     | 
| 
      
 437 
     | 
    
         
            +
                      print "\"#{room.desc}\"\n"
         
     | 
| 
      
 438 
     | 
    
         
            +
                    end
         
     | 
| 
      
 439 
     | 
    
         
            +
                    puts # Blank line
         
     | 
| 
      
 440 
     | 
    
         
            +
             
     | 
| 
      
 441 
     | 
    
         
            +
                    # Messages
         
     | 
| 
      
 442 
     | 
    
         
            +
                    messages.each do |message|
         
     | 
| 
      
 443 
     | 
    
         
            +
                      puts "\"#{message}\"\n"
         
     | 
| 
      
 444 
     | 
    
         
            +
                    end
         
     | 
| 
      
 445 
     | 
    
         
            +
                    puts # Blank line
         
     | 
| 
      
 446 
     | 
    
         
            +
             
     | 
| 
      
 447 
     | 
    
         
            +
                    # Items
         
     | 
| 
      
 448 
     | 
    
         
            +
                    items.each do |item|
         
     | 
| 
      
 449 
     | 
    
         
            +
                      desc = item.desc
         
     | 
| 
      
 450 
     | 
    
         
            +
                      desc += "/" + item.called.upcase[0, @wordlen] + "/" if item.called
         
     | 
| 
      
 451 
     | 
    
         
            +
                      puts "\"#{desc}\" #{item.where}"
         
     | 
| 
      
 452 
     | 
    
         
            +
                    end
         
     | 
| 
      
 453 
     | 
    
         
            +
                    puts # Blank line
         
     | 
| 
      
 454 
     | 
    
         
            +
             
     | 
| 
      
 455 
     | 
    
         
            +
                    # Action comments
         
     | 
| 
      
 456 
     | 
    
         
            +
                    actions.each do |action|
         
     | 
| 
      
 457 
     | 
    
         
            +
                      puts "\"#{action.comment || ""}\"\n"
         
     | 
| 
      
 458 
     | 
    
         
            +
                    end
         
     | 
| 
      
 459 
     | 
    
         
            +
                    puts # Blank line
         
     | 
| 
      
 460 
     | 
    
         
            +
             
     | 
| 
      
 461 
     | 
    
         
            +
                    # Trailer
         
     | 
| 
      
 462 
     | 
    
         
            +
                    puts tree.version || 0
         
     | 
| 
      
 463 
     | 
    
         
            +
                    puts tree.ident || 0
         
     | 
| 
      
 464 
     | 
    
         
            +
                    puts tree.unknown2 || 0
         
     | 
| 
      
 465 
     | 
    
         
            +
                  end
         
     | 
| 
      
 466 
     | 
    
         
            +
             
     | 
| 
      
 467 
     | 
    
         
            +
                  def room_by_name(loc, roommap)
         
     | 
| 
      
 468 
     | 
    
         
            +
                    if @game.options[:bug_tolerant] && loc[0,5] == "_ROOM"
         
     | 
| 
      
 469 
     | 
    
         
            +
                      Integer(loc[5,999])
         
     | 
| 
      
 470 
     | 
    
         
            +
                    else
         
     | 
| 
      
 471 
     | 
    
         
            +
                      roommap[loc]
         
     | 
| 
      
 472 
     | 
    
         
            +
                    end
         
     | 
| 
      
 473 
     | 
    
         
            +
                  end
         
     | 
| 
      
 474 
     | 
    
         
            +
             
     | 
| 
      
 475 
     | 
    
         
            +
                  # Complex API here, sorry.  If word, or an equivalent word
         
     | 
| 
      
 476 
     | 
    
         
            +
                  # according to synmap, is not already in list and map, inserts
         
     | 
| 
      
 477 
     | 
    
         
            +
                  # it and its synonyms into both, with map hashing words to the
         
     | 
| 
      
 478 
     | 
    
         
            +
                  # index they appear at.  Word is inserted at index if specified
         
     | 
| 
      
 479 
     | 
    
         
            +
                  # and otherwise at the first free slot, or off the end if there
         
     | 
| 
      
 480 
     | 
    
         
            +
                  # are no free slots.  Synonyms, if any, follow thereafter in a
         
     | 
| 
      
 481 
     | 
    
         
            +
                  # block.  Returns the index of the word in list
         
     | 
| 
      
 482 
     | 
    
         
            +
                  def insert_word(synmap, list, map, word, index = nil)
         
     | 
| 
      
 483 
     | 
    
         
            +
                    word = word.upcase[0, @wordlen]
         
     | 
| 
      
 484 
     | 
    
         
            +
                    syn = synmap[word] || [word]
         
     | 
| 
      
 485 
     | 
    
         
            +
                    canonical = syn[0]
         
     | 
| 
      
 486 
     | 
    
         
            +
                    return map[canonical] if map[canonical]
         
     | 
| 
      
 487 
     | 
    
         
            +
                    if !index
         
     | 
| 
      
 488 
     | 
    
         
            +
                      index = 1
         
     | 
| 
      
 489 
     | 
    
         
            +
                      index += 1 while syn.each_index.any? { |i| list[index+i] != nil }
         
     | 
| 
      
 490 
     | 
    
         
            +
                    end
         
     | 
| 
      
 491 
     | 
    
         
            +
             
     | 
| 
      
 492 
     | 
    
         
            +
                    firstindex = index
         
     | 
| 
      
 493 
     | 
    
         
            +
                    syn.each do |thisword|
         
     | 
| 
      
 494 
     | 
    
         
            +
                      if list[index]
         
     | 
| 
      
 495 
     | 
    
         
            +
                        return(gerror "can't insert word '#{thisword}' " +
         
     | 
| 
      
 496 
     | 
    
         
            +
                               "@at position #{index} -- got '#{list[index]}'")
         
     | 
| 
      
 497 
     | 
    
         
            +
                      end
         
     | 
| 
      
 498 
     | 
    
         
            +
                      list[index] = index == firstindex ? thisword : "*"+thisword
         
     | 
| 
      
 499 
     | 
    
         
            +
                      map[thisword] = firstindex
         
     | 
| 
      
 500 
     | 
    
         
            +
                      index += 1
         
     | 
| 
      
 501 
     | 
    
         
            +
                    end
         
     | 
| 
      
 502 
     | 
    
         
            +
                    firstindex
         
     | 
| 
      
 503 
     | 
    
         
            +
                  end
         
     | 
| 
      
 504 
     | 
    
         
            +
             
     | 
| 
      
 505 
     | 
    
         
            +
                  def gerror(str)
         
     | 
| 
      
 506 
     | 
    
         
            +
                    $stderr.puts "error: #{str}"
         
     | 
| 
      
 507 
     | 
    
         
            +
                    @had_errors = true
         
     | 
| 
      
 508 
     | 
    
         
            +
                    0
         
     | 
| 
      
 509 
     | 
    
         
            +
                  end
         
     | 
| 
      
 510 
     | 
    
         
            +
             
     | 
| 
      
 511 
     | 
    
         
            +
             
     | 
| 
      
 512 
     | 
    
         
            +
                  public :compile_to_stdout # Must be visible to Game.compile()
         
     | 
| 
      
 513 
     | 
    
         
            +
                  public :parse # Used by test_compile.rb
         
     | 
| 
      
 514 
     | 
    
         
            +
             
     | 
| 
      
 515 
     | 
    
         
            +
             
     | 
| 
      
 516 
     | 
    
         
            +
                  CGame = Struct.new(:ident, :version, :unknown1, :unknown2,
         
     | 
| 
      
 517 
     | 
    
         
            +
                                     :start, :treasury, :maxload, :wordlen,
         
     | 
| 
      
 518 
     | 
    
         
            +
                                     :lighttime, :lightsource, :rooms, :items,
         
     | 
| 
      
 519 
     | 
    
         
            +
                                     :actions, :verbgroups, :noungroups) #:nodoc:
         
     | 
| 
      
 520 
     | 
    
         
            +
                  CRoom = Struct.new(:name, :desc, :exits) #:nodoc:
         
     | 
| 
      
 521 
     | 
    
         
            +
                  CItem = Struct.new(:name, :desc, :called, :where) #:nodoc:
         
     | 
| 
      
 522 
     | 
    
         
            +
                  CAction = Struct.new(:verb, :noun, :conds, :instructions,
         
     | 
| 
      
 523 
     | 
    
         
            +
                                       :comment, :gathered_args) #:nodoc:
         
     | 
| 
      
 524 
     | 
    
         
            +
             
     | 
| 
      
 525 
     | 
    
         
            +
             
     | 
| 
      
 526 
     | 
    
         
            +
                  class Lexer #:nodoc:
         
     | 
| 
      
 527 
     | 
    
         
            +
                    attr_reader :lexeme
         
     | 
| 
      
 528 
     | 
    
         
            +
             
     | 
| 
      
 529 
     | 
    
         
            +
                    TOKENMAP = {
         
     | 
| 
      
 530 
     | 
    
         
            +
                      "start" => :start,
         
     | 
| 
      
 531 
     | 
    
         
            +
                      "treasury" => :treasury,
         
     | 
| 
      
 532 
     | 
    
         
            +
                      "ident" => :ident,
         
     | 
| 
      
 533 
     | 
    
         
            +
                      "version" => :version,
         
     | 
| 
      
 534 
     | 
    
         
            +
                      "unknown1" => :unknown1,
         
     | 
| 
      
 535 
     | 
    
         
            +
                      "unknown2" => :unknown2,
         
     | 
| 
      
 536 
     | 
    
         
            +
                      "maxload" => :maxload,
         
     | 
| 
      
 537 
     | 
    
         
            +
                      "wordlen" => :wordlen,
         
     | 
| 
      
 538 
     | 
    
         
            +
                      "lighttime" => :lighttime,
         
     | 
| 
      
 539 
     | 
    
         
            +
                      "lightsource" => :lightsource,
         
     | 
| 
      
 540 
     | 
    
         
            +
                      "room" => :room,
         
     | 
| 
      
 541 
     | 
    
         
            +
                      "exit" => :exit,
         
     | 
| 
      
 542 
     | 
    
         
            +
                      "north" => :direction, "south" => :direction, "east" => :direction,
         
     | 
| 
      
 543 
     | 
    
         
            +
                      "west" => :direction, "up" => :direction, "down" => :direction,
         
     | 
| 
      
 544 
     | 
    
         
            +
                      "item" => :item,
         
     | 
| 
      
 545 
     | 
    
         
            +
                      "called" => :called,
         
     | 
| 
      
 546 
     | 
    
         
            +
                      "at" => :at,
         
     | 
| 
      
 547 
     | 
    
         
            +
                      "nowhere" => :nowhere,
         
     | 
| 
      
 548 
     | 
    
         
            +
                      "carried" => :carried,
         
     | 
| 
      
 549 
     | 
    
         
            +
                      "action" => :action,
         
     | 
| 
      
 550 
     | 
    
         
            +
                      "occur" => :occur,
         
     | 
| 
      
 551 
     | 
    
         
            +
                      "when" => :when,
         
     | 
| 
      
 552 
     | 
    
         
            +
                      "and" => :and,
         
     | 
| 
      
 553 
     | 
    
         
            +
                      "comment" => :comment,
         
     | 
| 
      
 554 
     | 
    
         
            +
                      "verbgroup" => :verbgroup,
         
     | 
| 
      
 555 
     | 
    
         
            +
                      "noungroup" => :noungroup,
         
     | 
| 
      
 556 
     | 
    
         
            +
                    }
         
     | 
| 
      
 557 
     | 
    
         
            +
             
     | 
| 
      
 558 
     | 
    
         
            +
                    def initialize(game, filename, fh = nil)
         
     | 
| 
      
 559 
     | 
    
         
            +
                      if !fh
         
     | 
| 
      
 560 
     | 
    
         
            +
                        fh = File.new(filename)
         
     | 
| 
      
 561 
     | 
    
         
            +
                      end
         
     | 
| 
      
 562 
     | 
    
         
            +
                      @game, @filename, @fh = game, filename, fh
         
     | 
| 
      
 563 
     | 
    
         
            +
                      @linenumber = 0
         
     | 
| 
      
 564 
     | 
    
         
            +
                      @buffer = ""
         
     | 
| 
      
 565 
     | 
    
         
            +
                      @lookahead = nil
         
     | 
| 
      
 566 
     | 
    
         
            +
                    end
         
     | 
| 
      
 567 
     | 
    
         
            +
             
     | 
| 
      
 568 
     | 
    
         
            +
                    def error(str)
         
     | 
| 
      
 569 
     | 
    
         
            +
                      filename = (defined? @filename) ? @filename : "<UNKNOWN>"
         
     | 
| 
      
 570 
     | 
    
         
            +
                      $stderr.puts "#{filename}:#{@linenumber}:#{str}"
         
     | 
| 
      
 571 
     | 
    
         
            +
                      raise "syntax error"
         
     | 
| 
      
 572 
     | 
    
         
            +
                    end
         
     | 
| 
      
 573 
     | 
    
         
            +
             
     | 
| 
      
 574 
     | 
    
         
            +
                    def lex
         
     | 
| 
      
 575 
     | 
    
         
            +
                      token = _lex
         
     | 
| 
      
 576 
     | 
    
         
            +
                      @game.dputs :show_tokens, "token: #{render(token)}"
         
     | 
| 
      
 577 
     | 
    
         
            +
                      token
         
     | 
| 
      
 578 
     | 
    
         
            +
                    end
         
     | 
| 
      
 579 
     | 
    
         
            +
             
     | 
| 
      
 580 
     | 
    
         
            +
                    def _lex
         
     | 
| 
      
 581 
     | 
    
         
            +
                      @buffer.lstrip!
         
     | 
| 
      
 582 
     | 
    
         
            +
                      while @buffer == "" do
         
     | 
| 
      
 583 
     | 
    
         
            +
                        if !(@buffer = @fh.gets)
         
     | 
| 
      
 584 
     | 
    
         
            +
                          return :eof
         
     | 
| 
      
 585 
     | 
    
         
            +
                        end
         
     | 
| 
      
 586 
     | 
    
         
            +
                        @linenumber += 1
         
     | 
| 
      
 587 
     | 
    
         
            +
                        @buffer.chomp!
         
     | 
| 
      
 588 
     | 
    
         
            +
                        @buffer.rstrip!
         
     | 
| 
      
 589 
     | 
    
         
            +
                        @buffer.lstrip!
         
     | 
| 
      
 590 
     | 
    
         
            +
                      end
         
     | 
| 
      
 591 
     | 
    
         
            +
             
     | 
| 
      
 592 
     | 
    
         
            +
                      if @buffer[0] == "#"
         
     | 
| 
      
 593 
     | 
    
         
            +
                        # Comment runs to end of line
         
     | 
| 
      
 594 
     | 
    
         
            +
                        @buffer = ""
         
     | 
| 
      
 595 
     | 
    
         
            +
                        return _lex # Be honest, a GOTO would be better here
         
     | 
| 
      
 596 
     | 
    
         
            +
                      elsif match = @buffer.match(/^"(.*?)"/)
         
     | 
| 
      
 597 
     | 
    
         
            +
                        @lexeme, @buffer = match[1], match.post_match
         
     | 
| 
      
 598 
     | 
    
         
            +
                        :symbol
         
     | 
| 
      
 599 
     | 
    
         
            +
                      elsif match = @buffer.match(/^"(.*)/)
         
     | 
| 
      
 600 
     | 
    
         
            +
                        # Multi-line string -- can include hashes and indents
         
     | 
| 
      
 601 
     | 
    
         
            +
                        s = match[1]
         
     | 
| 
      
 602 
     | 
    
         
            +
                        while @buffer = @fh.gets
         
     | 
| 
      
 603 
     | 
    
         
            +
                          @linenumber += 1
         
     | 
| 
      
 604 
     | 
    
         
            +
                          @buffer.chomp!
         
     | 
| 
      
 605 
     | 
    
         
            +
                          if match = @buffer.match(/(.*?)"/)
         
     | 
| 
      
 606 
     | 
    
         
            +
                            @lexeme = s + "\n" + match[1]
         
     | 
| 
      
 607 
     | 
    
         
            +
                            @buffer = match.post_match
         
     | 
| 
      
 608 
     | 
    
         
            +
                            break
         
     | 
| 
      
 609 
     | 
    
         
            +
                          else
         
     | 
| 
      
 610 
     | 
    
         
            +
                            s += "\n" + @buffer
         
     | 
| 
      
 611 
     | 
    
         
            +
                          end
         
     | 
| 
      
 612 
     | 
    
         
            +
                        end
         
     | 
| 
      
 613 
     | 
    
         
            +
                        :symbol
         
     | 
| 
      
 614 
     | 
    
         
            +
                      elsif match = @buffer.match(/^(\d+)%/)
         
     | 
| 
      
 615 
     | 
    
         
            +
                        @lexeme, @buffer = match[1], match.post_match
         
     | 
| 
      
 616 
     | 
    
         
            +
                        :percent
         
     | 
| 
      
 617 
     | 
    
         
            +
                      elsif match = @buffer.match(/^([!a-z_0-9-]+)/i)
         
     | 
| 
      
 618 
     | 
    
         
            +
                        @lexeme, @buffer = match[1], match.post_match
         
     | 
| 
      
 619 
     | 
    
         
            +
                        TOKENMAP[@lexeme] || :symbol
         
     | 
| 
      
 620 
     | 
    
         
            +
                      else
         
     | 
| 
      
 621 
     | 
    
         
            +
                        # Must be a single character
         
     | 
| 
      
 622 
     | 
    
         
            +
                        @lexeme = @buffer[0]
         
     | 
| 
      
 623 
     | 
    
         
            +
                        @buffer[0] = ""
         
     | 
| 
      
 624 
     | 
    
         
            +
                        @lexeme
         
     | 
| 
      
 625 
     | 
    
         
            +
                      end
         
     | 
| 
      
 626 
     | 
    
         
            +
                    end
         
     | 
| 
      
 627 
     | 
    
         
            +
             
     | 
| 
      
 628 
     | 
    
         
            +
                    def peek
         
     | 
| 
      
 629 
     | 
    
         
            +
                      @lookahead ||= lex
         
     | 
| 
      
 630 
     | 
    
         
            +
                    end
         
     | 
| 
      
 631 
     | 
    
         
            +
             
     | 
| 
      
 632 
     | 
    
         
            +
                    def match(expected, estr = nil)
         
     | 
| 
      
 633 
     | 
    
         
            +
                      token = peek
         
     | 
| 
      
 634 
     | 
    
         
            +
                      @lookahead = nil
         
     | 
| 
      
 635 
     | 
    
         
            +
                      if token != expected
         
     | 
| 
      
 636 
     | 
    
         
            +
                        error("expected #{estr || expected}, got #{render(token)}" +
         
     | 
| 
      
 637 
     | 
    
         
            +
                              " (before `#{@buffer.lstrip}')")
         
     | 
| 
      
 638 
     | 
    
         
            +
                      end
         
     | 
| 
      
 639 
     | 
    
         
            +
                      @lexeme
         
     | 
| 
      
 640 
     | 
    
         
            +
                    end
         
     | 
| 
      
 641 
     | 
    
         
            +
             
     | 
| 
      
 642 
     | 
    
         
            +
                    def render(token)
         
     | 
| 
      
 643 
     | 
    
         
            +
                      extra = render_lexeme(token, @lexeme)
         
     | 
| 
      
 644 
     | 
    
         
            +
                      extra ? "#{token} #{extra}" : token
         
     | 
| 
      
 645 
     | 
    
         
            +
                    end
         
     | 
| 
      
 646 
     | 
    
         
            +
             
     | 
| 
      
 647 
     | 
    
         
            +
                    def render_lexeme(token, lexeme)
         
     | 
| 
      
 648 
     | 
    
         
            +
                      if token == :direction
         
     | 
| 
      
 649 
     | 
    
         
            +
                        lexeme
         
     | 
| 
      
 650 
     | 
    
         
            +
                      elsif token == :symbol
         
     | 
| 
      
 651 
     | 
    
         
            +
                        "\"#{lexeme}\""
         
     | 
| 
      
 652 
     | 
    
         
            +
                      elsif token == :percent
         
     | 
| 
      
 653 
     | 
    
         
            +
                        "'#{lexeme}%'"
         
     | 
| 
      
 654 
     | 
    
         
            +
                      else
         
     | 
| 
      
 655 
     | 
    
         
            +
                        nil
         
     | 
| 
      
 656 
     | 
    
         
            +
                      end
         
     | 
| 
      
 657 
     | 
    
         
            +
                    end
         
     | 
| 
      
 658 
     | 
    
         
            +
                  end
         
     | 
| 
      
 659 
     | 
    
         
            +
                end
         
     | 
| 
      
 660 
     | 
    
         
            +
              end
         
     | 
| 
      
 661 
     | 
    
         
            +
            end
         
     |