-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathemit.os
479 lines (414 loc) · 18.4 KB
/
emit.os
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
!
! BRACY/EMIT. Write fragments of HTML code.
!
! Copyright © 2014 James B. Moen.
!
! This program is free software: you can redistribute it and/or modify it
! under the terms of the GNU General Public License as published by the Free
! Software Foundation, either version 3 of the License, or (at your option)
! any later version.
!
! This program is distributed in the hope that it will be useful, but WITHOUT
! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
! FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
! more details.
!
! You should have received a copy of the GNU General Public License along with
! this program. If not, see <http://www.gnu.org/licenses/>.
!
(prog
! Constants.
string ampersand :− ''&'' ! An HTML '&'.
string blank :− '' '' ! An HTML nonbreaking ' '.
string closeQuote :− ''’'' ! An HTML '’'.
string greaterThan :− ''>'' ! An HTML '>'.
int htmlIndentChange :− 1 ! Indent HTML scopes this much.
int htmlParagraphIndent :− 5 ! Indent HTML paras this much.
string lessThan :− ''<'' ! An HTML '<'.
int maxHtmlLength :− 79 ! Max length of an HTML line.
string openQuote :− ''‘'' ! An HTML '‘'.
string uparrow :− ''↑'' ! An HTML '↑'.
! Spacers. These hold "invisible" chars and their styles.
var [maxSourceLineLength] char spacerCharsBuffer
var [maxSourceLineLength] set spacerStylesBuffer
row var char spacerChars :− spacerCharsBuffer↓{row var char}
row var set spacerStyles :− spacerStylesBuffer↓{row var set}
! Variables.
var codeStack(maxCode) codes ! Current codes.
var int htmlRemaining :− maxHtmlLength ! Chars left on line.
var int htmlIndent :− 0 ! Current indenting.
! AT LINE START. Test if we're about to write at the start of the current HTML
! line.
atLineStart :−
(form () bool:
htmlRemaining = maxHtmlLength)
! EMIT BLANKS. Write COUNT blanks to OUTPUT. If COUNT is less than or equal to
! zero, then write nothing.
emitBlanks :−
(proc (stream output, int count) void:
(in count
do write(output, ' ')))
! EMIT CHAR AND STYLE. Write the char CH using style codes in STYLE. Write non
! ASCII chars as &#ddd; and hope the browser likes them.
emitCharAndStyle :−
(form (char ch, set style) void:
(for int code in elements(codes.style − style)
do emitTurnOff(code))
(for int code in elements(style − codes.style)
do emitTurnOn(code))
(if isAscii(ch)
then emitAsciiCharAndStyle(ch, style)
else write(html, ''&#%i;'': ch{int})))
! EMIT ASCII CHAR AND STYLE. Write the ASCII char CH, using codes in STYLE. We
! sanitize CH if it looks like HTML punctuation. Ignore QUOTING and UPARROWING
! flags if the QUOTED style is in effect.
emitAsciiCharAndStyle :−
(form (char ch, set style) void:
(if capitalCode ∊ style ∧ isLetter(ch)
then write(html, upper(ch))
else if quotedCode ∊ style
then (case ch
of ' ': write(html, blank)
'&': write(html, ampersand)
'<': write(html, lessThan)
'>': write(html, greaterThan)
none: write(html, ch))
else (case ch
of ' ': write(html, blank)
'&': write(html, ampersand)
'\'': write(html, (if quoting then closeQuote else '''''))
'<': write(html, lessThan)
'>': write(html, greaterThan)
'^': write(html, (if uparrowing then uparrow else ''^''))
'`': write(html, (if quoting then openQuote else ''`''))
none: write(html, ch))))
! EMIT CLOSING TAG. Write TAG prefixed by a slash and surrounded by <>'s. It's
! indented and appears on a line by itself.
emitClosingTag :−
(form (string tag) void:
(if ¬ atLineStart()
then writeln(html)
htmlRemaining := maxHtmlLength)
htmlIndent −= htmlIndentChange
emitBlanks(html, htmlIndent)
writeln(html, ''</%s>'': tag)
htmlRemaining := maxHtmlLength)
! EMIT FIRST INDENTED LINE. Emit HTML code to display ITEM, which is the first
! line in an INDENTED scope. We make a copy of ITEM in the spacer buffer, then
! write ITEM itself. Turn off all ITEM's styles when we're done. The resulting
! HTML line may be longer than MAX HTML LENGTH.
emitFirstIndentedLine :−
(form (ref object item) void:
(with
ref object item :− (past item)
var string lineCharsStart :− item{ref word}↑.chars
var row set lineStylesStart :− item{ref word}↑.styles
var row var char spacerCharsEnd :− spacerChars
var row var set spacerStylesEnd :− spacerStyles
do (while head(lineCharsStart)
do spacerCharsEnd↑ := head(lineCharsStart)
spacerStylesEnd↑ := head(lineStylesStart) − grayCode
lineCharsStart := tail(lineCharsStart)
lineStylesStart := tail(lineStylesStart)
spacerCharsEnd := tail(spacerCharsEnd)
spacerStylesEnd := tail(spacerStylesEnd))
spacerCharsEnd↑ := ' '
spacerStylesEnd↑ := ∅
emitWord(item, nil)
writeln(html)
htmlRemaining := maxHtmlLength))
! EMIT HERALD. Write an HTML comment that includes this program's name and the
! time at which it started running. For example:
!
! <!-- Created by Bracy 0.1 on Sunday, June 15, 2008 at 3:55 AM CDT -->
!
! This is essentially the example from the Orson library file TIME.OS.
emitHerald :−
(form () void:
(for
int second, int minute, int hour, string ampm,
int date, string month, int year, string day, string zone
in decoded(now())
do writeln(html,
''<!-- Created by Bracy %s on %s, %s %i, %i at %i:%02i %s %s -->'':
version, day, month, date, year, hour, minute, ampm, zone)))
! EMIT LINE. Write LEFT, which points to a WORD containing an entire line.
emitLine :−
(form (ref object left) void:
(with ref object left :− (past left)
do (if head(left{ref word}↑.chars)
then emitWord(left, nil))))
! EMIT LONE TAG. Write TAG followed by ATTRIBUTES, surrounded by <>'s. It's on
! a line by itself and indented. If OBJECTS is nonempty then we use ATTRIBUTES
! as a format string to write its elements.
emitLoneTag :−
(alt
(form (string tag) void:
emitLoneTag(tag, ϵ:)),
(form (string tag, string attributes) void:
emitLoneTag(tag, attributes:)),
(form (string tag, string attributes, list objects) void:
(if ¬ atLineStart()
then writeln(html))
emitBlanks(html, htmlIndent)
(if attributes = ϵ
then writeln(html, '<' & tag & '>')
else if isEmpty(objects)
then writeln(html, '<' & tag & ' ' & attributes & '>')
else writeln(html, '<' & tag & ' ' & attributes & '>', objects))
htmlRemaining := maxHtmlLength))
! EMIT NEXT INDENTED LINE. Emit HTML code to display ITEM, the second or later
! line in an INDENTED scope. The HTML code may be longer than MAX HTML LENGTH.
emitNextIndentedLine :−
(form (ref object item) void:
(with
ref object item :− (past item)
var string lineCharsStart :− item{ref word}↑.chars
var row set lineStylesStart :− item{ref word}↑.styles
var row var char spacerCharsStart :− spacerChars
var row var set spacerStylesStart :− spacerStyles
var row var char spacerCharsEnd :− spacerChars
var row var set spacerStylesEnd :− spacerStyles
var bool updating :− true
! UPDATE SPACER BUFFER. Update the spacer buffer with chars from ITEM.
updateSpacerBuffer :−
(form () void:
(while head(lineCharsStart)
do (if updating
then (if head(lineCharsStart) ≠ ' '
then updating := false
spacerCharsEnd↑ := head(lineCharsStart)
spacerStylesEnd↑ := head(lineStylesStart) − grayCode)
else spacerCharsEnd↑ := head(lineCharsStart)
spacerStylesEnd↑ := head(lineStylesStart) − grayCode)
lineCharsStart := tail(lineCharsStart)
lineStylesStart := tail(lineStylesStart)
spacerCharsEnd := tail(spacerCharsEnd)
spacerStylesEnd := tail(spacerStylesEnd))
spacerCharsEnd↑ := ' '
spacerStylesEnd↑ := ∅)
! EMIT INVISIBLE. Emit invisible indenting chars from the spacer buffer, which
! have the same color as the BACKGROUND.
emitInvisible :−
(form () void:
lineCharsStart := item{ref word}↑.chars
lineStylesStart := item{ref word}↑.styles
emitBlanks(html, htmlIndent)
(if head(lineCharsStart) = ' '
then write(html, ''<font color="#'' & background & ''">'')
(while head(lineCharsStart) = ' '
do emitCharAndStyle(
head(spacerCharsStart),
head(spacerStylesStart))
lineCharsStart := tail(lineCharsStart)
lineStylesStart := tail(lineStylesStart)
spacerCharsStart := tail(spacerCharsStart)
spacerStylesStart := tail(spacerStylesStart))
emitTurnOff()
write(html, ''</font>'')))
! EMIT VISIBLE. Emit visible HTML code for the chars of ITEM after its leading
! blanks. Turn off all styles when we're done.
emitVisible :−
(form () void:
(while head(lineCharsStart)
do emitCharAndStyle(head(lineCharsStart), head(lineStylesStart))
lineCharsStart := tail(lineCharsStart)
lineStylesStart := tail(lineStylesStart))
emitTurnOff()
htmlRemaining := maxHtmlLength
writeln(html))
! This is EMIT NEXT INDENTED LINE's body. If ITEM is empty, then emit nothing.
do (if lineCharsStart↑
then updateSpacerBuffer()
emitInvisible()
emitVisible())))
! EMIT OPENING TAG. Write TAG followed by ATTRIBUTES, surrounded by <>'s. It's
! indented and appears on a line by itself. If OBJECTS is nonempty then we use
! ATTRIBUTES as a format string for the members of the list.
emitOpeningTag :−
(form (string tag, string attributes, list objects) void:
(if ¬ atLineStart()
then writeln(html))
emitBlanks(html, htmlIndent)
(if attributes = ϵ
then writeln(html, '<' & tag & '>')
else if isEmpty(objects)
then writeln(html, '<' & tag & ' ' & attributes & '>')
else writeln(html, '<' & tag & ' ' & attributes & '>', objects))
htmlIndent += htmlIndentChange
htmlRemaining := maxHtmlLength)
! EMIT PARAGRAPH INDENT. Write a line break and a series of nonbreaking spaces
! to start a new paragraph.
emitParagraphIndent :−
(form () void:
(with int temp :− htmlParagraphIndent × length(blank)
do emitLoneTag(''br'')
emitBlanks(html, htmlIndent)
(in htmlParagraphIndent
do write(html, blank))
htmlRemaining := maxHtmlLength − htmlIndent − temp))
! EMIT STRINGS. Write a list of indented strings to HTML.
emitStrings :−
(form (list strings) void:
(for obj string in elements(strings)
do emitBlanks(html, htmlIndent)
writeln(html, string))
htmlRemaining := maxHtmlLength)
! EMIT TITLE WORD. Like EMIT WORD, except that it writes the word ITEM without
! embedded markup. We write words left-justified and filled.
emitTitleWord :−
(proc (ref object item) void:
(with
string chars :− item{ref word}↑.chars
int needed :− count(chars)
do (if atLineStart()
then emitBlanks(html, htmlIndent)
write(html, chars)
htmlRemaining := maxHtmlLength − htmlIndent − needed
else if needed + 1 > htmlRemaining
then writeln(html)
emitBlanks(html, htmlIndent)
write(html, chars)
htmlRemaining := maxHtmlLength − htmlIndent − needed
else write(html, ' ')
write(html, chars)
htmlRemaining −= needed + 1)))
! EMIT TURN OFF. Write HTML tags that turn off the style code CODE, and record
! that we did it in the code stack CODES. We may need to write several tags to
! accomplish this, since other tags might be in the way. If CODE is not given,
! then write tags that turn off all currently active style codes.
emitTurnOff :−
(alt
(form () void:
(while ¬ isEmpty(codes)
do write(html, codeToClose[top(codes)])
pop(codes))),
(form (int code) void:
(while
(if isEmpty(codes)
then false
else write(html, codeToClose[top(codes)])
top(codes) ≠ code also pop(codes)))))
! EMIT TURN ON. Write an HTML tag that turns on the style code CODE and record
! that we did it in the code stack CODES.
emitTurnOn :−
(form (int code) void:
write(html, codeToOpen[code])
push(codes, code))
! EMIT WORD. Write the word LEFT with embedded style tags. LEFT is followed by
! the word RIGHT. We need RIGHT to tell which styles should be turned off when
! we get done writing LEFT. If LEFT isn't followed by another word, then RIGHT
! is NIL.
emitWord :−
(proc (ref object left, ref object right) void:
(with
var string leftChars :− left{ref word}↑.chars
var row set leftStyles :− left{ref word}↑.styles
int needed :− wordLength(left, right)
! EMIT CHARS AND STYLES. Write a word whose chars are in LEFT CHARS, and whose
! corresponding style code sets are in LEFT STYLES.
emitCharsAndStyles :−
(proc () void:
(while head(leftChars)
do emitCharAndStyle(head(leftChars), head(leftStyles))
leftChars := tail(leftChars)
leftStyles := tail(leftStyles))
(if isWord(right)
then (for int code
in elements(codes.style − right{ref word}↑.styles↑)
do emitTurnOff(code))
else emitTurnOff()))
! Write the word LEFT, left-justified and filled.
do (if atLineStart()
then emitBlanks(html, htmlIndent)
emitCharsAndStyles()
htmlRemaining := maxHtmlLength − htmlIndent − needed
else if needed + 1 > htmlRemaining
then writeln(html)
emitBlanks(html, htmlIndent)
emitCharsAndStyles()
htmlRemaining := maxHtmlLength − htmlIndent − needed
else write(html, ' ')
emitCharsAndStyles()
htmlRemaining −= needed + 1)))
! WORD LENGTH. Return the number of chars WRITE WORD will need to write a word
! LEFT, considering style tags and sanitized chars. We simulate WRITE WORD via
! a local CODES stack, incrementing COUNT for each char that would be written.
! LEFT is followed by the word RIGHT (or NIL).
wordLength :−
(proc (ref object left, ref object right) int:
(with
var codeStack(maxCode) codes :− (past codes)
var int count :− 0
var string leftChars :− left{ref word}↑.chars
var row set leftStyles :− left{ref word}↑.styles
inj ampersandΔ :− length(ampersand)
inj blankΔ :− length(blank)
inj closeQuoteΔ :− length(closeQuote)
inj greaterThanΔ :− length(greaterThan)
inj lessThanΔ :− length(lessThan)
inj openQuoteΔ :− length(openQuote)
inj uparrowΔ :− length(uparrow)
! COUNT TURN OFF. Simulate EMIT TURN OFF from WRITE WORD above.
countTurnOn :−
(form (int code) void:
count += length(codeToOpen[code]) + 2
push(codes, code))
! COUNT TURN ON. Simulate EMIT TURN ON from WRITE WORD above.
countTurnOff :−
(alt
(form () void:
(while ¬ isEmpty(codes)
do count += length(codeToClose[top(codes)]) + 3
pop(codes))),
(form (int code) void:
(while
(if isEmpty(codes)
then false
else count += length(codeToClose[top(codes)]) + 3
top(codes) ≠ code also pop(codes)))))
! INT LENGTH. Return how many decimal digits are needed to write a nonnegative
! NUMBER.
intLength :−
(form (int number) int:
(with
var int temp :− number
var int count :− temp = 0
do (while temp > 0
do count += 1
temp /= 10)
count))
! Simulate EMIT CHARS AND STYLES from WRITE WORD above. Do nothing special for
! CAPITAL CODE because it doesn't affect the number of chars written.
do (while head(leftChars)
do (for int code in elements(codes.style − head(leftStyles))
do countTurnOff(code))
(for int code in elements(head(leftStyles) − codes.style)
do countTurnOn(code))
(if isAscii(head(leftChars))
then (if quotedCode ∊ head(leftStyles)
then (case head(leftChars)
of ' ': count += blankΔ
'&': count += ampersandΔ
'<': count += lessThanΔ
'>': count += greaterThanΔ
none: count += 1)
else (case head(leftChars)
of ' ': count += blankΔ
'&': count += ampersandΔ
'\'': count += (if quoting then closeQuoteΔ else 1)
'<': count += lessThanΔ
'>': count += greaterThanΔ
'^': count += (if uparrowing then uparrowΔ else 1)
'`': count += (if quoting then openQuoteΔ else 1)
none: count += 1))
else count += intLength(head(leftChars)) + 3)
leftChars := tail(leftChars)
leftStyles := tail(leftStyles))
(if isWord(right)
then (for int code
in elements(codes.style − right{ref word}↑.styles[0])
do countTurnOff(code))
else countTurnOff())
count))
)