ifmapper 1.0.0 → 1.0.6

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.
Files changed (218) hide show
  1. data/HISTORY.txt +648 -627
  2. data/IFMapper.gemspec +29 -28
  3. data/IFMapper.rbw +31 -31
  4. data/TODO.txt +8 -7
  5. data/bin/IFMapper +31 -31
  6. data/docs/en/index.html +0 -0
  7. data/docs/en/start.html +3 -2
  8. data/docs/en/start.html~ +516 -0
  9. data/docs/es/index.html +0 -0
  10. data/docs/es/start.html +13 -14
  11. data/docs/es/start.html~ +1280 -0
  12. data/docs/images/IFMapper_main.gif +0 -0
  13. data/docs/images/automap.gif +0 -0
  14. data/docs/images/complex_connection.gif +0 -0
  15. data/docs/images/connection.gif +0 -0
  16. data/docs/images/connection_menu.gif +0 -0
  17. data/docs/images/room_description.gif +0 -0
  18. data/docs/images/room_small.gif +0 -0
  19. data/icons/copy.png +0 -0
  20. data/icons/cut.png +0 -0
  21. data/icons/filenew.png +0 -0
  22. data/icons/fileopen.png +0 -0
  23. data/icons/filesave.png +0 -0
  24. data/icons/filesaveas.png +0 -0
  25. data/icons/help.png +0 -0
  26. data/icons/kill.png +0 -0
  27. data/icons/nextpage.png +0 -0
  28. data/icons/paste.png +0 -0
  29. data/icons/prevpage.png +0 -0
  30. data/icons/printicon.png +0 -0
  31. data/icons/redo.png +0 -0
  32. data/icons/room_e.gif +0 -0
  33. data/icons/room_e.xpm +0 -0
  34. data/icons/room_n.gif +0 -0
  35. data/icons/room_n.xpm +0 -0
  36. data/icons/room_ne.gif +0 -0
  37. data/icons/room_ne.xpm +0 -0
  38. data/icons/room_nw.gif +0 -0
  39. data/icons/room_nw.xpm +0 -0
  40. data/icons/room_s.gif +0 -0
  41. data/icons/room_s.xpm +0 -0
  42. data/icons/room_se.gif +0 -0
  43. data/icons/room_se.xpm +0 -0
  44. data/icons/room_sw.gif +0 -0
  45. data/icons/room_sw.xpm +0 -0
  46. data/icons/room_w.gif +0 -0
  47. data/icons/room_w.xpm +0 -0
  48. data/icons/saveas.png +0 -0
  49. data/icons/undo.png +0 -0
  50. data/icons/winapp.png +0 -0
  51. data/icons/zoom.png +0 -0
  52. data/lib/IFMapper/AStar.rb +250 -250
  53. data/lib/IFMapper/Connection.rb +202 -202
  54. data/lib/IFMapper/FXAboutDialogBox.rb +32 -32
  55. data/lib/IFMapper/FXConnection.rb +364 -364
  56. data/lib/IFMapper/FXConnectionDialogBox.rb +124 -124
  57. data/lib/IFMapper/FXDCPostscript.rb +404 -404
  58. data/lib/IFMapper/FXDCPrint.rb +15 -15
  59. data/lib/IFMapper/FXItemList.rb +108 -0
  60. data/lib/IFMapper/FXMap.rb +2147 -2116
  61. data/lib/IFMapper/FXMapColorBox.rb +88 -88
  62. data/lib/IFMapper/FXMapDialogBox.rb +127 -127
  63. data/lib/IFMapper/FXMapFileDialog.rb +34 -34
  64. data/lib/IFMapper/FXMapperSettings.rb +206 -205
  65. data/lib/IFMapper/FXMapperWindow.rb +1592 -1571
  66. data/lib/IFMapper/FXPDFMapExporterOptionsDialogBox.rb +46 -0
  67. data/lib/IFMapper/FXRoom.rb +263 -263
  68. data/lib/IFMapper/FXRoomDialogBox.rb +159 -159
  69. data/lib/IFMapper/FXRoomList.rb +95 -95
  70. data/lib/IFMapper/FXSearchDialogBox.rb +51 -51
  71. data/lib/IFMapper/FXSection.rb +33 -33
  72. data/lib/IFMapper/FXSectionDialogBox.rb +38 -38
  73. data/lib/IFMapper/FXSpline.rb +52 -52
  74. data/lib/IFMapper/FXWarningBox.rb +51 -50
  75. data/lib/IFMapper/GUEReader.rb +445 -445
  76. data/lib/IFMapper/IFMReader.rb +584 -584
  77. data/lib/IFMapper/IFMWriter.rb +245 -227
  78. data/lib/IFMapper/Inform7Writer.rb +579 -573
  79. data/lib/IFMapper/InformReader.rb +478 -478
  80. data/lib/IFMapper/InformWriter.rb +364 -359
  81. data/lib/IFMapper/Map.rb +202 -200
  82. data/lib/IFMapper/MapPrinting.rb +162 -162
  83. data/lib/IFMapper/MapReader.rb +900 -900
  84. data/lib/IFMapper/PDFMapExporter.rb +526 -483
  85. data/lib/IFMapper/Room.rb +153 -151
  86. data/lib/IFMapper/Section.rb +234 -234
  87. data/lib/IFMapper/TADSReader.rb +474 -471
  88. data/lib/IFMapper/TADSWriter.rb +375 -370
  89. data/lib/IFMapper/TranscriptDialogBox.rb +0 -0
  90. data/lib/IFMapper/TranscriptReader.rb +1361 -1359
  91. data/lib/IFMapper/locales/en/Messages.rb +446 -435
  92. data/lib/IFMapper/locales/es/Messages.rb +451 -440
  93. data/lib/IFMapper/locales/es/Messages_iso-8859-1.rb +455 -440
  94. data/lib/IFMapper/locales/es/runme.sh +3 -3
  95. data/maps/A New Life.map b/data/maps/A New → Life.map +0 -0
  96. data/maps/AMFV.map +0 -0
  97. data/maps/AllRoads.map +0 -0
  98. data/maps/Aotearoa.map +0 -0
  99. data/maps/Bronze.map +0 -0
  100. data/maps/Bureaucracy.ifm +0 -0
  101. data/maps/Bureaucracy.map +0 -0
  102. data/maps/CityOfSecrets.map +0 -0
  103. data/maps/DDIV.map +0 -0
  104. data/maps/Following_A_Star.map +0 -0
  105. data/maps/Heated.map +0 -0
  106. data/maps/Heroine.map +0 -0
  107. data/maps/History Repeating.map b/data/maps/History → Repeating.map +0 -0
  108. data/maps/Hollywood_Hijinx.ifm +0 -0
  109. data/maps/Janitor.map +0 -0
  110. data/maps/Jigsaw.ifm +0 -0
  111. data/maps/Jigsaw.map +0 -0
  112. data/maps/LGOP.ifm +0 -0
  113. data/maps/Mercy.ifm +0 -0
  114. data/maps/Ninjas_Fate.map +0 -0
  115. data/maps/Pen_and_Paint.map +0 -0
  116. data/maps/Planetfall.ifm +0 -0
  117. data/maps/Planetfall.map +0 -0
  118. data/maps/Plundered_Hearts.ifm +0 -0
  119. data/maps/QuietEvening.map +0 -0
  120. data/maps/Ralph.ifm +0 -0
  121. data/maps/Reliques_of_Tolti_Alph.map +0 -0
  122. data/maps/Revolution.map +0 -0
  123. data/maps/Robots_of_Dawn.ifm +0 -0
  124. data/maps/SavoirFare.map +0 -0
  125. data/maps/Seastalker.ifm +0 -0
  126. data/maps/Seastalker.map +0 -0
  127. data/maps/Sherlock.ifm +0 -0
  128. data/maps/SoFar.ifm +0 -0
  129. data/maps/Starcross.ifm +0 -0
  130. data/maps/Suspended.ifm +0 -0
  131. data/maps/Tangle.map +0 -0
  132. data/maps/The_Lost_Sheep.map +0 -0
  133. data/maps/Unforgotten.map +0 -0
  134. data/maps/Warbler's Nest.map +0 -0
  135. data/maps/Warbler's_Nest.map +0 -0
  136. data/maps/Westminster_Abbey.map +0 -0
  137. data/maps/WinterWonderland.map +0 -0
  138. data/maps/Wishbringer.ifm +0 -0
  139. data/maps/Wishbringer2.ifm +0 -0
  140. data/maps/Zork1.ifm +0 -0
  141. data/maps/Zork2.ifm +0 -0
  142. data/maps/Zork3.ifm +0 -0
  143. data/maps/Zork_Zero.ifm +0 -0
  144. data/maps/anchor.ifm +0 -0
  145. data/maps/anchor.map +0 -0
  146. data/maps/atrox.ifm +0 -0
  147. data/maps/awaken.ifm +0 -0
  148. data/maps/babel.ifm +0 -0
  149. data/maps/balances.map +0 -0
  150. data/maps/ballerina.map +0 -0
  151. data/maps/bear.map +0 -0
  152. data/maps/bluechairs.map +0 -0
  153. data/maps/break_in.map +0 -0
  154. data/maps/bse.ifm +0 -0
  155. data/maps/building.map +0 -0
  156. data/maps/change.ifm +0 -0
  157. data/maps/christminster.map +0 -0
  158. data/maps/curses.ifm +0 -0
  159. data/maps/curves.ifm +0 -0
  160. data/maps/deadline.map +0 -0
  161. data/maps/delusions.map +0 -0
  162. data/maps/devours.map +0 -0
  163. data/maps/distress.map +0 -0
  164. data/maps/djinni.map +0 -0
  165. data/maps/dreamhold.map +0 -0
  166. data/maps/drift3.map +0 -0
  167. data/maps/eas.map +0 -0
  168. data/maps/eas2.map +0 -0
  169. data/maps/eas3.map +0 -0
  170. data/maps/edifice.ifm +0 -0
  171. data/maps/fallacy.map +0 -0
  172. data/maps/frozen.ifm +0 -0
  173. data/maps/gamlet.map +0 -0
  174. data/maps/glow.ifm +0 -0
  175. data/maps/guilty_bastards.map +0 -0
  176. data/maps/heist.map +0 -0
  177. data/maps/heroes.map +0 -0
  178. data/maps/inhumane.map +0 -0
  179. data/maps/kaged.map +0 -0
  180. data/maps/library.ifm +0 -0
  181. data/maps/lurkinghorror.map +0 -0
  182. data/maps/metamorphoses.map +0 -0
  183. data/maps/mindelec.ifm +0 -0
  184. data/maps/minster.ifm +0 -0
  185. data/maps/mite.map +0 -0
  186. data/maps/moonmist.map +0 -0
  187. data/maps/muldoon_legacy.map +0 -0
  188. data/maps/muse.ifm +0 -0
  189. data/maps/paperchase.ifm +0 -0
  190. data/maps/party.map +0 -0
  191. data/maps/pawn.map +0 -0
  192. data/maps/photograph.map +0 -0
  193. data/maps/pkgirl.map +0 -0
  194. data/maps/pytho.map +0 -0
  195. data/maps/risorgimento.map +0 -0
  196. data/maps/sherbet.map +0 -0
  197. data/maps/simple.map +0 -0
  198. data/maps/slouch.map +0 -0
  199. data/maps/space_st.ifm +0 -0
  200. data/maps/splashdown.map +0 -0
  201. data/maps/spring.map +0 -0
  202. data/maps/squarecircle.map +0 -0
  203. data/maps/stationfall.ifm +0 -0
  204. data/maps/theatre.ifm +0 -0
  205. data/maps/toonesia.ifm +0 -0
  206. data/maps/tortoise.ifm +0 -0
  207. data/maps/trinity.map +0 -0
  208. data/maps/vespers.map +0 -0
  209. data/maps/vgame.ifm +0 -0
  210. data/maps/wasp.map +0 -0
  211. data/maps/weather.ifm +0 -0
  212. data/maps/windhall.ifm +0 -0
  213. data/maps/worlds.map +0 -0
  214. data/maps/xtcontest.map +0 -0
  215. data/maps/zdungeon.map +0 -0
  216. data/maps/zebulon.ifm +0 -0
  217. data/maps/zerosum.map +0 -0
  218. metadata +226 -183
