ifmapper 1.0.0 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
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