-
Notifications
You must be signed in to change notification settings - Fork 0
/
day19.p
167 lines (138 loc) · 9.27 KB
/
day19.p
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
/*
--- Day 19: Go With The Flow ---
With the Elves well on their way constructing the North Pole base, you turn your attention back to understanding the inner workings of programming the device.
You can't help but notice that the device's opcodes don't contain any flow control like jump instructions. The device's manual goes on to explain:
"In programs where flow control is required, the instruction pointer can be bound to a register so that it can be manipulated directly. This way, setr/seti can function as absolute jumps, addr/addi can function as relative jumps, and other opcodes can cause truly fascinating effects."
This mechanism is achieved through a declaration like #ip 1, which would modify register 1 so that accesses to it let the program indirectly access the instruction pointer itself. To compensate for this kind of binding, there are now six registers (numbered 0 through 5); the five not bound to the instruction pointer behave as normal. Otherwise, the same rules apply as the last time you worked with this device.
When the instruction pointer is bound to a register, its value is written to that register just before each instruction is executed, and the value of that register is written back to the instruction pointer immediately after each instruction finishes execution. Afterward, move to the next instruction by adding one to the instruction pointer, even if the value in the instruction pointer was just updated by an instruction. (Because of this, instructions must effectively set the instruction pointer to the instruction before the one they want executed next.)
The instruction pointer is 0 during the first instruction, 1 during the second, and so on. If the instruction pointer ever causes the device to attempt to load an instruction outside the instructions defined in the program, the program instead immediately halts. The instruction pointer starts at 0.
It turns out that this new information is already proving useful: the CPU in the device is not very powerful, and a background process is occupying most of its time. You dump the background process' declarations and instructions to a file (your puzzle input), making sure to use the names of the opcodes rather than the numbers.
For example, suppose you have the following program:
#ip 0
seti 5 0 1
seti 6 0 2
addi 0 1 0
addr 1 2 3
setr 1 0 0
seti 8 0 4
seti 9 0 5
When executed, the following instructions are executed. Each line contains the value of the instruction pointer at the time the instruction started, the values of the six registers before executing the instructions (in square brackets), the instruction itself, and the values of the six registers after executing the instruction (also in square brackets).
ip=0 [0, 0, 0, 0, 0, 0] seti 5 0 1 [0, 5, 0, 0, 0, 0]
ip=1 [1, 5, 0, 0, 0, 0] seti 6 0 2 [1, 5, 6, 0, 0, 0]
ip=2 [2, 5, 6, 0, 0, 0] addi 0 1 0 [3, 5, 6, 0, 0, 0]
ip=4 [4, 5, 6, 0, 0, 0] setr 1 0 0 [5, 5, 6, 0, 0, 0]
ip=6 [6, 5, 6, 0, 0, 0] seti 9 0 5 [6, 5, 6, 0, 0, 9]
In detail, when running this program, the following events occur:
The first line (#ip 0) indicates that the instruction pointer should be bound to register 0 in this program. This is not an instruction, and so the value of the instruction pointer does not change during the processing of this line.
The instruction pointer contains 0, and so the first instruction is executed (seti 5 0 1). It updates register 0 to the current instruction pointer value (0), sets register 1 to 5, sets the instruction pointer to the value of register 0 (which has no effect, as the instruction did not modify register 0), and then adds one to the instruction pointer.
The instruction pointer contains 1, and so the second instruction, seti 6 0 2, is executed. This is very similar to the instruction before it: 6 is stored in register 2, and the instruction pointer is left with the value 2.
The instruction pointer is 2, which points at the instruction addi 0 1 0. This is like a relative jump: the value of the instruction pointer, 2, is loaded into register 0. Then, addi finds the result of adding the value in register 0 and the value 1, storing the result, 3, back in register 0. Register 0 is then copied back to the instruction pointer, which will cause it to end up 1 larger than it would have otherwise and skip the next instruction (addr 1 2 3) entirely. Finally, 1 is added to the instruction pointer.
The instruction pointer is 4, so the instruction setr 1 0 0 is run. This is like an absolute jump: it copies the value contained in register 1, 5, into register 0, which causes it to end up in the instruction pointer. The instruction pointer is then incremented, leaving it at 6.
The instruction pointer is 6, so the instruction seti 9 0 5 stores 9 into register 5. The instruction pointer is incremented, causing it to point outside the program, and so the program ends.
What value is left in register 0 when the background process halts?
*/
DEFINE VARIABLE iOperations AS INTEGER NO-UNDO.
DEFINE VARIABLE iPC AS INTEGER NO-UNDO.
DEFINE VARIABLE iPCReg AS INTEGER NO-UNDO.
DEFINE VARIABLE iProgSeq AS INTEGER NO-UNDO.
DEFINE VARIABLE iRegister AS INTEGER EXTENT 6 NO-UNDO.
FUNCTION bin_and RETURNS INTEGER
(iFirstOperand AS INTEGER, iSecondOperand AS INTEGER):
DEFINE VARIABLE iLoopCounter AS INTEGER NO-UNDO.
DEFINE VARIABLE iANDedResult AS INTEGER NO-UNDO.
DO iLoopCounter = 1 to 32:
IF GET-BITS(iFirstOperand, iLoopCounter, 1) + GET-BITS(iSecondOperand, iLoopCounter, 1) EQ 2 THEN
iANDedResult = iANDedResult + EXP(2, iLoopCounter - 1).
END.
RETURN iANDedResult.
END FUNCTION.
FUNCTION bin_or RETURNS INTEGER
(iFirstOperand AS INTEGER, iSecondOperand AS INTEGER):
DEFINE VARIABLE iLoopCounter AS INTEGER NO-UNDO.
DEFINE VARIABLE iORedResult AS INTEGER NO-UNDO.
DO iLoopCounter = 1 to 32:
IF GET-BITS(iFirstOperand, iLoopCounter, 1) + GET-BITS(iSecondOperand, iLoopCounter, 1) GE 1 THEN
iORedResult = iORedResult + EXP(2, iLoopCounter - 1).
END.
RETURN iORedResult.
END FUNCTION.
DEFINE TEMP-TABLE ttProgram NO-UNDO
FIELD id AS INTEGER
FIELD cOperation AS CHAR
FIELD iA AS INTEGER
FIELD iB AS INTEGER
FIELD iC AS INTEGER
INDEX ix IS PRIMARY UNIQUE id.
/*****************************************************************************/
ETIME(YES).
/* load the program */
INPUT FROM C:\User\JCCARDOT\Perso\Travail\aoc\aoc2018\day19.txt.
REPEAT:
CREATE ttProgram.
ASSIGN ttProgram.id = iProgSeq.
IMPORT DELIMITER " " ttProgram.cOperation ttProgram.iA ttProgram.iB ttProgram.iC.
IF ttProgram.cOperation = "#ip" THEN DO:
iPCReg = ttProgram.iA + 1.
DELETE ttProgram.
END.
ELSE
iProgSeq = iProgSeq + 1.
END.
DELETE ttProgram.
INPUT CLOSE.
/* FOR EACH ttProgram: DISP ttProgram. END. */
/* run it */
DO WHILE TRUE:
FIND ttProgram WHERE ttProgram.id = iPC NO-ERROR.
IF NOT AVAILABLE ttProgram THEN LEAVE.
iRegister[iPCReg] = iPC.
RUN doOperation.
iPC = iRegister[iPCReg] + 1.
/* MESSAGE iPC SKIP iRegister[1] iRegister[2] iRegister[3] iRegister[4] iRegister[5] iRegister[6] */
/* VIEW-AS ALERT-BOX INFO BUTTONS OK. */
iOperations = iOperations + 1.
IF iOperations MOD 10000 = 0 THEN DISP iOperations ETIME SKIP iPC SKIP iRegister[1] iRegister[2] iRegister[3] iRegister[4] iRegister[5] iRegister[6].
END.
MESSAGE ETIME SKIP
iRegister[1]
VIEW-AS ALERT-BOX INFO BUTTONS OK.
/* 113022 */
/* 2223 */
PROCEDURE doOperation:
CASE ttProgram.cOperation:
WHEN "addr" THEN
iRegister[iC + 1] = iRegister[iA + 1] + iRegister[iB + 1].
WHEN "addi" THEN
iRegister[iC + 1] = iRegister[iA + 1] + iB.
WHEN "mulr" THEN
iRegister[iC + 1] = iRegister[iA + 1] * iRegister[iB + 1].
WHEN "muli" THEN
iRegister[iC + 1] = iRegister[iA + 1] * iB.
WHEN "banr" THEN
iRegister[iC + 1] = bin_and(iRegister[iA + 1], iRegister[iB + 1]).
WHEN "bani" THEN
iRegister[iC + 1] = bin_and(iRegister[iA + 1], iB).
WHEN "borr" THEN
iRegister[iC + 1] = bin_or(iRegister[iA + 1], iRegister[iB + 1]).
WHEN "bori" THEN
iRegister[iC + 1] = bin_or(iRegister[iA + 1], iB).
WHEN "setr" THEN
iRegister[iC + 1] = iRegister[iA + 1].
WHEN "seti" THEN
iRegister[iC + 1] = iA.
WHEN "gtir" THEN
iRegister[iC + 1] = IF iA > iRegister[iB + 1] THEN 1 ELSE 0.
WHEN "gtri" THEN
iRegister[iC + 1] = IF iRegister[iA + 1] > iB THEN 1 ELSE 0.
WHEN "gtrr" THEN
iRegister[iC + 1] = IF iRegister[iA + 1] > iRegister[iB + 1] THEN 1 ELSE 0.
WHEN "eqir" THEN
iRegister[iC + 1] = IF iA = iRegister[iB + 1] THEN 1 ELSE 0.
WHEN "eqri" THEN
iRegister[iC + 1] = IF iRegister[iA + 1] = iB THEN 1 ELSE 0.
WHEN "eqrr" THEN
iRegister[iC + 1] = IF iRegister[iA + 1] = iRegister[iB + 1] THEN 1 ELSE 0.
OTHERWISE MESSAGE "unknown operation " ttProgram.cOperation
VIEW-AS ALERT-BOX INFO BUTTONS OK.
END CASE.
END PROCEDURE.