@@ -1,471 +1,474 @@
1
-
2
- require 'IFMapper/MapReader'
3
-
4
-
5
- # Take a quoted TADS string and return a valid ASCII one, replacing
6
- # TADS's special characters.
7
- module TADSUnquote
8
- def unquote(text)
9
- return '' unless text
10
- text.gsub!(/\\'/, "'") # restore quotes
11
- text.gsub!(/\\"/, '"') # restore quotes
12
- text.gsub!(/<<.*>>/, '') # remove embedded functions
13
- text.gsub!(/<\/?q>/, '"') # Change quotes
14
- return text
15
- end
16
- end
17
-
18
-
19
- #
20
- # Class that allows creating a map from an TADS source file.
21
- #
22
- class TADSReader < MapReader
23
-
24
- # TADS 3 directional properties need to be spelled fully
25
- DIRECTIONS = {
26
- 'north' => 0,
27
- 'northeast' => 1,
28
- 'east' => 2,
29
- 'southeast' => 3,
30
- 'south' => 4,
31
- 'southwest' => 5,
32
- 'west' => 6,
33
- 'northwest' => 7,
34
- 'up' => 8,
35
- 'down' => 9,
36
- 'in' => 10,
37
- 'out' => 11
38
- }
39
-
40
- FUNCTION = /^\[ (\w+);/
41
-
42
- GO_OBJ = /\b(#{DIRECTIONS.keys.join('|').gsub(/_to/, '_obj')})\s*:/i
43
-
44
- # SINGLE QUOTED STRING
45
- SQ = '((?:\\\\\'|[^\'])+)'
46
-
47
- # DOUBLE QUOTED STRING
48
- DQ = '((?:\\\"|[^"])+)'
49
-
50
- # Equal sign (TADS supports C or Pascal-like =)
51
- EQ = '\s*(?:=|\:=)\s*'
52
-
53
-
54
- DIR_EQ = "(#{DIRECTIONS.keys.join('|')})#{EQ}"
55
- DIR_TO = /(?:^|\s+)#{DIR_EQ}/
56
- DIR = /(?:^|\s+)#{DIR_EQ}(\w+)/
57
- DIR_OPEN = /(?:^|\s+)#{DIR_EQ}\(\s*[\d\w]+\.isOpen\s*\?\s*([\d\w]+)/
58
- DIR_INSELF = /(?:^|\s+)#{DIR_EQ}\(\s*[\d\w]+\.isIn\(\s*self\s*\)\s*\?\s*([\d\w]+)/
59
- DIR_MSG = /(?:^|\s+)(#{DIRECTIONS.keys.join('|')})\s*\:\s*TravelMessage\s*\{\s*\->\s*(\w+)/
60
-
61
- ENTER_DIR = /(?:^|\s+)(#{DIRECTIONS.keys.join('|')})\s*:\s*.*<<replaceAction\(Enter\s*,\s*(\w+)/i
62
-
63
- OBJLOCATION = /(?:^|\s+)(?:(?:destination|location)#{EQ}(\w+)|@(\w+)|locationList#{EQ}\[([\w\s,]+)\])/
64
- OBJNAME = /(?:^|\s+)name#{EQ}(?:'#{SQ}'|\(\s*described\s*\?\s*'#{SQ}')/
65
- CONNECTOR = /(?:^|\s+)room\d+\s*#{EQ}\s*(\w+)/
66
- ROOMNAME = /(?:^|\s+)roomName#{EQ}'#{SQ}'/
67
- DESCRIPTION = /(?:^|\s+)desc#{EQ}(?:\s*$|\s+"#{DQ}?("?))/
68
-
69
-
70
- # TADS3 template definitions are like:
71
- # [+] [TAG:] Classes ['adjective nouns'] 'Name' @location
72
- PLUS = '\s*([\+]+\s*)?'
73
- TAG = '(?:([\w\d]+)\s*:)?\s*'
74
- CLS = '([\w\s\-\>,]+)'
75
- NOMS = "(?:\'#{SQ}\')?"
76
- NAME = "(?:\s+\'#{SQ}\')?"
77
- LOC = '\s+(?:@([\d\w]+))?'
78
-
79
- CLASS = /^class\s+(\w+)\s*:\s*([\w,\s]+)/i
80
- DOOR = /(?:^|\s+)door_to(?:\s+([^,;]*)|$)/i
81
- INCLUDE = /^#include\s+[<"]([^">]+)[">]/
82
-
83
- STD_LIB = [
84
- 'adv3.h',
85
- 'en_us.h',
86
- 'bignum.h',
87
- 'array.h',
88
- 'tok.h',
89
- 'bytearr.h',
90
- 'charset.h',
91
- 'dict.h',
92
- 'reflect.h',
93
- 'gramprod.h',
94
- 'systype.h',
95
- 'tads.h',
96
- 'tadsgen.h',
97
- 'tadsio.h',
98
- 't3.h',
99
- 'vector.h',
100
- 'file.h',
101
- 't3test.h',
102
- 'strcomp.h',
103
- ]
104
-
105
-
106
- attr_reader :map
107
-
108
-
109
-
110
- include TADSUnquote
111
-
112
- class TADSRoom < MapRoom
113
- include TADSUnquote
114
- end
115
-
116
- class TADSObject < MapObject
117
- include TADSUnquote
118
- end
119
-
120
-
121
- def new_room
122
- super(TADSRoom)
123
- end
124
-
125
- def new_obj(loc = nil)
126
- super(loc, TADSObject)
127
- end
128
-
129
- #
130
- # Main parsing loop. We basically parse the file twice to
131
- # solve dependencies. Yes, this is inefficient, but the alternative
132
- # was to build a full parser that understands forward dependencies.
133
- #
134
- def parse(file)
135
- # We start map at 0, 0
136
- @x, @y = [0, 0]
137
- @room = nil
138
-
139
- if @map.kind_of?(FXMap)
140
- @map.options['Edit on Creation'] = false
141
- @map.window.hide
142
- end
143
- @map.section = 0
144
-
145
- @parsing = nil
146
- @last_section = 0
147
- @ignore_first_section = true
148
- @room_idx = 0
149
- line_number = 0
150
-
151
- debug "...Parse... #{file.path}"
152
- while not file.eof?
153
- @line = ''
154
- while not file.eof? and @line == ''
155
- @line << file.readline()
156
- @line.sub!( /^\s*\/\/.*$/, '')
157
- line_number += 1
158
- end
159
- # todo: Remove multi-line comments
160
- # Remove comments at end of line
161
- @line.sub!( /\s+\/\/[^"]*$/, '')
162
- # Remove starting spaces (if any)
163
- @line.sub! /^\s+/, ''
164
- # Replace \n with simple space
165
- @line.gsub! /\n/, ' '
166
- next if @line == ''
167
- full_line = @line.dup
168
- begin
169
- parse_line
170
- rescue ParseError, MapError => e
171
- message = "#{e}.\nat #{file.path}, line #{line_number}:\n>>>> #{full_line};\n"
172
- raise message
173
- rescue => e
174
- message = "#{e}\n#{e.backtrace}"
175
- raise message
176
- end
177
- end
178
- debug "...End Parse..."
179
- end
180
-
181
-
182
-
183
- def find_file(file)
184
- return file if File.exists?(file)
185
- @include_dirs.each { |d|
186
- [ "#{d}/#{file}",
187
- "#{d}/#{file}.h",
188
- "#{d}/#{file}.inf", ].each { |full|
189
- return full if File.exists?(full)
190
- }
191
- }
192
- return nil
193
- end
194
-
195
- def get_parent_classes(clist)
196
- ret = []
197
- clist.each { |c|
198
- c.strip!
199
- if @classes[c]
200
- ret += get_parent_classes(@classes[c])
201
- else
202
- ret << c
203
- end
204
- }
205
- return ret
206
- end
207
-
208
- #
209
- # Parse a line of file
210
- #
211
- def parse_line
212
- if @line =~ INCLUDE
213
- name = $1
214
- unless STD_LIB.include?(name)
215
- file = find_file(name)
216
- if file
217
- File.open(file, 'r') { |f| parse(f) }
218
- else
219
- raise ParseError, "Include file '#{name}' not found"
220
- end
221
- end
222
- end
223
-
224
- if @line =~ CLASS
225
- @clas = $1
226
- inherits = $2
227
- debug "CLASS: #@clas : #{inherits}"
228
- @classes[@clas] = inherits.split(/\s*,\s*/)
229
- @tag = @name = nil
230
- return
231
- end
232
-
233
-
234
-
235
- #
236
- # Handle room description
237
- #
238
- if @in_desc == 1 then
239
- if @line =~ /^\s*"#{DQ}("\s*)?$/
240
- # check for possible start description
241
- @room.desc << $1
242
- if $2
243
- @in_desc = nil
244
- else
245
- @in_desc = 2
246
- end
247
- return
248
- elsif @room.name == @room.tag and @line =~ /^\s*#{NOMS}#{NAME}\s*/
249
- name = $1 || $2
250
- @room.name = name
251
- end
252
- end
253
-
254
- if @in_desc == 2
255
- @line =~ /\s*#{DQ}?("\s*)?/
256
- @room.desc << $1 if @room and $1
257
- @in_desc = nil if $2
258
- return
259
- end
260
-
261
- if @gameid and @line =~ /name\s*=\s*'(#{SQ})'/
262
- @map.name = $1
263
- end
264
-
265
- # TADS3 definition is like:
266
- # [+] [TAG:] Classes ['adjective nouns'] 'Name' @location
267
- re = /^#{PLUS}#{TAG}#{CLS}#{NOMS}#{NAME}#{LOC}/
268
- if @line =~ re
269
-
270
-
271
- prev = $1
272
- @name = [$5, $4]
273
- @tag = $2
274
- @clas = $3 # can be several classes
275
- @in_desc = false
276
-
277
- if @tag
278
- if @clas == "GameID"
279
- @gameid = true
280
- else
281
- @gameid = false
282
- end
283
- end
284
-
285
- loc = $6
286
- if prev and @room
287
- loc = @room.tag
288
- end
289
-
290
- ## For user classes, flatten inheritance
291
- clist = @clas.split(/(?:\s*,\s*|\s)/)
292
- flist = get_parent_classes(clist)
293
- @clas = flist.join(' ')
294
-
295
- if @clas =~ /\b(Outdoor|Dark)?Room\b/
296
- @obj = nil
297
- @name = @name[1] || @name[0] || @tag
298
- debug "+++ ROOM #@name TAG: #@tag"
299
- @tag = @name if not @tag
300
- @desc = ''
301
- new_room
302
- @room.light = false if $1 == 'Dark'
303
- @in_desc = 1
304
- elsif @clas =~ /\bFakeConnector\b/
305
- @functions << @tag
306
- elsif @clas =~ /\bRoomConnector\b/
307
- debug "+++ CONNECTOR TAG: #@tag"
308
- @desc = ''
309
- new_door(loc)
310
- @obj.connector = true
311
- elsif @clas =~ /\b(?:(?:Hidden|Secret)?Door|ThroughPassage|PathPassage|TravelWithMessage|Stairway(?:Up|Down))\b/
312
- @desc = ''
313
- @name = @name[0] || @name[1]
314
- @tag = @name if not @tag
315
- debug "+++ DOOR #@tag"
316
- if @clas =~ /\s+->\s*(\w+)/
317
- # this is the other side of the door... find matching side
318
- @obj = @tags[$1]
319
- if not @obj
320
- new_door(loc)
321
- @tags[$1] = @obj
322
- else
323
- @tags[@tag] = @obj
324
- @obj.location << loc if loc
325
- end
326
- else
327
- new_door(loc) if @tag
328
- @obj.locked = true if @clas =~ /\bLockable(WithKey)?\b/
329
- end
330
- @obj.connector = true if @clas !~ /\b(?:Hidden|Secret)?Door\b/
331
- elsif @clas =~ /\b(?:Thing|Food|Person)\b/
332
- @obj = nil
333
- @desc = ''
334
- @name = @name[0] || @name[1]
335
- @name = '' if @name =~ /\//
336
- @tag = @name if not @tag
337
- new_obj(loc) if @tag
338
- elsif @clas =~ /\bEnterable\b/ and @tag
339
- @desc = ''
340
- @name = @name[0] || @name[1]
341
- @name = '' if @name =~ /\//
342
- @tag = @name if not @tag
343
- new_obj(loc)
344
- if @clas =~ /\s+->\s*(\w+)/
345
- @obj.enterable << $1
346
- end
347
- else
348
- if @tag
349
- @obj = nil
350
- end
351
- @name = (@name[0] || @name[1] || @tag)
352
- end
353
-
354
- # debug <<"EOF"
355
- # Name: #@name
356
- # Classes : #@clas
357
- # Tag : #@tag
358
- # Location: #{loc} #{loc.class}
359
- # EOF
360
-
361
-
362
- end
363
-
364
- if @obj
365
- if @obj.name == '' and @line =~ /^\s*#{NOMS}#{NAME}\s*$/
366
- name = $2 || $1
367
- @obj.name = name
368
- end
369
- @obj.name = $1 || $2 if @line =~ OBJNAME
370
- if @line =~ OBJLOCATION
371
- loc = $1 || $2 || $3
372
- if loc
373
- locs = loc.split(/\s*,\s*/)
374
- @obj.location += locs
375
- @obj.location.uniq!
376
- end
377
- end
378
- @obj.location << loc if @obj.connector and @line =~ CONNECTOR
379
- end
380
-
381
- if @room and @line =~ ROOMNAME
382
- @room.name = $1
383
- return
384
- end
385
-
386
- if @line =~ DESCRIPTION
387
- @desc << $1 if @desc and $1
388
- @in_desc = 2 if $2 == ''
389
- end
390
-
391
- # dirs = @line.scan(DIR_TO) + @line.scan(DIR) + @line.scan(DIR_MSG)
392
- if @room
393
- dirs = ( @line.scan(DIR) + @line.scan(DIR_MSG) +
394
- @line.scan(DIR_OPEN) + @line.scan(DIR_INSELF) +
395
- @line.scan(ENTER_DIR) )
396
- if dirs.size > 0
397
- dirs.each { |d, room|
398
- next if not room or room == 'nil' or room =~ /^noTravel/
399
- dir = DIRECTIONS[d]
400
- @room.exits[dir] = room
401
- }
402
- end
403
- end
404
-
405
- end
406
-
407
-
408
-
409
-
410
- def parse_makefile(file)
411
- dir = File.dirname(file)
412
- files = []
413
- File.foreach(file) { |line|
414
- if line =~ /-source\s*([^\s]+)/
415
- f = $1
416
- if f !~ /^(?:[A-Z]:|\/)/
417
- f = dir + '/' + f
418
- end
419
- files << "#{f}.t"
420
- end
421
- }
422
-
423
- return files
424
- end
425
-
426
- def set_include_dirs
427
- # Try to find t3make(.exe) in path.
428
- paths = ENV['PATH'].split(SEP)
429
- paths.each { |p|
430
- next if not File.directory?(p)
431
- Dir.foreach(p) { |x|
432
- if x =~ /^t3make(.exe)?$/i
433
- @include_dirs << p
434
- @include_dirs << p + "/include"
435
- @include_dirs << p + "/lib"
436
- @include_dirs << p + "/contrib"
437
- break
438
- end
439
- }
440
- }
441
- end
442
-
443
- def read_file(file)
444
- if file =~ /.t3m/
445
- files = parse_makefile(file)
446
- else
447
- files = file
448
- end
449
-
450
- files.each_with_index { |file, idx|
451
- super(file)
452
- }
453
- end
454
-
455
- def initialize(file, map = Map.new('TADS Map'))
456
- debug "Initialize"
457
- @classes = { 'Object' => {} }
458
- super
459
- end
460
- end
461
-
462
-
463
- if $0 == __FILE__
464
- p "Opening file '#{ARGV[0]}'"
465
- BEGIN {
466
- $LOAD_PATH << 'C:\Windows\Escritorio\IFMapper\lib'
467
- }
468
-
469
- require "IFMapper/Map"
470
- TADSReader.new(ARGV[0])
471
- end
1
+
2
+ require 'IFMapper/MapReader'
3
+
4
+
5
+ # Take a quoted TADS string and return a valid ASCII one, replacing
6
+ # TADS's special characters.
7
+ module TADSUnquote
8
+ def unquote(text)
9
+ return '' unless text
10
+ text.gsub!(/\\'/, "'") # restore quotes
11
+ text.gsub!(/\\"/, '"') # restore quotes
12
+ text.gsub!(/<<.*>>/, '') # remove embedded functions
13
+ text.gsub!(/<\/?q>/, '"') # Change quotes
14
+ return text
15
+ end
16
+ end
17
+
18
+
19
+ #
20
+ # Class that allows creating a map from an TADS source file.
21
+ #
22
+ class TADSReader < MapReader
23
+
24
+ # TADS 3 directional properties need to be spelled fully
25
+ DIRECTIONS = {
26
+ 'north' => 0,
27
+ 'northeast' => 1,
28
+ 'east' => 2,
29
+ 'southeast' => 3,
30
+ 'south' => 4,
31
+ 'southwest' => 5,
32
+ 'west' => 6,
33
+ 'northwest' => 7,
34
+ 'up' => 8,
35
+ 'down' => 9,
36
+ 'in' => 10,
37
+ 'out' => 11
38
+ }
39
+
40
+ FUNCTION = /^\[ (\w+);/
41
+
42
+ GO_OBJ = /\b(#{DIRECTIONS.keys.join('|').gsub(/_to/, '_obj')})\s*:/i
43
+
44
+ # SINGLE QUOTED STRING
45
+ SQ = '((?:\\\\\'|[^\'])+)'
46
+
47
+ # DOUBLE QUOTED STRING
48
+ DQ = '((?:\\\"|[^"])+)'
49
+
50
+ # Equal sign (TADS supports C or Pascal-like =)
51
+ EQ = '\s*(?:=|\:=)\s*'
52
+
53
+
54
+ DIR_EQ = "(#{DIRECTIONS.keys.join('|')})#{EQ}"
55
+ DIR_TO = /(?:^|\s+)#{DIR_EQ}/
56
+ DIR = /(?:^|\s+)#{DIR_EQ}(\w+)/
57
+ DIR_OPEN = /(?:^|\s+)#{DIR_EQ}\(\s*[\d\w]+\.isOpen\s*\?\s*([\d\w]+)/
58
+ DIR_INSELF = /(?:^|\s+)#{DIR_EQ}\(\s*[\d\w]+\.isIn\(\s*self\s*\)\s*\?\s*([\d\w]+)/
59
+ DIR_MSG = /(?:^|\s+)(#{DIRECTIONS.keys.join('|')})\s*\:\s*TravelMessage\s*\{\s*\->\s*(\w+)/
60
+
61
+ ENTER_DIR = /(?:^|\s+)(#{DIRECTIONS.keys.join('|')})\s*:\s*.*<<replaceAction\(Enter\s*,\s*(\w+)/i
62
+
63
+ OBJLOCATION = /(?:^|\s+)(?:(?:destination|location)#{EQ}(\w+)|@(\w+)|locationList#{EQ}\[([\w\s,]+)\])/
64
+ OBJNAME = /(?:^|\s+)name#{EQ}(?:'#{SQ}'|\(\s*described\s*\?\s*'#{SQ}')/
65
+ CONNECTOR = /(?:^|\s+)room\d+\s*#{EQ}\s*(\w+)/
66
+ ROOMNAME = /(?:^|\s+)roomName#{EQ}'#{SQ}'/
67
+ DESCRIPTION = /(?:^|\s+)desc#{EQ}(?:\s*$|\s+"#{DQ}?("?))/
68
+
69
+
70
+ # TADS3 template definitions are like:
71
+ # [+] [TAG:] Classes ['adjective nouns'] 'Name' @location
72
+ PLUS = '\s*([\+]+\s*)?'
73
+ TAG = '(?:([\w\d]+)\s*:)?\s*'
74
+ CLS = '([\w\s\-\>,]+)'
75
+ NOMS = "(?:\'#{SQ}\')?"
76
+ NAME = "(?:\s+\'#{SQ}\')?"
77
+ LOC = '\s+(?:@([\d\w]+))?'
78
+
79
+ CLASS = /^class\s+(\w+)\s*:\s*([\w,\s]+)/i
80
+ DOOR = /(?:^|\s+)door_to(?:\s+([^,;]*)|$)/i
81
+ INCLUDE = /^#include\s+[<"]([^">]+)[">]/
82
+
83
+ STD_LIB = [
84
+ 'adv3.h',
85
+ 'en_us.h',
86
+ 'bignum.h',
87
+ 'array.h',
88
+ 'tok.h',
89
+ 'bytearr.h',
90
+ 'charset.h',
91
+ 'dict.h',
92
+ 'reflect.h',
93
+ 'gramprod.h',
94
+ 'systype.h',
95
+ 'tads.h',
96
+ 'tadsgen.h',
97
+ 'tadsio.h',
98
+ 't3.h',
99
+ 'vector.h',
100
+ 'file.h',
101
+ 't3test.h',
102
+ 'strcomp.h',
103
+ ]
104
+
105
+
106
+ attr_reader :map
107
+
108
+
109
+
110
+ include TADSUnquote
111
+
112
+ class TADSRoom < MapRoom
113
+ include TADSUnquote
114
+ end
115
+
116
+ class TADSObject < MapObject
117
+ include TADSUnquote
118
+ end
119
+
120
+
121
+ def new_room
122
+ super(TADSRoom)
123
+ end
124
+
125
+ def new_obj(loc = nil)
126
+ super(loc, TADSObject)
127
+ end
128
+
129
+ #
130
+ # Main parsing loop. We basically parse the file twice to
131
+ # solve dependencies. Yes, this is inefficient, but the alternative
132
+ # was to build a full parser that understands forward dependencies.
133
+ #
134
+ def parse(file)
135
+ # We start map at 0, 0
136
+ @x, @y = [0, 0]
137
+ @room = nil
138
+
139
+ if @map.kind_of?(FXMap)
140
+ @map.options['Edit on Creation'] = false
141
+ @map.window.hide
142
+ end
143
+ @map.section = 0
144
+
145
+ @in_desc = false
146
+ @parsing = nil
147
+ @last_section = 0
148
+ @ignore_first_section = true
149
+ @room_idx = 0
150
+ @gameid = nil
151
+ @obj = nil
152
+ line_number = 0
153
+
154
+ debug "...Parse... #{file.path}"
155
+ while not file.eof?
156
+ @line = ''
157
+ while not file.eof? and @line == ''
158
+ @line << file.readline()
159
+ @line.sub!( /^\s*\/\/.*$/, '')
160
+ line_number += 1
161
+ end
162
+ # todo: Remove multi-line comments
163
+ # Remove comments at end of line
164
+ @line.sub!( /\s+\/\/[^"]*$/, '')
165
+ # Remove starting spaces (if any)
166
+ @line.sub! /^\s+/, ''
167
+ # Replace \n with simple space
168
+ @line.gsub! /\n/, ' '
169
+ next if @line == ''
170
+ full_line = @line.dup
171
+ begin
172
+ parse_line
173
+ rescue ParseError, MapError => e
174
+ message = "#{e}.\nat #{file.path}, line #{line_number}:\n>>>> #{full_line};\n"
175
+ raise message
176
+ rescue => e
177
+ message = "#{e}\n#{e.backtrace}"
178
+ raise message
179
+ end
180
+ end
181
+ debug "...End Parse..."
182
+ end
183
+
184
+
185
+
186
+ def find_file(file)
187
+ return file if File.exists?(file)
188
+ @include_dirs.each { |d|
189
+ [ "#{d}/#{file}",
190
+ "#{d}/#{file}.h",
191
+ "#{d}/#{file}.inf", ].each { |full|
192
+ return full if File.exists?(full)
193
+ }
194
+ }
195
+ return nil
196
+ end
197
+
198
+ def get_parent_classes(clist)
199
+ ret = []
200
+ clist.each { |c|
201
+ c.strip!
202
+ if @classes[c]
203
+ ret += get_parent_classes(@classes[c])
204
+ else
205
+ ret << c
206
+ end
207
+ }
208
+ return ret
209
+ end
210
+
211
+ #
212
+ # Parse a line of file
213
+ #
214
+ def parse_line
215
+ if @line =~ INCLUDE
216
+ name = $1
217
+ unless STD_LIB.include?(name)
218
+ file = find_file(name)
219
+ if file
220
+ File.open(file, 'r') { |f| parse(f) }
221
+ else
222
+ raise ParseError, "Include file '#{name}' not found"
223
+ end
224
+ end
225
+ end
226
+
227
+ if @line =~ CLASS
228
+ @clas = $1
229
+ inherits = $2
230
+ debug "CLASS: #@clas : #{inherits}"
231
+ @classes[@clas] = inherits.split(/\s*,\s*/)
232
+ @tag = @name = nil
233
+ return
234
+ end
235
+
236
+
237
+
238
+ #
239
+ # Handle room description
240
+ #
241
+ if @in_desc == 1 then
242
+ if @line =~ /^\s*"#{DQ}("\s*)?$/
243
+ # check for possible start description
244
+ @room.desc << $1
245
+ if $2
246
+ @in_desc = nil
247
+ else
248
+ @in_desc = 2
249
+ end
250
+ return
251
+ elsif @room.name == @room.tag and @line =~ /^\s*#{NOMS}#{NAME}\s*/
252
+ name = $1 || $2
253
+ @room.name = name
254
+ end
255
+ end
256
+
257
+ if @in_desc == 2
258
+ @line =~ /\s*#{DQ}?("\s*)?/
259
+ @room.desc << $1 if @room and $1
260
+ @in_desc = nil if $2
261
+ return
262
+ end
263
+
264
+ if @gameid and @line =~ /name\s*=\s*'(#{SQ})'/
265
+ @map.name = $1
266
+ end
267
+
268
+ # TADS3 definition is like:
269
+ # [+] [TAG:] Classes ['adjective nouns'] 'Name' @location
270
+ re = /^#{PLUS}#{TAG}#{CLS}#{NOMS}#{NAME}#{LOC}/
271
+ if @line =~ re
272
+
273
+
274
+ prev = $1
275
+ @name = [$5, $4]
276
+ @tag = $2
277
+ @clas = $3 # can be several classes
278
+ @in_desc = false
279
+
280
+ if @tag
281
+ if @clas == "GameID"
282
+ @gameid = true
283
+ else
284
+ @gameid = false
285
+ end
286
+ end
287
+
288
+ loc = $6
289
+ if prev and @room
290
+ loc = @room.tag
291
+ end
292
+
293
+ ## For user classes, flatten inheritance
294
+ clist = @clas.split(/(?:\s*,\s*|\s)/)
295
+ flist = get_parent_classes(clist)
296
+ @clas = flist.join(' ')
297
+
298
+ if @clas =~ /\b(Outdoor|Dark)?Room\b/
299
+ @obj = nil
300
+ @name = @name[1] || @name[0] || @tag
301
+ debug "+++ ROOM #@name TAG: #@tag"
302
+ @tag = @name if not @tag
303
+ @desc = ''
304
+ new_room
305
+ @room.light = false if $1 == 'Dark'
306
+ @in_desc = 1
307
+ elsif @clas =~ /\bFakeConnector\b/
308
+ @functions << @tag
309
+ elsif @clas =~ /\bRoomConnector\b/
310
+ debug "+++ CONNECTOR TAG: #@tag"
311
+ @desc = ''
312
+ new_door(loc)
313
+ @obj.connector = true
314
+ elsif @clas =~ /\b(?:(?:Hidden|Secret)?Door|ThroughPassage|PathPassage|TravelWithMessage|Stairway(?:Up|Down))\b/
315
+ @desc = ''
316
+ @name = @name[0] || @name[1]
317
+ @tag = @name if not @tag
318
+ debug "+++ DOOR #@tag"
319
+ if @clas =~ /\s+->\s*(\w+)/
320
+ # this is the other side of the door... find matching side
321
+ @obj = @tags[$1]
322
+ if not @obj
323
+ new_door(loc)
324
+ @tags[$1] = @obj
325
+ else
326
+ @tags[@tag] = @obj
327
+ @obj.location << loc if loc
328
+ end
329
+ else
330
+ new_door(loc) if @tag
331
+ @obj.locked = true if @clas =~ /\bLockable(WithKey)?\b/
332
+ end
333
+ @obj.connector = true if @clas !~ /\b(?:Hidden|Secret)?Door\b/
334
+ elsif @clas =~ /\b(?:Thing|Food|Person)\b/
335
+ @obj = nil
336
+ @desc = ''
337
+ @name = @name[0] || @name[1]
338
+ @name = '' if @name =~ /\//
339
+ @tag = @name if not @tag
340
+ new_obj(loc) if @tag
341
+ elsif @clas =~ /\bEnterable\b/ and @tag
342
+ @desc = ''
343
+ @name = @name[0] || @name[1]
344
+ @name = '' if @name =~ /\//
345
+ @tag = @name if not @tag
346
+ new_obj(loc)
347
+ if @clas =~ /\s+->\s*(\w+)/
348
+ @obj.enterable << $1
349
+ end
350
+ else
351
+ if @tag
352
+ @obj = nil
353
+ end
354
+ @name = (@name[0] || @name[1] || @tag)
355
+ end
356
+
357
+ # debug <<"EOF"
358
+ # Name: #@name
359
+ # Classes : #@clas
360
+ # Tag : #@tag
361
+ # Location: #{loc} #{loc.class}
362
+ # EOF
363
+
364
+
365
+ end
366
+
367
+ if @obj
368
+ if @obj.name == '' and @line =~ /^\s*#{NOMS}#{NAME}\s*$/
369
+ name = $2 || $1
370
+ @obj.name = name
371
+ end
372
+ @obj.name = $1 || $2 if @line =~ OBJNAME
373
+ if @line =~ OBJLOCATION
374
+ loc = $1 || $2 || $3
375
+ if loc
376
+ locs = loc.split(/\s*,\s*/)
377
+ @obj.location += locs
378
+ @obj.location.uniq!
379
+ end
380
+ end
381
+ @obj.location << loc if @obj.connector and @line =~ CONNECTOR
382
+ end
383
+
384
+ if @room and @line =~ ROOMNAME
385
+ @room.name = $1
386
+ return
387
+ end
388
+
389
+ if @line =~ DESCRIPTION
390
+ @desc << $1 if @desc and $1
391
+ @in_desc = 2 if $2 == ''
392
+ end
393
+
394
+ # dirs = @line.scan(DIR_TO) + @line.scan(DIR) + @line.scan(DIR_MSG)
395
+ if @room
396
+ dirs = ( @line.scan(DIR) + @line.scan(DIR_MSG) +
397
+ @line.scan(DIR_OPEN) + @line.scan(DIR_INSELF) +
398
+ @line.scan(ENTER_DIR) )
399
+ if dirs.size > 0
400
+ dirs.each { |d, room|
401
+ next if not room or room == 'nil' or room =~ /^noTravel/
402
+ dir = DIRECTIONS[d]
403
+ @room.exits[dir] = room
404
+ }
405
+ end
406
+ end
407
+
408
+ end
409
+
410
+
411
+
412
+
413
+ def parse_makefile(file)
414
+ dir = File.dirname(file)
415
+ files = []
416
+ File.foreach(file) { |line|
417
+ if line =~ /-source\s*([^\s]+)/
418
+ f = $1
419
+ if f !~ /^(?:[A-Z]:|\/)/
420
+ f = dir + '/' + f
421
+ end
422
+ files << "#{f}.t"
423
+ end
424
+ }
425
+
426
+ return files
427
+ end
428
+
429
+ def set_include_dirs
430
+ # Try to find t3make(.exe) in path.
431
+ paths = ENV['PATH'].split(SEP)
432
+ paths.each { |p|
433
+ next if not File.directory?(p)
434
+ Dir.foreach(p) { |x|
435
+ if x =~ /^t3make(.exe)?$/i
436
+ @include_dirs << p
437
+ @include_dirs << p + "/include"
438
+ @include_dirs << p + "/lib"
439
+ @include_dirs << p + "/contrib"
440
+ break
441
+ end
442
+ }
443
+ }
444
+ end
445
+
446
+ def read_file(file)
447
+ if file =~ /.t3m/
448
+ files = parse_makefile(file)
449
+ else
450
+ files = file
451
+ end
452
+
453
+ files.each_with_index { |file, idx|
454
+ super(file)
455
+ }
456
+ end
457
+
458
+ def initialize(file, map = Map.new('TADS Map'))
459
+ debug "Initialize"
460
+ @classes = { 'Object' => {} }
461
+ super
462
+ end
463
+ end
464
+
465
+
466
+ if $0 == __FILE__
467
+ p "Opening file '#{ARGV[0]}'"
468
+ BEGIN {
469
+ $LOAD_PATH << 'C:\Windows\Escritorio\IFMapper\lib'
470
+ }
471
+
472
+ require "IFMapper/Map"
473
+ TADSReader.new(ARGV[0])
474
+ end