.
116 | */
117 | border-top-right-radius: 5px;
118 | border-bottom-right-radius: 5px;
119 | }
120 | .comment {
121 | color: #333333;
122 | font-style: italic;
123 | }
124 | .mnemonic {
125 | color: #000077;
126 | font-weight: bold;
127 | }
128 | .register {
129 | color: #773300;
130 | font-weight: bold;
131 | }
132 | #buttons {
133 | top: 400px;
134 | position: absolute;
135 | width: 100%;
136 | text-align: center;
137 | }
138 | .flag {
139 | color: #007700;
140 | font-weight: bold;
141 | }
142 | .label {
143 | color: #770077;
144 | }
145 | label {
146 | display: block;
147 | font-family: Arial, Helvetica, sans-serif;
148 | margin-left: 8px;
149 | margin-top: 3px;
150 | }
151 | .string {
152 | color: #770000;
153 | }
154 | .number {
155 | color: #007777;
156 | }
157 | .directive {
158 | color: #770077;
159 | font-weight: bold;
160 | }
161 | .parenthesis {
162 | font-weight: bold;
163 | }
164 | #highlightButton {
165 | position: absolute;
166 | left: 0;
167 | text-align: left;
168 | margin-left: 0;
169 | float: left;
170 | }
171 | #assembleButton {
172 | position: absolute;
173 | right: 5px;
174 | text-align: right;
175 | margin-right: 0;
176 | float: right;
177 | }
178 | @keyframes assembleButtonAnimation {
179 | begin {
180 | background: white;
181 | }
182 | 50% {
183 | background: lightblue;
184 | }
185 | end {
186 | background: white;
187 | }
188 | }
189 | #assembleButton:target {
190 | animation: assembleButtonAnimation 2s infinite;
191 | }
192 | #whyClickAssemble {
193 | position: absolute;
194 | top: 450px;
195 | width: calc(100% - 10px);
196 | text-align: justify;
197 | font-family: sans-serif;
198 | left: 50%;
199 | transform: translateX(-50%);
200 | }
201 | #divWithMachineCode {
202 | position: absolute;
203 | width: calc(100% - 5px);
204 | background: white;
205 | height: 100vh;
206 | top: 450px;
207 | overflow: scroll;
208 | max-height: 400px;
209 | margin-bottom: 10px;
210 | }
211 | #warningAboutJavaScript {
212 | display: block;
213 | justify-content: center;
214 | text-align: center;
215 | align-items: center;
216 | height: 100%;
217 | font-weight: bold;
218 | font-family: Arial;
219 | margin-top: auto;
220 | margin-bottom: auto;
221 | margin-right: 25px;
222 | margin-left: 10px;
223 | max-width: 450px;
224 | }
225 | table,
226 | th,
227 | td {
228 | border: 1px solid black;
229 | border-collapse: collapse;
230 | }
231 | th {
232 | font-family: Arial, Sans-Serif;
233 | }
234 | td {
235 | font-family:
236 | Courier New,
237 | monospace;
238 | text-align: center;
239 | }
240 | td:nth-child(1) {
241 | text-align: right;
242 | font-weight: bold;
243 | }
244 | #machineCode {
245 | width: calc(100% - 10px);
246 | margin: 5px;
247 | }
248 | table {
249 | margin-left: auto;
250 | margin-right: auto;
251 | margin-top: 5px;
252 | margin-bottom: 5px;
253 | }
254 | #simulationButtons {
255 | top: 855px;
256 | position: absolute;
257 | width: 100%;
258 | height: 50px;
259 | text-align: center;
260 | }
261 | #simulationResults {
262 | top: 910px;
263 | position: absolute;
264 | width: calc(100% - 8px);
265 | height: 100vh;
266 | max-height: 400px;
267 | overflow: scroll;
268 | background-color: white;
269 | margin-bottom: 15px;
270 | }
271 | input {
272 | width: 30px;
273 | }
274 | .regbank_b {
275 | font-style: italic;
276 | }
277 | .active {
278 | background: white;
279 | color: black;
280 | }
281 | .inactive {
282 | background: black;
283 | color: white;
284 | border-color: white;
285 | }
286 | tr:last-child td.inactive {
287 | border-bottom-color: black;
288 | }
289 | .changed,
290 | .turning_off {
291 | background: lightGreen;
292 | color: black;
293 | font-weight: bold;
294 | }
295 | .turning_off {
296 | background: red;
297 | }
298 | #interrupt_flag {
299 | font-style: normal;
300 | }
301 | .tooltip {
302 | display: none;
303 | }
304 | button:hover .tooltip {
305 | display: block;
306 | position: absolute;
307 | background: #ffffaa;
308 | z-index: 10;
309 | padding: 2px;
310 | font-style: italic;
311 | font-family: Times;
312 | }
313 | .exampleCodeLink {
314 | margin: 5px;
315 | width: 120px;
316 | flex-shrink: 0;
317 | align-items: center;
318 | justify-content: center;
319 | height: 150px;
320 | background: white;
321 | text-align: center;
322 | border-radius: 10px;
323 | box-shadow: 3px 5px 5px black;
324 | }
325 | .exampleCodeLink:hover {
326 | background: lightGray;
327 | transition-delay: 250ms;
328 | transition-duration: 250ms;
329 | }
330 | .exampleCodeLink:last-child {
331 | margin-right: 7.5px;
332 | }
333 | .exampleCodeLink:first-child {
334 | margin-left: 7.5px;
335 | }
336 | .exampleCodeLink:last-child::after {
337 | /*
338 | Firefox 52, apparently, ignores margin-right on the last element of the
339 | flexbox. So, let's solve it somewhow differently.
340 | */
341 | content: " ";
342 | display: block;
343 | width: 1px;
344 | flex-shrink: 0;
345 | white-space: pre;
346 | }
347 | #divWithExamples {
348 | background: #ffffee;
349 | font-family: Arial;
350 | width: calc(100% - 7px);
351 | max-width: calc(
352 | 500px - 7px
353 | ); /* To make the warning about
354 | * lack of support for modern
355 | * JavaScript legible in
356 | * Internet Explorer 11.
357 | */
358 | position: absolute;
359 | top: 405px;
360 | padding-left: 2px;
361 | border-radius: 10px;
362 | }
363 | .exampleCodeLink img {
364 | width: 100px;
365 | height: 100px;
366 | margin-left: auto;
367 | margin-top: 5px;
368 | margin-right: auto;
369 | background: white;
370 | }
371 | #examplesSpan {
372 | display: block;
373 | text-align: center;
374 | font-weight: normal;
375 | font-size: 1em;
376 | margin-bottom: 5px;
377 | margin-top: 5px;
378 | }
379 | #examples {
380 | overflow-x: scroll;
381 | overflow-y: hidden;
382 | margin: 5px;
383 | display: flex;
384 | justify-content: center;
385 | align-items: center;
386 | background: gray;
387 | height: 173px;
388 | border-radius: 10px;
389 | }
390 | .callForMoreExamples {
391 | margin: 5px;
392 | }
393 | #graphicalResults {
394 | overflow: scroll;
395 | position: absolute;
396 | top: 910px;
397 | height: 200px;
398 | width: calc(100% - 5px);
399 | background: white;
400 | }
401 | .sevenSegmentDisplay {
402 | background: black;
403 | width: 50px;
404 | height: 100px;
405 | margin: 5px;
406 | }
407 | .breakpoint_icon {
408 | display: none;
409 | width: calc(1em - 3px);
410 | height: calc(1em - 3px);
411 | }
412 | #register_PC {
413 | font-style: normal;
414 | /*Otherwise it will turn italic when Regbank B
415 | is being used, even though it stays active.*/
416 | }
417 | button {
418 | position: relative;
419 | /*
420 | https://stackoverflow.com/questions/64995585/tooltips-work-in-firefox-but-not-in-chrome
421 | */
422 | background: #cccccc; /*WebPositive apparently shows
423 | no background on buttons by
424 | default.*/
425 | padding-top: 3px;
426 | padding-bottom: 1px;
427 | }
428 | #downloadHex {
429 | margin-top: 5px;
430 | margin-bottom: 5px;
431 | margin-left: auto;
432 | margin-right: auto;
433 | display: block;
434 | }
435 | textarea {
436 | resize: none;
437 | overflow: scroll;
438 | width: calc(100% - 3 * 8px - 2px);
439 | margin-left: calc(2 * 8px);
440 | height: 95px;
441 | background-color: black;
442 | color: aquamarine;
443 | }
444 | #UART_IO {
445 | height: 250px;
446 | font-size: 1em;
447 | position: absolute;
448 | width: calc(100% - 5px);
449 | overflow: hidden;
450 | background-color: azure;
451 | display: none;
452 | }
453 | #UART_OUTPUT {
454 | height: 95px;
455 | width: calc(100% - 3 * 8px);
456 | margin-left: calc(2 * 8px);
457 | overflow: scroll;
458 | background-color: black;
459 | color: aquamarine;
460 | margin-top: auto;
461 | border-style: solid;
462 | border-width: 2px;
463 | border-right-color: lightgray;
464 | border-bottom-color: lightgray;
465 | border-top-color: darkgray;
466 | border-left-color: darkgray;
467 | }
468 | #UART_enable_button {
469 | position: absolute;
470 | width: calc(100% - 5px);
471 | font-size: 24px;
472 | line-height: 36px;
473 | }
474 | #fetchingExamples {
475 | background: white;
476 | padding: 10px;
477 | margin: 10px;
478 | }
479 | #MIT_licence {
480 | background: #eeeeff;
481 | border-top-left-radius: 5px;
482 | border-top-right-radius: 5px;
483 | position: fixed;
484 | bottom: 0px;
485 | left: 10px;
486 | width: calc(100% - 20px);
487 | font-family: Arial;
488 | }
489 | #licence_message {
490 | float: left;
491 | padding-left: 10px;
492 | padding-top: 5px;
493 | padding-bottom: 5px;
494 | padding-right: 5px;
495 | text-align: center;
496 | width: calc(
497 | 100% - 75px /* 75px is the width of the "Close" button. */ - 2 *
498 | (10px + 5px)
499 | );
500 | }
501 | #closing_the_licence {
502 | float: right;
503 | position: absolute;
504 | top: 50%;
505 | transform: translate(0, -50%);
506 | margin-right: 10px;
507 | line-height: 2em;
508 | width: 75px;
509 | background: #eeffee;
510 | }
511 | #warningAboutDownloadingHexadecimal {
512 | text-align: justify;
513 | font-family: Arial;
514 | margin-left: 5px;
515 | margin-right: 5px;
516 | }
517 | #uploadSuccessfulMessage {
518 | display: none;
519 | border-top-left-radius: 10px;
520 | border-top-right-radius: 10px;
521 | overflow: hidden;
522 | border-bottom-style: ridge;
523 | border-left-style: ridge;
524 | border-right-style: ridge;
525 | position: fixed;
526 | left: 50%;
527 | top: 50%;
528 | transform: translate(-50%, -50%);
529 | max-width: calc(100% - 10px);
530 | background: #ffffee;
531 | z-index: 100;
532 | box-shadow: 5px 5px 2.5px gray;
533 | }
534 | #messageAccompanyingTheURL {
535 | margin-left: 5px;
536 | margin-right: 5px;
537 | }
538 | #titleBar {
539 | background: linear-gradient(to right, darkBlue, #00ffff);
540 | color: white;
541 | padding-left: 10px;
542 | padding-right: 10px;
543 | padding-bottom: 5px;
544 | padding-top: 5px;
545 | font-family: sans-serif;
546 | }
547 | #closeButton {
548 | float: right;
549 | margin-right: 5px;
550 | margin-bottom: 5px;
551 | }
552 | #copyButton {
553 | float: left;
554 | margin-left: 5px;
555 | margin-bottom: 5px;
556 | }
557 | #shareURL {
558 | background: white;
559 | font-family: monospace;
560 | margin: 3px;
561 | border-style: solid;
562 | border-width: 1px;
563 | padding: 2px;
564 | }
565 | .sticky-header {
566 | position: sticky;
567 | top: 0;
568 | background-color: white;
569 | }
570 | #deleteTheProgram {
571 | display: none;
572 | grid-template-areas:
573 | "label label label"
574 | "password password password"
575 | ". . button";
576 | background-color: white;
577 | padding: 5px;
578 | }
579 | #label_for_input_password {
580 | grid-area: label;
581 | }
582 | #input_password {
583 | grid-area: password;
584 | width: calc(100% - 10px);
585 | margin-bottom: 5px;
586 | }
587 | #deleteTheProgramButton {
588 | grid-area: button;
589 | float: right;
590 | display: block;
591 | }
592 |
--------------------------------------------------------------------------------
/permutations.psm:
--------------------------------------------------------------------------------
1 | ;A very advanced example: Implementing the permutations algorithm in PicoBlaze assembly.
2 | ;For sorting, we will use the Bubble Sort algorithm. And we will use stack instead of recursion.
3 |
4 | base_decimal
5 |
6 | constant NDEBUG, 1
7 | constant address_of_the_current_attempt, 8
8 | constant digits_of_the_ordinal_number, 16
9 | constant bottom_of_the_stack, 24
10 |
11 | address 0
12 |
13 | namereg sf, length_of_the_input
14 |
15 | regbank a
16 | call print_the_introduction_message
17 | load length_of_the_input, 0
18 | beginning_of_the_input_loop:
19 | call UART_RX
20 | compare s9, a'x ;The new-line character.
21 | jump z, end_of_the_input_loop
22 | store s9, (length_of_the_input)
23 | add length_of_the_input, 1
24 | jump beginning_of_the_input_loop
25 | end_of_the_input_loop:
26 | compare length_of_the_input, 0
27 | jump z, 0
28 |
29 | ;An improved version of BubbleSort written by
Sep Roland...
30 | beginning_of_the_bubble_sort:
31 | load s5, length_of_the_input
32 | outer_bubble_sort_loop:
33 | sub s5, 1
34 | jump z, end_of_the_bubble_sort
35 | load s4, 0 ; Indicates swap(s) performed.
36 | load s1, 0
37 | inner_bubble_sort_loop:
38 | compare s1, s5
39 | jump nc, end_of_the_inner_bubble_sort_loop
40 | load s0, s1
41 | add s1, 1
42 | fetch s2, (s0)
43 | fetch s3, (s1)
44 | compare s3, s2
45 | jump nc, inner_bubble_sort_loop
46 | store s3, (s0)
47 | store s2, (s1)
48 | load s4, 1
49 | jump inner_bubble_sort_loop
50 | end_of_the_inner_bubble_sort_loop:
51 | test s4, s4
52 | jump nz, outer_bubble_sort_loop
53 | end_of_the_bubble_sort:
54 |
55 | jump NDEBUG ? the_permutations_algorithm : printing_the_sorted_array
56 | printing_the_sorted_array:
57 | call print_the_sorted_array_message
58 | load s0, 0
59 | printing_the_sorted_array_loop:
60 | compare s0, length_of_the_input
61 | jump nc, end_of_the_printing_the_sorted_array_loop
62 | fetch s9, (s0)
63 | call UART_TX
64 | add s0, 1
65 | jump printing_the_sorted_array_loop
66 | end_of_the_printing_the_sorted_array_loop:
67 | load s9, a'x
68 | call UART_TX
69 |
70 | the_permutations_algorithm:
71 |
72 | ;Let's set all the digits of the ordinal number of permutations to "0"
73 | regbank b
74 | load s0, digits_of_the_ordinal_number
75 | load s2, digits_of_the_ordinal_number ;End of the digits of the ordinal number.
76 | reset_ordinal_numbers_loop:
77 | compare s0, bottom_of_the_stack
78 | jump nc, end_of_the_reset_ordinal_numbers_loop
79 | load s1, "0"
80 | store s1, (s0)
81 | add s0, 1
82 | jump reset_ordinal_numbers_loop
83 | end_of_the_reset_ordinal_numbers_loop:
84 | regbank a
85 |
86 | namereg se, top_of_the_stack
87 | load top_of_the_stack, bottom_of_the_stack
88 | load s0, 0
89 | store s0, (top_of_the_stack)
90 | add top_of_the_stack, length_of_the_input
91 | add top_of_the_stack, 1
92 | beginning_of_the_permutations_loop:
93 | compare top_of_the_stack, bottom_of_the_stack
94 | jump z, end_of_the_permutations_loop
95 | sub top_of_the_stack, length_of_the_input
96 | sub top_of_the_stack, 1
97 | namereg sd, length_of_the_current_attempt
98 | fetch length_of_the_current_attempt, (top_of_the_stack)
99 | load s0, address_of_the_current_attempt
100 | store length_of_the_current_attempt, (s0)
101 | load s1, 0
102 | copying_the_current_attempt_from_the_stack_loop:
103 | compare s1, length_of_the_current_attempt
104 | jump nc, end_of_copying
105 | load s0, address_of_the_current_attempt
106 | add s0, s1
107 | add s0, 1
108 | load s3, top_of_the_stack
109 | add s3, s1
110 | add s3, 1
111 | fetch s4, (s3)
112 | store s4, (s0)
113 | add s1, 1
114 | jump copying_the_current_attempt_from_the_stack_loop
115 | end_of_copying:
116 | jump NDEBUG ? dont_print_the_current_attempt : print_the_current_attempt
117 | print_the_current_attempt:
118 | call print_the_length_of_the_current_attempt_message
119 | load s9, length_of_the_current_attempt
120 | add s9, "0"
121 | call UART_TX
122 | load s9, a'x
123 | call UART_TX
124 | call print_the_current_attempt_message
125 | load s0, address_of_the_current_attempt + 1
126 | printing_the_current_attempt_loop:
127 | load s1, address_of_the_current_attempt + 1
128 | add s1, length_of_the_current_attempt
129 | compare s0, s1
130 | jump nc, end_of_the_printing_the_current_attempt_loop
131 | fetch s9, (s0)
132 | call UART_TX
133 | add s0, 1
134 | jump printing_the_current_attempt_loop
135 | end_of_the_printing_the_current_attempt_loop:
136 | load s9, a'x
137 | call UART_TX
138 | dont_print_the_current_attempt:
139 | compare length_of_the_current_attempt, length_of_the_input
140 | jump c, current_attempt_is_not_a_solution
141 | call print_found_a_solution_message
142 | load s0, address_of_the_current_attempt + 1
143 | printing_the_solution_loop:
144 | load s1, address_of_the_current_attempt + 1
145 | add s1, length_of_the_current_attempt
146 | compare s0, s1
147 | jump nc, end_of_the_printing_the_solution_loop
148 | fetch s9, (s0)
149 | call UART_TX
150 | add s0, 1
151 | jump printing_the_solution_loop
152 | end_of_the_printing_the_solution_loop:
153 | load s9, a'x
154 | call UART_TX
155 | regbank b
156 | call print_the_ordinal_number_message
157 | load s1, digits_of_the_ordinal_number
158 | increasing_the_ordinal_number_loop:
159 | fetch s0, (s1)
160 | add s0, 1
161 | store s0, (s1)
162 | compare s0, "9" + 1
163 | jump nz, end_of_increasing_the_ordinal_number_loop
164 | load s0, "0"
165 | store s0, (s1)
166 | add s1, 1
167 | jump increasing_the_ordinal_number_loop
168 | end_of_increasing_the_ordinal_number_loop:
169 | compare s1, s2
170 | jump c, not_a_new_digit
171 | load s2, s1
172 | not_a_new_digit:
173 | load s1, s2
174 | printing_the_ordinal_number:
175 | fetch s9, (s1)
176 | call UART_TX
177 | sub s1, 1
178 | compare s1, digits_of_the_ordinal_number
179 | jump nc, printing_the_ordinal_number
180 | end_of_printing_the_ordinal_number:
181 | load s9, a'x
182 | call UART_TX
183 | regbank a
184 | jump end_of_the_branching
185 | current_attempt_is_not_a_solution:
186 | load s0, length_of_the_input
187 | sub s0, 1
188 | add_a_new_character_loop:
189 | ;No check at the beginning of this loop, for this is a do-while-loop,
190 | ;rather than a while-loop. Checking whether an overflow has occurred
191 | ;when decreasing the pointer stored in s0 by 1 is done at the end of
192 | ;this loop.
193 | namereg sc, character_we_try_to_add
194 | fetch character_we_try_to_add, (s0)
195 | load s7, s0
196 | add s7, 1
197 | load s8, 0 ;Whether we already tried adding that character.
198 | check_if_we_already_tried_that_character_loop:
199 | compare s7, length_of_the_input
200 | jump nc, end_of_the_check_if_we_already_tried_that_character_loop
201 | fetch s6, (s7)
202 | compare s6, character_we_try_to_add
203 | jump nz, third_characters_are_not_equal_label
204 | load s8, 1
205 | third_characters_are_not_equal_label:
206 | add s7, 1
207 | jump check_if_we_already_tried_that_character_loop
208 | end_of_the_check_if_we_already_tried_that_character_loop:
209 | test s8, s8
210 | jump nz, dont_add_the_new_character
211 | jump NDEBUG ? dont_print_the_character_we_are_trying_to_add : print_the_character_we_are_trying_to_add
212 | print_the_character_we_are_trying_to_add:
213 | call print_we_are_trying_to_add_message
214 | load s9, character_we_try_to_add
215 | call UART_TX
216 | load s9, a'x
217 | call UART_TX
218 | dont_print_the_character_we_are_trying_to_add:
219 | load s2, 0 ; How many of the chosen character are present in the current attempt.
220 | load s1, address_of_the_current_attempt + 1
221 | count_in_the_current_attempt_loop:
222 | load s4, address_of_the_current_attempt + 1
223 | add s4, length_of_the_current_attempt
224 | compare s1, s4
225 | jump z, end_of_the_count_in_the_current_attempt_loop
226 | fetch s4, (s1)
227 | compare s4, character_we_try_to_add
228 | jump nz, first_the_characters_are_not_equal_label
229 | add s2, 1
230 | first_the_characters_are_not_equal_label:
231 | add s1, 1
232 | jump count_in_the_current_attempt_loop
233 | end_of_the_count_in_the_current_attempt_loop:
234 | jump NDEBUG ? dont_print_how_many_in_the_current_attempt : print_how_many_in_the_current_attempt
235 | print_how_many_in_the_current_attempt:
236 | call print_the_current_attempt_count_message
237 | load s9, s2
238 | add s9, "0"
239 | call UART_TX
240 | load s9, a'x
241 | call UART_TX
242 | dont_print_how_many_in_the_current_attempt:
243 | load s3, 0 ; How many of the chosen character are present in the input.
244 | load s1, 0
245 | count_in_the_input_loop:
246 | compare s1, length_of_the_input
247 | jump z, end_of_the_count_in_the_input_loop
248 | fetch s4, (s1)
249 | compare s4, character_we_try_to_add
250 | jump nz, second_the_characters_are_not_equal_label
251 | add s3, 1
252 | second_the_characters_are_not_equal_label:
253 | add s1, 1
254 | jump count_in_the_input_loop
255 | end_of_the_count_in_the_input_loop:
256 | jump NDEBUG ? dont_print_how_many_in_the_input : print_how_many_in_the_input
257 | print_how_many_in_the_input:
258 | call print_count_in_the_input_message
259 | load s9, s3
260 | add s9, "0"
261 | call UART_TX
262 | load s9, a'x
263 | call UART_TX
264 | dont_print_how_many_in_the_input:
265 | compare s2, s3
266 | jump nc, dont_add_the_new_character
267 | load s1, NDEBUG
268 | test s1, s1
269 | call z, print_the_we_are_adding_the_new_character_message
270 | load s1, top_of_the_stack
271 | load s2, length_of_the_current_attempt
272 | add s2, 1
273 | store s2, (s1)
274 | add s1, 1
275 | load s3, address_of_the_current_attempt + 1
276 | copying_the_new_attempt_loop:
277 | load s5, address_of_the_current_attempt + 1
278 | add s5, length_of_the_current_attempt
279 | compare s3, s5
280 | jump z, end_of_the_copying_the_new_attempt_loop
281 | fetch s4, (s3)
282 | store s4, (s1)
283 | add s3, 1
284 | add s1, 1
285 | jump copying_the_new_attempt_loop
286 | end_of_the_copying_the_new_attempt_loop:
287 | ;s1 now points to the location right after the copied attempt.
288 | store character_we_try_to_add, (s1)
289 | add top_of_the_stack, length_of_the_input
290 | add top_of_the_stack, 1
291 | dont_add_the_new_character:
292 | sub s0, 1
293 | jump nc, add_a_new_character_loop
294 | end_of_the_add_a_new_character_loop:
295 | load sb, NDEBUG
296 | compare sb, 0
297 | call z, print_the_exited_the_loop_message
298 | end_of_the_branching:
299 | jump beginning_of_the_permutations_loop
300 | end_of_the_permutations_loop:
301 | call print_the_end_message
302 | jump 0
303 |
304 | print_the_introduction_message:
305 | print_string "Enter a short string and press enter.", s9, UART_TX
306 | load s9, a'x
307 | call UART_TX
308 | return
309 |
310 | print_the_sorted_array_message:
311 | print_string "After the Bubble Sort algorithm, the input string looks like this: ", s9, UART_TX
312 | return
313 |
314 | print_the_current_attempt_message:
315 | print_string "The current attempt is: ", s9, UART_TX
316 | return
317 |
318 | print_we_are_trying_to_add_message:
319 | print_string "We are trying to add the character: ", s9, UART_TX
320 | return
321 |
322 | print_the_current_attempt_count_message:
323 | print_string "The count of that character in the current attempt is: ", s9, UART_TX
324 | return
325 |
326 | print_count_in_the_input_message:
327 | print_string "The count of that character in the input string is: ", s9, UART_TX
328 | return
329 |
330 | print_the_we_are_adding_the_new_character_message:
331 | print_string "We will try to add that character.", s9, UART_TX
332 | load s9, a'x
333 | call UART_TX
334 | return
335 |
336 | print_found_a_solution_message:
337 | print_string "Found a permutation: ", s9, UART_TX
338 | return
339 |
340 | print_the_end_message:
341 | print_string "The end!", s9, UART_TX
342 | load s9, a'x
343 | call UART_TX
344 | return
345 |
346 | print_the_length_of_the_current_attempt_message:
347 | print_string "The length of the current attempt is: ", s9, UART_TX
348 | return
349 |
350 | print_the_ordinal_number_message:
351 | print_string "That's the permutation #", s9, UART_TX
352 | return
353 |
354 | print_the_exited_the_loop_message:
355 | print_string "The 'add_a_new_character_loop' loop has exited!", s9, UART_TX
356 | load s9, a'x
357 | call UART_TX
358 | return
359 |
360 | base_hexadecimal
361 | ;Now follows some boilerplate code
362 | ;we use in our Computer Architecture
363 | ;classes...
364 | CONSTANT LED_PORT, 00
365 | CONSTANT HEX1_PORT, 01
366 | CONSTANT HEX2_PORT, 02
367 | CONSTANT UART_TX_PORT, 03
368 | CONSTANT UART_RESET_PORT, 04
369 | CONSTANT SW_PORT, 00
370 | CONSTANT BTN_PORT, 01
371 | CONSTANT UART_STATUS_PORT, 02
372 | CONSTANT UART_RX_PORT, 03
373 | ; Tx data_present
374 | CONSTANT U_TX_D, 00000001'b
375 | ; Tx FIFO half_full
376 | CONSTANT U_TX_H, 00000010'b
377 | ; TxFIFO full
378 | CONSTANT U_TX_F, 00000100'b
379 | ; Rxdata_present
380 | CONSTANT U_RX_D, 00001000'b
381 | ; RxFIFO half_full
382 | CONSTANT U_RX_H, 00010000'b
383 | ; RxFIFO full
384 | CONSTANT U_RX_F, 00100000'b
385 |
386 | UART_RX:
387 | INPUT sA, UART_STATUS_PORT
388 | TEST sA, U_RX_D
389 | JUMP NZ, input_not_empty
390 | LOAD s0, s0
391 | JUMP UART_RX
392 | input_not_empty:
393 | INPUT s9, UART_RX_PORT
394 | RETURN
395 |
396 | UART_TX:
397 | INPUT sA, UART_STATUS_PORT
398 | TEST sA, U_TX_F
399 | JUMP NZ, UART_TX
400 | OUTPUT s9, UART_TX_PORT
401 | RETURN
402 |
403 | ;You may also be interested in
my implementation of the same algorithm in WebAssembly.
404 | ;I've also opened
a StackExchange thread about this program.
405 |
406 |
--------------------------------------------------------------------------------
/preprocessor.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | function isMnemonic(str) {
3 | if (typeof str !== "string") {
4 | alert(
5 | 'Internal compiler error: The first argument of the "isMnemonic" function is not a string!');
6 | return false;
7 | }
8 | for (const mnemonic of mnemonics)
9 | if (RegExp("^" + mnemonic + "$", "i").test(str))
10 | return true;
11 | return false;
12 | }
13 | function makeCompilationContext(root_of_the_abstract_syntax_tree,
14 | oldCompilationContext) {
15 | let context;
16 | if (typeof oldCompilationContext == "undefined")
17 | default_base_of_literals_in_assembly = 16;
18 | if (typeof oldCompilationContext != "undefined")
19 | context = oldCompilationContext;
20 | else
21 | context = {
22 | constants : new Map(),
23 | namedRegisters : new Map(),
24 | labels : new Map(),
25 | };
26 | if (typeof PicoBlaze === "object") {
27 | context.constants.set("PicoBlaze_Simulator_for_Android", 1);
28 | context.constants.set("PicoBlaze_Simulator_in_JS", 0);
29 | } else {
30 | context.constants.set("PicoBlaze_Simulator_in_JS", 1);
31 | context.constants.set("PicoBlaze_Simulator_for_Android", 0);
32 | }
33 | if (!(root_of_the_abstract_syntax_tree instanceof TreeNode) ||
34 | root_of_the_abstract_syntax_tree.text != "assembly") {
35 | // Such an error would be impossible in C++, but there is nothing preventing
36 | // it in JavaScript.
37 | alert(
38 | "Internal compiler error: The input to the preprocessor doesn't appear to be the output of the parser!");
39 | return context;
40 | }
41 | let address;
42 | for (const node_of_depth_1 of root_of_the_abstract_syntax_tree.children) {
43 | if (typeof oldCompilationContext != "undefined" &&
44 | !isDirective(node_of_depth_1.text)) {
45 | alert(
46 | "Line #" + node_of_depth_1.lineNumber + ": \"" +
47 | node_of_depth_1.text +
48 | "\" appears in an if-branching or a while-loop, but it is not a preprocessor directive. Only preprocessor directives can appear in if-branching and while-loops.");
49 | return context;
50 | }
51 | if (isMnemonic(node_of_depth_1.text)) {
52 | if (typeof oldCompilationContext != "undefined") {
53 | alert(
54 | "Line #" + node_of_depth_1.lineNumber + ': A mnemonic "' +
55 | node_of_depth_1.text +
56 | '" appears in an if-branching or a while-loop, where only preprocessor directives can appear!');
57 | return context;
58 | }
59 | if (typeof address === "undefined") {
60 | alert("Line " + node_of_depth_1.lineNumber + ': The mnemonic "' +
61 | node_of_depth_1.text +
62 | '" appears before any address has been set.');
63 | return context;
64 | }
65 | address++; // This won't work for most assembly language dialects, but it
66 | // works for PicoBlaze (where all directives have the same
67 | // size: 18 bits).
68 | }
69 | if (/^BASE_HEXADECIMAL$/i.test(node_of_depth_1.text)) {
70 | default_base_of_literals_in_assembly = 16;
71 | if (node_of_depth_1.children.length == 1) {
72 | default_base_of_literals_in_assembly =
73 | node_of_depth_1.children[0].interpretAsArithmeticExpression(
74 | context.constants);
75 | } else if (
76 | node_of_depth_1.children.length !=
77 | 0) { // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/35
78 | alert(
79 | "Line " + node_of_depth_1.lineNumber +
80 | ': The "BASE_HEXADECIMAL" pseudo-mnemonic should have 0 or 1 arguments.');
81 | return context;
82 | }
83 | }
84 | if (/^BASE_DECIMAL$/i.test(node_of_depth_1.text)) {
85 | default_base_of_literals_in_assembly = 10;
86 | if (node_of_depth_1.children.length == 1) {
87 | default_base_of_literals_in_assembly =
88 | node_of_depth_1.children[0].interpretAsArithmeticExpression(
89 | context.constants);
90 | } else if (node_of_depth_1.children.length != 0) {
91 | alert(
92 | "Line " + node_of_depth_1.lineNumber +
93 | ': The "BASE_DECIMAL" pseudo-mnemonic should have 0 or 1 arguments.');
94 | return context;
95 | }
96 | }
97 | if (/:$/.test(node_of_depth_1.text)) {
98 | console.log(
99 | "DEBUG: Dealing with a label, point #1..."); // Eh, those JavaScript
100 | // debuggers are worse
101 | // than useless, I think
102 | // now. Logging debug
103 | // messages is so much
104 | // easier than trying to
105 | // use a debugger.
106 | if (typeof address === "undefined") {
107 | alert("Line " + node_of_depth_1.lineNumber + ': The label "' +
108 | node_of_depth_1.text +
109 | '" appears before any address has been set.');
110 | return context;
111 | }
112 | if (!/^(_|[a-z])\w*:$/i.test(node_of_depth_1.text)) {
113 | alert("Line " + node_of_depth_1.lineNumber + ': "' +
114 | node_of_depth_1.text + '" is not an allowed label name.');
115 | return context;
116 | }
117 | console.log("DEBUG: Dealing with a label, point #2...");
118 | context.labels.set(
119 | node_of_depth_1.text.substring(
120 | 0,
121 | node_of_depth_1.text.length -
122 | 1), // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30
123 | address);
124 | console.log("DEBUG: Dealing with a label, point #3...");
125 | }
126 | if (/^address$/i.test(node_of_depth_1.text) ||
127 | /^org$/i.test(node_of_depth_1.text)) {
128 | if (typeof oldCompilationContext != "undefined") {
129 | alert(
130 | "Line #" + node_of_depth_1.lineNumber +
131 | ': The pseudo-mnemonic "address" appears in an if-branching or a while-loop, where only preprocessor directives can appear!');
132 | return context;
133 | }
134 | console.log("DEBUG: Setting the address, point #1...");
135 | if (node_of_depth_1.children.length != 1) {
136 | alert(
137 | "Line " + node_of_depth_1.lineNumber +
138 | ': The "address" pseudo-mnemonic doesn\'t have exactly one argument.');
139 | return context;
140 | }
141 | console.log("DEBUG: Setting the address, point #2...");
142 | address = node_of_depth_1.children[0].interpretAsArithmeticExpression(
143 | context.constants);
144 | console.log("DEBUG: Setting the address, point #3...");
145 | }
146 | if (/^PRINT_STRING$/i.test(node_of_depth_1.text)) {
147 | if (node_of_depth_1.children.length != 5) {
148 | alert(
149 | "Line " + node_of_depth_1.lineNumber +
150 | ": The \"print_string\" pseudo-mnemonic should have exactly five arguments (a ',' token also counts as an argument)!");
151 | return context;
152 | }
153 | if (node_of_depth_1.children[0].text[0] != '\"') {
154 | alert(
155 | "Line " + node_of_depth_1.lineNumber +
156 | ": The first argument to the \"print_string\" pseudo-mnemonic should be a string!");
157 | return context;
158 | }
159 | address += (node_of_depth_1.children[0].text.length - 2) * 2;
160 | }
161 | if (/^constant$/i.test(node_of_depth_1.text) ||
162 | /^equ$/i.test(node_of_depth_1.text)) {
163 | console.log("DEBUG: Setting a constant, point #1...");
164 | if (node_of_depth_1.children.length != 3) {
165 | alert(
166 | "Line " + node_of_depth_1.lineNumber +
167 | ': The AST node "constant" should have exactly three child nodes (the comma is also an AST node).');
168 | return context;
169 | }
170 | if (!/^(_|[a-z])\w*$/i.test(node_of_depth_1.children[0].text)) {
171 | alert("Line " + node_of_depth_1.lineNumber + ': "' +
172 | node_of_depth_1.children[0].text +
173 | '" is not an allowed constant name.');
174 | return context;
175 | }
176 | if (node_of_depth_1.children[1].text != ",") {
177 | alert("Line " + node_of_depth_1.lineNumber +
178 | ': The second child of the "' + node_of_depth_1.text +
179 | '" node is "' + node_of_depth_1.children[1].text +
180 | '" instead of a comma.');
181 | return context;
182 | }
183 | console.log("DEBUG: Setting a constant, point #2...");
184 | context.constants.set(
185 | node_of_depth_1.children[0].text,
186 | node_of_depth_1.children[2].interpretAsArithmeticExpression(
187 | context.constants));
188 | console.log("DEBUG: Setting a constant, point #3...");
189 | }
190 | if (/^namereg$/i.test(node_of_depth_1.text)) {
191 | console.log("DEBUG: Naming a register, point #1...");
192 | if (node_of_depth_1.children.length != 3) {
193 | alert(
194 | "Line " + node_of_depth_1.lineNumber +
195 | ': The AST node "namereg" should have exactly three child nodes (the comma is also an AST node).');
196 | return context;
197 | }
198 | if (context.namedRegisters.has(node_of_depth_1.children[2].text)) {
199 | alert("Line " + node_of_depth_1.lineNumber + ': Variable named "' +
200 | node_of_depth_1.children[2].text +
201 | '" has already been declared!');
202 | return context;
203 | }
204 | if (!/s([0-9]|[a-f])/i.test(node_of_depth_1.children[0].text)) {
205 | alert(
206 | "Line " + node_of_depth_1.lineNumber + ': "' +
207 | node_of_depth_1.children[0].text +
208 | "\" is supposed to be a register name, but it doesn't match the regular expression for registers.");
209 | return context;
210 | }
211 | if (node_of_depth_1.children[1].text != ",") {
212 | alert("Line " + node_of_depth_1.lineNumber +
213 | ': The second child of the "' + node_of_depth_1.text +
214 | '" node is "' + node_of_depth_1.children[1].text +
215 | '" instead of a comma.');
216 | return context;
217 | }
218 | console.log("DEBUG: Naming a register, point #2...");
219 | context.namedRegisters.set(node_of_depth_1.children[2].text,
220 | node_of_depth_1.children[0].text);
221 | console.log("DEBUG: Naming a register, point #3...");
222 | }
223 | if (/^display$/i.test(node_of_depth_1.text) &&
224 | (
225 | typeof PicoBlaze !==
226 | "object" // Because UART_OUTPUT is not declared in PicoBlaze
227 | // Simulator for Android, which we detect by `PicoBlaze`
228 | // being declared.
229 | )) {
230 | if (node_of_depth_1.children[0].text[0] == '"')
231 | document.getElementById("UART_OUTPUT").innerText +=
232 | node_of_depth_1.children[0]
233 | .text
234 | .substring( // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30
235 | 1, node_of_depth_1.children[0].text.length - 2 + 1);
236 | else {
237 | const ASCIIValue =
238 | node_of_depth_1.children[0].interpretAsArithmeticExpression(
239 | context.constants);
240 | if (ASCIIValue != '\n'.charCodeAt(0))
241 | document.getElementById("UART_OUTPUT")
242 | .appendChild(
243 | document.createTextNode(String.fromCharCode(ASCIIValue)));
244 | else
245 | document.getElementById("UART_OUTPUT")
246 | .appendChild(document.createElement(
247 | "br")); // This doesn't appear to work in Firefox if UART is
248 | // disabled while assembling, and I have opened a
249 | // GitHub issue about that:
250 | // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/8
251 | }
252 | } else if (/^display$/i.test(node_of_depth_1.text)) {
253 | if (node_of_depth_1.children[0].text[0] == '"') {
254 | for (let i = 0; i < node_of_depth_1.children[0].text.length; i++)
255 | if (node_of_depth_1.children[0].text[i] != '"')
256 | PicoBlaze.displayCharacterOnTerminal(
257 | node_of_depth_1.children[0].text.charCodeAt(
258 | i)); // Right now, `displayCharacterOnTerminal` is a
259 | // no-operation in PicoBlaze_Simulator_for_Android.
260 | } else {
261 | PicoBlaze.displayCharacterOnTerminal(
262 | node_of_depth_1.children[0].interpretAsArithmeticExpression(
263 | context.constants));
264 | }
265 | }
266 | if (/^if$/i.test(node_of_depth_1.text) &&
267 | node_of_depth_1.children.length == 2) {
268 | //"if" without "else"
269 | if (node_of_depth_1.children[0].interpretAsArithmeticExpression(
270 | context.constants)) {
271 | context = makeCompilationContext(node_of_depth_1.children[1], context);
272 | }
273 | } else if (/^if$/i.test(node_of_depth_1.text) &&
274 | node_of_depth_1.children.length == 3) {
275 | // if-else
276 | if (node_of_depth_1.children[0].interpretAsArithmeticExpression(
277 | context.constants))
278 | context = makeCompilationContext(node_of_depth_1.children[1], context);
279 | else
280 | context = makeCompilationContext(node_of_depth_1.children[2], context);
281 | } else if (/^if$/i.test(node_of_depth_1.text)) {
282 | alert(
283 | "Line #" + node_of_depth_1.lineNumber +
284 | ': The "if" node should have either 2 or 3 child nodes. This one has ' +
285 | node_of_depth_1.children.length + " child nodes!");
286 | return context;
287 | }
288 | if (/^while$/i.test(node_of_depth_1.text) &&
289 | node_of_depth_1.children.length == 2) {
290 | while (node_of_depth_1.children[0].interpretAsArithmeticExpression(
291 | context.constants)) {
292 | context = makeCompilationContext(node_of_depth_1.children[1], context);
293 | }
294 | } else if (/^while$/i.test(node_of_depth_1.text)) {
295 | alert("Line #" + node_of_depth_1.lineNumber +
296 | ': The "while" node should have 2 nodes. This one has ' +
297 | node_of_depth_1.children.length + " child nodes!");
298 | }
299 | }
300 | return context;
301 | }
302 |
--------------------------------------------------------------------------------
/footerScript.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | // For now, attempting to highlight code as the user is typing is worse
3 | // than useless, because it moves the cursor to the beginning.
4 | /*
5 | document.getElementById("assemblyCode").
6 | oninput=syntaxHighlighter;
7 | */
8 | setUpLineNumbers();
9 | let hasTheCodeBeenModifiedSinceLastSuccessfulAssembly = false;
10 | let areWeCurrentlyAssembling = false;
11 | document.getElementById("assemblyCode").oninput = () => {
12 | setUpLineNumbers();
13 | hasTheCodeBeenModifiedSinceLastSuccessfulAssembly = true;
14 | };
15 | document.getElementById("assemblyCode").onscroll = () => {
16 | document.getElementById("lineNumbers")
17 | .scroll(0, document.getElementById("assemblyCode").scrollTop);
18 | };
19 | document.getElementById("highlightButton").onclick = syntaxHighlighter;
20 | let hasTheCodeBeenAssembled = false;
21 | document.getElementById("assembleButton").onclick = () => {
22 | const assembly = document.getElementById("assemblyCode").innerText;
23 |
24 | let tokenized;
25 | try {
26 | tokenized = tokenize(assembly);
27 | } catch (error) {
28 | alert("Internal compiler error in the tokenizer: " + error.message);
29 | return;
30 | }
31 | let resultOfTokenizing = "[";
32 | for (let i = 0; i < tokenized.length; i++) {
33 | const token = tokenized[i];
34 | if (token.text === "\n")
35 | resultOfTokenizing += '"\\n"';
36 | else
37 | resultOfTokenizing += '"' + token.text + '"';
38 | if (i !== tokenized.length - 1)
39 | resultOfTokenizing += ",";
40 | }
41 | resultOfTokenizing += "]";
42 | console.log("Result of tokenizing: ", resultOfTokenizing);
43 | let parsed;
44 | try {
45 | parsed = parse(tokenized);
46 | } catch (error) {
47 | alert("Internal compiler error in the parser: " + error.message);
48 | }
49 | console.log("Result of parsing: ", parsed.getLispExpression());
50 | let context;
51 | try {
52 | context = makeCompilationContext(parsed);
53 | } catch (error) {
54 | alert("Internal compiler error in the preprocessor: " + error.message);
55 | }
56 | console.log("Result of preprocessing: ", context);
57 | try {
58 | assemble(parsed, context);
59 | } catch (error) {
60 | alert("Internal assembler error: " + error.message);
61 | }
62 | drawTable();
63 | areWeCurrentlyAssembling = true;
64 | stopSimulation();
65 | areWeCurrentlyAssembling = false;
66 | hasTheCodeBeenAssembled = true;
67 | hasTheCodeBeenModifiedSinceLastSuccessfulAssembly = false;
68 | };
69 | function stopSimulation() {
70 | if (!playing && !areWeCurrentlyAssembling) {
71 | alert("You are not supposed to press the stop button unless the simulation is currently playing, and it is not right now!");
72 | return;
73 | }
74 | document.getElementById("fastForwardButton").disabled = false;
75 | document.getElementById("singleStepButton").disabled = false;
76 | document.getElementById("UART_INPUT").disabled = false;
77 | if (playing)
78 | clearInterval(simulationThread);
79 | document.getElementById("PC_label_" + formatAsAddress(PC)).innerHTML = "";
80 | PC = 0;
81 | document.getElementById("PC_label_000").innerHTML =
82 | "

";
83 | playing = false;
84 | document.getElementById("playImage").style.display = "inline";
85 | document.getElementById("pauseImage").style.display = "none";
86 | for (let i = 0; i < 256; i++)
87 | output[i] = 0;
88 | for (let i = 0; i < 16; i++)
89 | registers[0][i] = registers[1][i] = 0;
90 | flagZ = [ 0, 0 ];
91 | flagC = [ 0, 0 ];
92 | callStack = [];
93 | regbank = 0;
94 | flagIE = 1;
95 | displayRegistersAndFlags();
96 | displayOutput();
97 | currentlyReadCharacterInUART = 0;
98 | }
99 | setupLayout();
100 | window.onresize = setupLayout;
101 | drawTable();
102 | displayRegistersAndFlags();
103 | window.onscroll = () => {
104 | if (window.innerWidth >= 700) {
105 | if (window.scrollY > document.getElementById("assemblyCode").clientHeight) {
106 | document.body.style.backgroundPosition = "top left";
107 | } else {
108 | document.body.style.backgroundPosition = "top right";
109 | }
110 | } else {
111 | document.body.style.backgroundPosition = "top left";
112 | }
113 | };
114 | function onPlayPauseButton() {
115 | playing = !playing;
116 | if (!playing) {
117 | clearInterval(simulationThread);
118 | document.getElementById("fastForwardButton").disabled = false;
119 | document.getElementById("singleStepButton").disabled = false;
120 | document.getElementById("UART_INPUT").disabled = false;
121 | document.getElementById("playImage").style.display = "inline";
122 | document.getElementById("pauseImage").style.display = "none";
123 | } else {
124 | if (!hasTheCodeBeenAssembled) {
125 | if (!confirm(
126 | "The code has not been assembled. Do you really want to proceed starting the emulation?"))
127 | return;
128 | } else if (
129 | hasTheCodeBeenModifiedSinceLastSuccessfulAssembly) // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/28
130 | {
131 | if (!confirm(
132 | "The code has been modified since the last successful assembling. Are you sure you want to proceed starting the emulation?"))
133 | return;
134 | }
135 | if (!document.getElementById("shouldWeUpdateRegisters")
136 | .checked) // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/20
137 | document.getElementById("PC_label_" + formatAsAddress(PC)).innerHTML =
138 | " ";
139 | simulationThread = setInterval(simulateOneInstruction, 500);
140 | document.getElementById("fastForwardButton").disabled = true;
141 | document.getElementById("singleStepButton").disabled = true;
142 | document.getElementById("UART_INPUT").disabled = true;
143 | document.getElementById("playImage").style.display = "none";
144 | document.getElementById("pauseImage").style.display = "inline";
145 | }
146 | }
147 | function onSingleStepButton() {
148 | if (playing)
149 | return;
150 | simulateOneInstruction();
151 | }
152 | function fastForward() {
153 | if (!hasTheCodeBeenAssembled) {
154 | if (!confirm(
155 | "The code has not been assembled. Do you really want to proceed starting the emulation?"))
156 | return;
157 | } else if (
158 | hasTheCodeBeenModifiedSinceLastSuccessfulAssembly) // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/28
159 | {
160 | if (!confirm(
161 | "The code has been modified since the last successful assembling. Are you sure you want to proceed starting the emulation?"))
162 | return;
163 | }
164 | playing = true;
165 | if (!document.getElementById("shouldWeUpdateRegisters")
166 | .checked) // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/20
167 | document.getElementById("PC_label_" + formatAsAddress(PC)).innerHTML = " ";
168 | document.getElementById("fastForwardButton").disabled = true;
169 | document.getElementById("singleStepButton").disabled = true;
170 | document.getElementById("UART_INPUT").disabled = true;
171 | simulationThread = setInterval(simulateOneInstruction, 0);
172 | document.getElementById("playImage").style.display = "none";
173 | document.getElementById("pauseImage").style.display = "inline";
174 | }
175 | document.getElementById("playPauseButton").onclick = onPlayPauseButton;
176 | document.getElementById("stopButton").onclick = stopSimulation;
177 | document.getElementById("singleStepButton").onclick = onSingleStepButton;
178 | document.getElementById("fastForwardButton").onclick = fastForward;
179 | const svgNS = document.getElementById("emptySVG").namespaceURI;
180 | let sevenSegmentDisplays = document.createElement("div");
181 | sevenSegmentDisplays.style.width = 4 * 60 + "px";
182 | sevenSegmentDisplays.style.marginLeft = "auto";
183 | sevenSegmentDisplays.style.marginRight = "auto";
184 | for (let i = 0; i < 4; i++) {
185 | let sevenSegmentDisplay = document.createElementNS(svgNS, "svg");
186 | sevenSegmentDisplay.classList.add("sevenSegmentDisplay");
187 | sevenSegmentDisplay.id = "sevenSegmentDisplay" + i;
188 | const polygons = [
189 | "15,5 35, 5 40,10 35,15 15,15 10,10", // Segment A (top)
190 | "40,10 45,15 45,45 40,50 35,45 35,15", // Segment B (top-right)
191 | "40,50 45,55 45,85 40,90 35,85 35,55", // Segment C (bottom-right)
192 | "40,90 35,95 15,95 10,90 15,85 35,85", // Segment D (bottom)
193 | "10,90 5,85 5,55 10,50 15,55 15,85", // Segment E (bottom-left)
194 | "10,50 5,45 5,15 10,10 15,15 15,45", // Segment F (top-left)
195 | "10,50 15,45 35,45 40,50 35,55 15,55", // Segment G (hyphen in the middle)
196 | ];
197 | for (const polygonPoints of polygons) {
198 | let polygon = document.createElementNS(svgNS, "polygon");
199 | if (polygons.indexOf(polygonPoints) === 6)
200 | polygon.setAttribute("fill", "#ffaaaa");
201 | else
202 | polygon.setAttribute("fill", "#333333");
203 | polygon.setAttribute("points", polygonPoints);
204 | polygon.setAttribute("stroke", "black");
205 | sevenSegmentDisplay.appendChild(polygon);
206 | }
207 | sevenSegmentDisplays.appendChild(sevenSegmentDisplay);
208 | }
209 | document.getElementById("graphicalResults").appendChild(sevenSegmentDisplays);
210 | let LEDs = document.createElementNS(svgNS, "svg");
211 | LEDs.setAttribute("width", 400);
212 | LEDs.setAttribute("height", 60);
213 | LEDs.style.background = "darkGreen"; //"fill" does not work here.
214 | LEDs.style.marginLeft = "auto"; // No idea why this is necessary.
215 | LEDs.style.marginRight = "auto";
216 | LEDs.style.display = "block";
217 | for (let i = 0; i < 8; i++) {
218 | let LED = document.createElementNS(svgNS, "circle");
219 | LED.setAttribute("fill", "#333333");
220 | LED.setAttribute("cx", 25 + i * 50);
221 | LED.setAttribute("cy", 15);
222 | LED.setAttribute("r", 5);
223 | LED.id = "LED" + (7 - i);
224 | LEDs.appendChild(LED);
225 | let switchHolder = document.createElementNS(svgNS, "rect");
226 | switchHolder.setAttribute("fill", "black");
227 | switchHolder.setAttribute("width", 5);
228 | switchHolder.setAttribute("height", 30);
229 | switchHolder.setAttribute("y", 25);
230 | switchHolder.setAttribute("x", 23 + i * 50);
231 | LEDs.appendChild(switchHolder);
232 | let button = document.createElementNS(svgNS, "rect");
233 | button.setAttribute("fill", "gray");
234 | button.setAttribute("width", 15);
235 | button.setAttribute("height", 15);
236 | button.setAttribute("x", 18 + i * 50);
237 | button.setAttribute("y", 25 + 30 - 15);
238 | button.id = "switch" + i;
239 | button.setAttribute("data-buttonValue", 1 << (7 - i));
240 | button.onclick = onSwitchPressed;
241 | LEDs.appendChild(button);
242 | }
243 | document.getElementById("graphicalResults").appendChild(LEDs);
244 | function onSwitchPressed(event) {
245 | let valueOfTheFirstInput =
246 | parseInt(document.getElementById("input_00").value, 16);
247 | valueOfTheFirstInput ^= event.target.getAttribute("data-buttonValue");
248 | // https://discord.com/channels/530598289813536771/847014270922391563/1434254492014219315
249 | const isValid =
250 | valueOfTheFirstInput & event.target.getAttribute("data-buttonValue");
251 | if (isValid)
252 | event.target.setAttribute("y", 25);
253 | else
254 | event.target.setAttribute("y", 25 + 30 - 15);
255 | document.getElementById("input_00").value =
256 | formatAsByte(valueOfTheFirstInput);
257 | }
258 | document.getElementById("input_00").oninput = () => {
259 | const value =
260 | Math.abs(parseInt(document.getElementById("input_00").value, 16) | 0) %
261 | 256;
262 | let formatedAsBinary = value.toString(2);
263 | while (formatedAsBinary.length < 8)
264 | formatedAsBinary = "0" + formatedAsBinary;
265 | for (let i = 0; i < 8; i++)
266 | document.getElementById("switch" + i)
267 | .setAttribute("y", 40 - formatedAsBinary[i] * 15);
268 | };
269 | document.getElementById("UART_enable_button").onclick = () => {
270 | is_UART_enabled = !is_UART_enabled;
271 | document.getElementById("input_02").disabled =
272 | document.getElementById("input_03").disabled = is_UART_enabled;
273 | document.getElementById("UART_IO").style.display =
274 | is_UART_enabled ? "block" : "none";
275 | document.getElementById("enable_or_disable_UART").innerHTML =
276 | is_UART_enabled ? "Disable" : "Enable";
277 | if (is_UART_enabled)
278 | document.getElementById("shouldWeUpdateRegisters").checked = false;
279 | window.onresize();
280 | };
281 | fetch(URL_of_JSON_with_examples)
282 | .then((response) => {
283 | if (!response.ok)
284 | throw new Error(response.status);
285 | else
286 | return response.json();
287 | })
288 | .then((examplesArray) => {
289 | let examplesHTML = examplesArray
290 | .map((example) => `
291 |
302 | `).join("") + `
303 |
309 |
318 | `;
319 | document.getElementById("examples").style.justifyContent = "initial";
320 | document.getElementById("examples").style.alignItems = "initial";
321 | document.getElementById("examples").innerHTML = examplesHTML;
322 | })
323 | .catch((error) => {
324 | document.getElementById("fetchingExamples").innerHTML =
325 | "Failed to fetch the examples JSON from GitHub: " + error;
326 | });
327 |
--------------------------------------------------------------------------------
/TreeNode.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function LevenshtainDistance(A, B) {
4 | // Will be used to find the closest label to the one that the user has.
5 | // Adapted from:
6 | // https://github.com/royalpranjal/Interview-Bit/blob/master/DynamicProgramming/EditDistance.cpp
7 |
8 | const row = A.length;
9 | const col = B.length;
10 |
11 | const temp = [];
12 | for (let i = 0; i < row + 1; i++) {
13 | let tmp = [];
14 | for (let j = 0; j < col + 1; j++) {
15 | tmp.push(0);
16 | }
17 | temp.push(tmp);
18 | }
19 |
20 | const min = (a, b) => {
21 | if (a < b)
22 | return a;
23 | else
24 | return b;
25 | }; // I am not sure whether there should be a semi-colon here or not. The code
26 | // seems to somehow compile either way in both Firefox and Chrome.
27 |
28 | for (let i = 0; i < temp.length; i++) {
29 | for (let j = 0; j < temp[0].length; j++) {
30 | if (j == 0) {
31 | temp[i][j] = i;
32 | } else if (i == 0) {
33 | temp[i][j] = j;
34 | } else if (A[i - 1] == B[j - 1]) {
35 | temp[i][j] = temp[i - 1][j - 1];
36 | } else {
37 | temp[i][j] = min(temp[i - 1][j - 1], temp[i - 1][j]);
38 | temp[i][j] = min(temp[i][j - 1], temp[i][j]);
39 | temp[i][j] = temp[i][j] + 1;
40 | }
41 | }
42 | }
43 |
44 | return temp[row][col];
45 | }
46 |
47 | class TreeNode {
48 | constructor(text, lineNumber) {
49 | this.text = text;
50 | this.lineNumber = lineNumber;
51 | this.children = [];
52 | }
53 | getLispExpression() {
54 | if (!this.children.length)
55 | return '"' + (this.text == "\n" ? "\\n" : this.text) + '"';
56 | let ret = '("' + this.text + '" ';
57 | for (let i = 0; i < this.children.length; i++)
58 | if (i < this.children.length - 1)
59 | ret += this.children[i].getLispExpression() + " ";
60 | else
61 | ret += this.children[i].getLispExpression() + ")";
62 | return ret;
63 | }
64 | interpretAsArithmeticExpression(constants, labels) {
65 | if (!(constants instanceof Map)) {
66 | alert(
67 | 'Internal compiler error: The "constants" argument is not of the type "Map"!');
68 | }
69 | if (labels && !(labels instanceof Map)) {
70 | alert(
71 | 'Internal compiler error: The "labels" argument is not of the type "Map"!');
72 | }
73 | if (labels && labels.has(this.text)) {
74 | return labels.get(this.text);
75 | }
76 | if (constants.has(this.text))
77 | return constants.get(this.text);
78 | if (this.children.length != 2 &&
79 | (this.text == "*" || this.text == "/" || this.text == "+" ||
80 | this.text == "-" || this.text == "<" || this.text == "=" ||
81 | this.text == ">" || this.text == "&" || this.text == "|")) {
82 | alert("Line #" + this.lineNumber + ": The binary operator " + this.text +
83 | " has less than two operands.");
84 | return NaN;
85 | }
86 | if (this.text == "^")
87 | return (this.children[0].interpretAsArithmeticExpression(constants) **
88 | this.children[1].interpretAsArithmeticExpression(constants));
89 | if (this.text == "*")
90 | return (this.children[0].interpretAsArithmeticExpression(constants) *
91 | this.children[1].interpretAsArithmeticExpression(constants));
92 | if (this.text == "/")
93 | return (this.children[0].interpretAsArithmeticExpression(constants) /
94 | this.children[1].interpretAsArithmeticExpression(constants));
95 | if (this.text == "+")
96 | return (this.children[0].interpretAsArithmeticExpression(constants) +
97 | this.children[1].interpretAsArithmeticExpression(constants));
98 | if (this.text == "-")
99 | return (this.children[0].interpretAsArithmeticExpression(constants) -
100 | this.children[1].interpretAsArithmeticExpression(constants));
101 | if (this.text == "<")
102 | return (this.children[0].interpretAsArithmeticExpression(constants) <
103 | this.children[1].interpretAsArithmeticExpression(constants));
104 | if (this.text == ">")
105 | return (this.children[0].interpretAsArithmeticExpression(constants) >
106 | this.children[1].interpretAsArithmeticExpression(constants));
107 | if (this.text == "=")
108 | return (this.children[0].interpretAsArithmeticExpression(constants) ==
109 | this.children[1].interpretAsArithmeticExpression(constants));
110 | if (this.text == "&")
111 | return (this.children[0].interpretAsArithmeticExpression(constants) &&
112 | this.children[1].interpretAsArithmeticExpression(constants));
113 | if (this.text == "|")
114 | return (this.children[0].interpretAsArithmeticExpression(constants) ||
115 | this.children[1].interpretAsArithmeticExpression(constants));
116 | if (this.text == "?:")
117 | return (
118 | this.children[0].interpretAsArithmeticExpression(constants)
119 | ? this.children[1].interpretAsArithmeticExpression(constants,
120 | labels)
121 | : this.children[2].interpretAsArithmeticExpression(
122 | constants,
123 | labels)); // We are passing "labels" in an attempt to mend
124 | // this bug:
125 | // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/38
126 | if (this.text == "()") {
127 | if (this.children.length != 1) {
128 | alert("Line #" + this.lineNumber +
129 | ": The node '()' doesn't have exactly 1 child.");
130 | return NaN;
131 | }
132 | return this.children[0].interpretAsArithmeticExpression(constants);
133 | }
134 | if (this.text == "invertBits()") {
135 | if (this.children.length != 1) {
136 | alert("Line #" + this.lineNumber +
137 | ": The node 'invertBits()' doesn't have exactly 1 child.");
138 | return NaN;
139 | }
140 | return ~this.children[0].interpretAsArithmeticExpression(constants);
141 | }
142 | if (this.text == "bitand()") {
143 | if (this.children.length != 3 || this.children[1].text != ',') {
144 | alert(
145 | "Line #" + this.lineNumber +
146 | ": The node 'bitand()' doesn't have exactly 3 children (a comma is also a child node).");
147 | return NaN;
148 | }
149 | return this.children[0].interpretAsArithmeticExpression(constants) &
150 | this.children[2].interpretAsArithmeticExpression(constants);
151 | }
152 | if (this.text == "bitor()") {
153 | if (this.children.length != 3 || this.children[1].text != ',') {
154 | alert(
155 | "Line #" + this.lineNumber +
156 | ": The node 'bitor()' doesn't have exactly 3 children (a comma is also a child node).");
157 | return NaN;
158 | }
159 | return this.children[0].interpretAsArithmeticExpression(constants) |
160 | this.children[2].interpretAsArithmeticExpression(constants);
161 | }
162 | if (this.text == "mod()") {
163 | if (this.children.length != 3 || this.children[1].text != ',') {
164 | alert(
165 | "Line #" + this.lineNumber +
166 | ": The node 'mod()' doesn't have exactly 3 children (a comma is also a child node).");
167 | return NaN;
168 | }
169 | return this.children[0].interpretAsArithmeticExpression(constants) %
170 | this.children[2].interpretAsArithmeticExpression(constants);
171 | }
172 | if (/\'d$/.test(this.text)) {
173 | for (let i = 0; i < this.text.length - 2; i++)
174 | if (this.text.charCodeAt(i) < '0'.charCodeAt(0) ||
175 | this.text.charCodeAt(i) > '9'.charCodeAt(0)) {
176 | alert(
177 | "Line #" + this.lineNumber + ": `" + this.text +
178 | "` is not a valid decimal constant!"); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/27
179 | return NaN;
180 | }
181 | return parseInt(this.text.substring(
182 | 0,
183 | this.text.length -
184 | 2)); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30
185 | }
186 | if (/\'o$/.test(this.text)) {
187 | for (let i = 0; i < this.text.length - 2; i++)
188 | if (this.text.charCodeAt(i) < '0'.charCodeAt(0) ||
189 | this.text.charCodeAt(i) > '7'.charCodeAt(0)) {
190 | alert(
191 | "Line #" + this.lineNumber + ": `" + this.text +
192 | "` is not a valid octal constant!"); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/27
193 | return NaN;
194 | }
195 | return parseInt(
196 | this.text.substring(0, this.text.length - 2),
197 | 8); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30
198 | }
199 | if (/\'b$/.test(this.text)) {
200 | for (let i = 0; i < this.text.length - 2; i++)
201 | if (!(this.text[i] == '0' || this.text[i] == '1')) {
202 | alert("Line #" + this.lineNumber + ": `" + this.text +
203 | "` is not a valid binary constant!");
204 | return NaN;
205 | }
206 | return parseInt(
207 | this.text.substring(0, this.text.length - 2),
208 | 2); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30
209 | }
210 | if (/\'x$/.test(this.text)) {
211 | if (!(/^([a-f]|[0-9])*\'x$/i.test(this.text))) {
212 | alert("Line #" + this.lineNumber + ": `" + this.text +
213 | "` is not a valid hexadecimal constant!");
214 | return NaN;
215 | }
216 | return parseInt(
217 | this.text.substring(0, this.text.length - 2),
218 | 16); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30
219 | }
220 | if (/^([a-f]|[0-9])*$/i.test(this.text))
221 | return parseInt(
222 | this.text,
223 | ((typeof default_base_of_literals_in_assembly) == "undefined")
224 | ? 16
225 | : default_base_of_literals_in_assembly);
226 | if (this.text[0] === '"' && this.text.length === 3)
227 | return this.text.charCodeAt(1);
228 | let keys = [];
229 | constants.forEach((value, key) => { keys.push(key); });
230 | let smallest_Levenshtain_distance = keys[0];
231 | for (const key of keys) {
232 | if (LevenshtainDistance(key, this.text) <
233 | LevenshtainDistance(smallest_Levenshtain_distance, this.text)) {
234 | smallest_Levenshtain_distance = key;
235 | }
236 | }
237 | if (confirm("Instead of \"" + this.text + "\", in the line #" +
238 | this.lineNumber + ", did you perhaps mean \"" +
239 | smallest_Levenshtain_distance + "\"?")) {
240 | if (constants.has(smallest_Levenshtain_distance)) {
241 | constants.set(this.text, constants.get(smallest_Levenshtain_distance));
242 | return constants.get(this.text);
243 | }
244 | }
245 | alert('Some part of the assembler tried to interpret the token "' +
246 | this.text + '" in the line #' + this.lineNumber +
247 | " as a part of an arithmetic expression, which makes no sense.");
248 | return NaN;
249 | }
250 | checkTypes() {
251 | // Let's check for inconsistencies that would be impossible in C++, but are
252 | // possible in JavaScript.
253 | if (typeof this.text !== "string") {
254 | alert("Internal compiler error: For some token in the line #" +
255 | this.lineNumber + ', the "text" property is not of type "string"');
256 | return false;
257 | }
258 | if (!(this.children instanceof Array)) {
259 | alert('Internal compiler error: The "children" property of the "' +
260 | this.text + '" token in the line #' + this.lineNumber +
261 | " is not an array!");
262 | return false;
263 | }
264 | for (const child of this.children)
265 | if (!(child instanceof TreeNode)) {
266 | alert('Internal compiler error: The node "this.text" in the line #' +
267 | this.lineNumber +
268 | " has a child that is not an instance of TreeNode!");
269 | return false;
270 | }
271 | return true;
272 | }
273 | getRegisterNumber(registers) {
274 | if (registers.has(this.text))
275 | return registers.get(this.text)
276 | .substring(1)
277 | .toLowerCase(); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30
278 | if (/^s(\d|[a-f])$/i.test(this.text))
279 | return this.text.substring(1)
280 | .toLowerCase(); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/30
281 | // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/37#issuecomment-2778246629
282 | let register_name_with_Levenshtain_distance_of_one;
283 | registers.forEach((value, key) => {
284 | if (LevenshtainDistance(key, this.text) == 1)
285 | register_name_with_Levenshtain_distance_of_one = key;
286 | });
287 | if (register_name_with_Levenshtain_distance_of_one) {
288 | if (confirm(
289 | "Instead of \"" + this.text + "\", in the line #" +
290 | this.lineNumber + ", did you perhaps mean \"" +
291 | register_name_with_Levenshtain_distance_of_one +
292 | "\", an alternate name for the register \"" +
293 | registers.get(register_name_with_Levenshtain_distance_of_one) +
294 | "\"?")) {
295 | registers.set(
296 | this.text,
297 | registers.get(register_name_with_Levenshtain_distance_of_one));
298 | return registers.get(this.text).substring(1).toLowerCase();
299 | }
300 | }
301 | return "none";
302 | }
303 | getLabelAddress(labels, constants) {
304 | if (labels.has(this.text))
305 | return formatAsAddress(labels.get(this.text));
306 | if (constants.has(this.text))
307 | return formatAsAddress(constants.get(this.text));
308 | if (/^(\d|[a-f])*$/i.test(this.text) ||
309 | [ "+", "-", "*", "/", "^", "?:" ].includes(this.text))
310 | return formatAsAddress(this.interpretAsArithmeticExpression(
311 | constants,
312 | labels)); // We are passing the `labels` argument to resolve this bug:
313 | // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/38
314 | if (this.text == "()")
315 | return "none"; // Must not detect "()" as a label.
316 | let keys = [];
317 | labels.forEach((value, key) => { keys.push(key); });
318 | constants.forEach((value, key) => { keys.push(key); });
319 | let smallest_Levenshtain_distance = keys[0];
320 | for (const key of keys) {
321 | if (LevenshtainDistance(key, this.text) <
322 | LevenshtainDistance(smallest_Levenshtain_distance, this.text)) {
323 | smallest_Levenshtain_distance = key;
324 | }
325 | }
326 | if (confirm("Instead of \"" + this.text + "\", in the line #" +
327 | this.lineNumber + ", did you perhaps mean \"" +
328 | smallest_Levenshtain_distance + "\"?")) {
329 | if (labels.has(smallest_Levenshtain_distance)) {
330 | labels.set(this.text, labels.get(smallest_Levenshtain_distance));
331 | return formatAsAddress(labels.get(smallest_Levenshtain_distance));
332 | }
333 | if (constants.has(smallest_Levenshtain_distance)) {
334 | constants.set(this.text, constants.get(smallest_Levenshtain_distance));
335 | return formatAsAddress(constants.get(smallest_Levenshtain_distance));
336 | }
337 | }
338 | return "none";
339 | }
340 | }
341 |
--------------------------------------------------------------------------------
/parser.js:
--------------------------------------------------------------------------------
1 | // I have hand-written this parser partly because I don't know of any BISON-like
2 | // tool that supports JavaScript and partly because I am not even sure how would
3 | // I make a parser for PicoBlaze Assembly in BISON. The keywords "enable" and
4 | // "disable" are problematic (they can be both mnemonics and, let's say so,
5 | // "adverbs"). I have opened a StackExchange question about that:
6 | // https://langdev.stackexchange.com/q/1679/330
7 | "use strict";
8 |
9 | /*
10 | * In most assemblers, the parser returns a two-dimensional array of trees,
11 | * many of those trees containing only a single node (and only arithmetic
12 | * expressions being represented with a multiple-node tree). The parser of
13 | * this assembler works differently, more like a parser for higher-level
14 | * programming languages. The parser of this assembler returns one big
15 | * tree, with the root being a node containing the text "assembly". Labels,
16 | * preprocessor directives and mnemonics are nodes of depth equal to 1, and
17 | * their operands are their children.
18 | */
19 |
20 | function parse(tokenized) {
21 |
22 | // This function is recursive, so we are going to print the argument to
23 | // make it easier to debug it.
24 | let report = "[";
25 | for (let i = 0; i < tokenized.length; i++)
26 | if (i < tokenized.length - 1)
27 | report += tokenized[i].getLispExpression() + ",";
28 | else
29 | report += tokenized[i].getLispExpression();
30 | report += "]";
31 | console.log("Parsing the expression: " + report);
32 |
33 | let root_of_abstract_syntax_tree = new TreeNode(
34 | "assembly", 0); // Value which will be returned from the parser.
35 |
36 | for (
37 | let i = 0; i < tokenized.length;
38 | i++ // First, let's deal with if-branching and while-loops...
39 | ) {
40 | if (/^if$/i.test(tokenized[i].text)) {
41 | let pointerToTheNextNewline = i + 1, condition = [];
42 | while (tokenized[pointerToTheNextNewline].text != "\n") {
43 | condition.push(tokenized[pointerToTheNextNewline]);
44 | if (pointerToTheNextNewline >= tokenized.length) {
45 | alert(
46 | "Line #" + tokenized[i].lineNumber +
47 | ': The condition after "if" doesn\'t end in a new-line character!');
48 | return root_of_abstract_syntax_tree;
49 | }
50 | pointerToTheNextNewline++;
51 | }
52 | tokenized[i].children.push(parse(condition).children[0]);
53 | tokenized.splice(i + 1, pointerToTheNextNewline - i);
54 | let pointerToTheEndIfOrElse = i + 1, counter = 1, thenClause = [];
55 | while (true) {
56 | if (pointerToTheEndIfOrElse >= tokenized.length) {
57 | alert("Line #" + tokenized[i].lineNumber +
58 | ': The "if" directive here isn\'t closed by an "endif"!');
59 | return root_of_abstract_syntax_tree;
60 | }
61 | if (/^if$/i.test(tokenized[pointerToTheEndIfOrElse].text))
62 | counter++;
63 | if (/^endif$/i.test(tokenized[pointerToTheEndIfOrElse].text))
64 | counter--;
65 | if (!counter ||
66 | (/^else$/i.test(tokenized[pointerToTheEndIfOrElse].text) &&
67 | counter == 1))
68 | break;
69 | thenClause.push(tokenized[pointerToTheEndIfOrElse]);
70 | pointerToTheEndIfOrElse++;
71 | }
72 | let lineNumberOfElseOrEndIf =
73 | tokenized[pointerToTheEndIfOrElse].lineNumber;
74 | tokenized.splice(i + 1, pointerToTheEndIfOrElse - i);
75 | tokenized[i].children.push(parse(thenClause));
76 | if (counter) {
77 | // If there is an "else"-clause
78 | let pointerToEndIf = i + 1, elseClause = [];
79 | while (counter) {
80 | if (pointerToEndIf >= tokenized.length) {
81 | alert("Line #" + lineNumberOfElseOrEndIf +
82 | ': The "else" here is not followed by an "endif"!');
83 | return root_of_abstract_syntax_tree;
84 | }
85 | if (/^if$/i.test(tokenized[pointerToEndIf].text))
86 | counter++;
87 | if (/^endif$/i.test(tokenized[pointerToEndIf].text))
88 | counter--;
89 | if (/^else$/i.test(tokenized[pointerToEndIf].text) && counter == 1) {
90 | alert("Line #" + tokenized[pointerToEndIf].lineNumber +
91 | ': Found "else" when expecting "endif"!');
92 | return root_of_abstract_syntax_tree;
93 | }
94 | elseClause.push(tokenized[pointerToEndIf]);
95 | pointerToEndIf++;
96 | }
97 | elseClause.splice(elseClause.length - 1, 1);
98 | tokenized.splice(i + 1, pointerToEndIf - i);
99 | tokenized[i].children.push(parse(elseClause));
100 | }
101 | } else if (/^endif$/i.test(tokenized[i].text) ||
102 | /^else$/i.test(tokenized[i].text)) {
103 | alert("Line #" + tokenized[i].lineNumber +
104 | ': The preprocessor directive "' + tokenized[i].text +
105 | '" found without the corresponding "if" directive!');
106 | return root_of_abstract_syntax_tree;
107 | } else if (/^while$/i.test(tokenized[i].text)) {
108 | let pointerToTheNextNewline = i + 1, condition = [];
109 | while (tokenized[pointerToTheNextNewline].text != "\n") {
110 | condition.push(tokenized[pointerToTheNextNewline]);
111 | if (pointerToTheNextNewline >= tokenized.length) {
112 | alert(
113 | "Line #" + tokenized[i].lineNumber +
114 | ': The condition after "while" doesn\'t end in a new-line character!');
115 | return root_of_abstract_syntax_tree;
116 | }
117 | pointerToTheNextNewline++;
118 | }
119 | tokenized[i].children.push(parse(condition).children[0]);
120 | tokenized.splice(i + 1, pointerToTheNextNewline - i);
121 | let pointerToEndWhile = i + 1, counter = 1, loopClause = [];
122 | while (counter) {
123 | if (pointerToEndWhile >= tokenized.length) {
124 | alert("Line #" + tokenized[i].lineNumber +
125 | ': The "while" here isn\'t being closed by an "endwhile"!');
126 | return root_of_abstract_syntax_tree;
127 | }
128 | if (/^while$/i.test(tokenized[pointerToEndWhile].text))
129 | counter++;
130 | if (/^endwhile$/i.test(tokenized[pointerToEndWhile].text))
131 | counter--;
132 | loopClause.push(tokenized[pointerToEndWhile]);
133 | pointerToEndWhile++;
134 | }
135 | loopClause.splice(loopClause.length - 1, 1);
136 | tokenized[i].children.push(parse(loopClause));
137 | tokenized.splice(i + 1, pointerToEndWhile - i);
138 | } else if (/^endwhile$/i.test(tokenized[i].text)) {
139 | alert(
140 | "Line #" + tokenized[i].lineNumber +
141 | ': The preprocessor directive "endwhile" found without the corresponding "while" directive!');
142 | return root_of_abstract_syntax_tree;
143 | }
144 | }
145 |
146 | for (
147 | let i = 0; i < tokenized.length;
148 | i++ // Then, let's deal with the parentheses.
149 | ) {
150 | if (/\($/.test(tokenized[i].text)) {
151 | // As far as I know, PicoBlaze Assembly uses only this type of
152 | // parentheses.
153 | let counter = 1;
154 | let j = i + 1;
155 | while (counter) {
156 | if (j >= tokenized.length) {
157 | alert("The parenthesis on line " + tokenized[i].lineNumber +
158 | " isn't closed!");
159 | return root_of_abstract_syntax_tree;
160 | }
161 | if (/\($/.test(tokenized[j].text))
162 | counter++;
163 | if (tokenized[j].text == ")")
164 | counter--;
165 | j++;
166 | }
167 | let newArray = [];
168 | for (let k = i + 1; k < j - 1; k++)
169 | newArray.push(tokenized[k]);
170 | tokenized.splice(i + 1, j - i - 1);
171 | tokenized[i].text += ")";
172 | tokenized[i].children = parse(newArray).children;
173 | }
174 | }
175 |
176 | // Dealing with mnemonics and preprocessor directives...
177 | for (let i = 0; i < tokenized.length; i++) {
178 | if (tokenized[i].text == "\n") {
179 | // Delete the new-line characters when you pass over them.
180 | tokenized.splice(i, 1);
181 | i--;
182 | continue;
183 | }
184 | // Check if the current token is a mnemonic or a preprocessor directive...
185 | let isMnemonicOrPreprocessorDirective = false;
186 | for (const mnemonic of mnemonics)
187 | if (RegExp("^" + mnemonic + "$", "i").test(tokenized[i].text))
188 | isMnemonicOrPreprocessorDirective = true;
189 | for (const directive of preprocessor)
190 | if (RegExp("^" + directive + "$", "i").test(tokenized[i].text) &&
191 | !/^while$/i.test(tokenized[i].text) &&
192 | !/^if$/i.test(tokenized[i].text))
193 | isMnemonicOrPreprocessorDirective = true;
194 | if (!isMnemonicOrPreprocessorDirective ||
195 | (tokenized.length === 1 && (/^enable$/i.test(tokenized[0].text) ||
196 | /^disable$/i.test(tokenized[0].text))))
197 | continue;
198 | // If the current token is a mnemonic or a preprocessor directive, seek for
199 | // the next new-line character. Unfortunately, we can't use the C++ find_if
200 | // here...
201 | let j = i;
202 | while (true) {
203 | if (j >= tokenized.length) {
204 | alert(
205 | "Internal compiler error: The assembly-lanaguage expression in line " +
206 | tokenized[i].lineNumber + " doesn't end with a new-line token!\n" +
207 | "Did you try writing something like `load (load s0, s1), s2`? That's invalid assembly code, assembly language doesn't support linguistic recursion. You need to write this:\n" +
208 | "load s1, s2\n" +
209 | "load s0, s1\n" +
210 | "instead."); // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/17
211 | return root_of_abstract_syntax_tree;
212 | }
213 | if (tokenized[j].text == "\n")
214 | break;
215 | j++;
216 | }
217 | let newArray = [];
218 | for (let k = i + 1; k < j; k++)
219 | newArray.push(tokenized[k]);
220 | tokenized[i].children = parse(newArray).children;
221 | tokenized.splice(i + 1, j - i - 1);
222 | }
223 |
224 | // Parsing arithmetic expressions...
225 | for (let i = tokenized.length - 1; i >= 0;
226 | i--) // We need to iterate backward rather than forward in order to parse
227 | // tthe expressions such as "inst --5" correctly.
228 | if ((tokenized[i].text == "+" || tokenized[i].text == "-") &&
229 | (i == 0 || tokenized[i - 1].text == "," ||
230 | tokenized[i - 1].text.substring(tokenized[i - 1].length - 1) == "(" ||
231 | tokenized[i - 1].text == "\n" ||
232 | ([
233 | "+", "-", "*", "/", "^", "&", "|", "=", "<", ">", "?", ":"
234 | ].includes(tokenized[i - 1].text) &&
235 | !tokenized[i - 1].children.length)) &&
236 | !tokenized[i].children.length) {
237 | // Unary operators
238 | if (tokenized.length == 1 ||
239 | i >=
240 | tokenized.length -
241 | 1 || // https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/42
242 | tokenized[i + 1].text == "," ||
243 | tokenized[i + 1].text == "\n") {
244 | alert("Line #" + tokenized[i].lineNumber + ": The unary operator '" +
245 | tokenized[i].text + "' has zero operands!");
246 | return root_of_abstract_syntax_tree;
247 | }
248 | tokenized[i].children = [
249 | new TreeNode("0", tokenized[i].lineNumber),
250 | tokenized[i + 1],
251 | ];
252 | tokenized.splice(i + 1, 1);
253 | }
254 |
255 | /*
256 | * To better understand how the following code (for parsing arithmetic
257 | * expressions) works, I'd suggest you to study the task "Izraz" from Infokup
258 | * 2013: https://informatika.azoo.hr/natjecanje/dogadjaj/235/rezultati
259 | */
260 |
261 | const parseBinaryOperators = (operators) => {
262 | for (let i = 0; i < tokenized.length; i++)
263 | if (operators.includes(tokenized[i].text) &&
264 | tokenized[i].children.length == 0) {
265 | if (i == 0 || tokenized[i - 1].text == "," ||
266 | tokenized[i - 1].text == "\n" || i == tokenized.length - 1 ||
267 | tokenized[i + 1].text == "," || tokenized[i + 1].text == "\n") {
268 | alert("Line #" + tokenized[i].lineNumber + ": The binary operator '" +
269 | tokenized[i].text + "' has less than two operands!");
270 | return false;
271 | }
272 | tokenized[i].children = [ tokenized[i - 1], tokenized[i + 1] ];
273 | tokenized.splice(i - 1, 1);
274 | tokenized.splice(i, 1);
275 | i--;
276 | continue;
277 | }
278 | return true;
279 | };
280 |
281 | const binaryOperators = [
282 | [ "^" ], // Exponentiation (has the highest priority).
283 | [
284 | "*", "/"
285 | ], // Multiplication and division have the same priority, that's why they
286 | // are in the same row in the 2-dimensional array.
287 | [ "+", "-" ], // So do addition and subtraction have the same priority...
288 | [ "<", ">", "=" ], [ "&" ],
289 | [ "|" ] // Logical "or" (has the lowest priority).
290 | ];
291 | for (const operators of binaryOperators)
292 | if (!parseBinaryOperators(operators))
293 | return root_of_abstract_syntax_tree;
294 |
295 | // Ternary conditional operator...
296 | /*
297 | * What is the best way of parsing right-associative operators, such as the
298 | * ternary conditional `?:` operator? In both my AEC-to-WebAssembly compiler
299 | * and the following code in my PicoBlaze assembler, I was using the "scan
300 | * backwards" method. However, I received some comments that it is considered
301 | * to be an anti-pattern. So, I opened a StackExchange question about that:
302 | * https://langdev.stackexchange.com/q/4071/330
303 | */
304 | let lastColon = tokenized.length - 2;
305 | if (lastColon > 0)
306 | while (lastColon) {
307 | if (tokenized[lastColon].text == ':' &&
308 | tokenized[lastColon + 1].text != '\n') {
309 | let questionMarkCorrespondingToTheLastColon = lastColon, counter = 1;
310 | console.log(
311 | "DEBUG: Parsing the ternary conditional operator. The colon is at the index: " +
312 | lastColon);
313 | while (counter) {
314 | questionMarkCorrespondingToTheLastColon--;
315 | if (!questionMarkCorrespondingToTheLastColon ||
316 | questionMarkCorrespondingToTheLastColon < 0) {
317 | alert(
318 | "Line #" + tokenized[lastColon].lineNumber +
319 | ": There is a colon without a matching question mark before it!");
320 | return root_of_abstract_syntax_tree;
321 | }
322 | if (tokenized[questionMarkCorrespondingToTheLastColon].text == '?')
323 | counter--;
324 | else if (tokenized[questionMarkCorrespondingToTheLastColon].text ==
325 | ':')
326 | counter++;
327 | }
328 | console.log("DEBUG: The corresponding question mark is at the index: " +
329 | questionMarkCorrespondingToTheLastColon);
330 | let nodesThatRecursionDealsWith = [];
331 | for (let i = questionMarkCorrespondingToTheLastColon + 1; i < lastColon;
332 | i++)
333 | nodesThatRecursionDealsWith.push(tokenized[i]);
334 | tokenized[questionMarkCorrespondingToTheLastColon].text = "?:";
335 | tokenized[questionMarkCorrespondingToTheLastColon].children.push(
336 | tokenized[questionMarkCorrespondingToTheLastColon - 1]);
337 | const whatTheRecursionReturned = parse(nodesThatRecursionDealsWith);
338 | if (whatTheRecursionReturned.children.length > 1) {
339 | alert("Line #" + whatTheRecursionReturned.children[1].lineNumber +
340 | ": Unexpected token `" +
341 | whatTheRecursionReturned.children[1].text + "`!");
342 | return root_of_abstract_syntax_tree;
343 | }
344 | tokenized[questionMarkCorrespondingToTheLastColon].children.push(
345 | whatTheRecursionReturned.children[0]);
346 | tokenized[questionMarkCorrespondingToTheLastColon].children.push(
347 | tokenized[lastColon + 1]);
348 | console.log(
349 | "DEBUG: The ternary conditional operator converted to LISP is: " +
350 | tokenized[questionMarkCorrespondingToTheLastColon]
351 | .getLispExpression());
352 | tokenized.splice(questionMarkCorrespondingToTheLastColon + 1,
353 | lastColon - questionMarkCorrespondingToTheLastColon +
354 | 1);
355 | tokenized.splice(questionMarkCorrespondingToTheLastColon - 1, 1);
356 | lastColon = questionMarkCorrespondingToTheLastColon;
357 | }
358 | lastColon--;
359 | }
360 |
361 | root_of_abstract_syntax_tree.children = tokenized;
362 | if (root_of_abstract_syntax_tree.checkTypes())
363 | return root_of_abstract_syntax_tree;
364 |
365 | return new TreeNode("assembly", 0);
366 | }
367 |
--------------------------------------------------------------------------------