osiris_set_std/control.rs
1//! 0x01 Control operations (branch, thread, future, message, configuration)
2//! * `0x0101` [jump]:`address`
3//! * `0x0102` [call]:`address`
4//! * `0x0103` return ([call_return])
5//! * `0x0104` [goto] `direct:32`
6//! * `0x0105` [gosub] `direct:32`
7//! * `0x0108` loop:`counter_init` ([loop_init])
8//! * `0x0109` [next] `direct:32`
9//! * `0x010A` [goto_if]:`CMP` `direct:32`
10//! * `0x010B` [gosub_if]:`CMP` `direct:32`
11//! * `0x010C` [goto_check]:`checked` `direct:32`
12//! * `0x010D` [gosub_check]:`checked` `direct:32`
13//! * `0x010E` [skip_if]:`CMP`
14//! * `0x010F` [skip_check]:`checked`
15//! * `0x01FF` [halt]
16//!
17//!
18//! * TODO 0x0170 FORK $target :
19//! invoke [Cpu] which `JUMP`s to `$target` with a new context instance,
20//! while the current [Cpu] continues with the next instruction.
21//! * TODO 0x0180 PROMISE ?
22//! * TODO 0x0181 RESOLVE ?
23//! * TODO 0x0188 PRODUCE ?
24//! * TODO 0x0187 CONSUME ?
25//! * TODO ... MESSAGES
26//! * TODO 0x01F0 CHANGE FREQUENCY
27//!
28
29use std::collections::HashMap;
30
31use osiris_data::data::identification::Address;
32use osiris_process::compare::Compare;
33use osiris_process::operation::error::{OperationError, OperationResult};
34use osiris_process::operation::{Operation, OperationSet};
35use osiris_process::operation::scheme::{ArgumentScheme, ArgumentType, InstructionScheme, OperationId};
36use osiris_process::processor::Cpu;
37
38use crate::logic::FALSE;
39
40pub const SET_MASK: u16 = 0x0100;
41
42/// 0x0101
43pub const JUMP: OperationId = OperationId::new(SET_MASK | 0x01);
44
45/// 0x0102
46pub const CALL: OperationId = OperationId::new(SET_MASK | 0x02);
47
48/// 0x0103
49pub const CALL_RETURN: OperationId = OperationId::new(SET_MASK | 0x03);
50
51/// 0x0104
52pub const GOTO: OperationId = OperationId::new(SET_MASK | 0x04);
53
54/// 0x0105
55pub const GOSUB: OperationId = OperationId::new(SET_MASK | 0x05);
56
57/// 0x0108
58pub const LOOP: OperationId = OperationId::new(SET_MASK | 0x08);
59
60/// 0x0109
61pub const NEXT: OperationId = OperationId::new(SET_MASK | 0x09);
62
63/// 0x010A
64pub const GOTO_IF: OperationId = OperationId::new(SET_MASK | 0x0A);
65
66/// 0x010B
67pub const GOSUB_IF: OperationId = OperationId::new(SET_MASK | 0x0B);
68
69/// 0x010C
70pub const GOTO_CHECK: OperationId = OperationId::new(SET_MASK | 0x0C);
71
72/// 0x010D
73pub const GOSUB_CHECK: OperationId = OperationId::new(SET_MASK | 0x0D);
74
75/// 0x010E
76pub const SKIP_IF: OperationId = OperationId::new(SET_MASK | 0x0E);
77
78/// 0x010F
79pub const SKIP_CHECK: OperationId = OperationId::new(SET_MASK | 0x0F);
80
81/// 0x01FF
82pub const HALT: OperationId = OperationId::new(SET_MASK | 0xFF);
83
84fn _jump(cpu: &mut Cpu, address: Address) -> OperationResult<()> {
85 match cpu.point_instruction(address) {
86 Ok(_) => Ok(()),
87 Err(err) => Err(OperationError::MemoryError(err)),
88 }
89}
90
91fn _goto(cpu: &mut Cpu, current: Address, target: u32) -> OperationResult<()> {
92 _jump(cpu, Address::new(0xFFFFFFFF_00000000 & current.to_u64() | target as u64))
93}
94
95/// # `0x0101` [jump]:`address`
96///
97/// ## Target
98/// - The register containing the target address in memory.
99///
100/// ## Arguments
101/// - None.
102///
103/// ## Operation
104/// - `current = r:target`
105///
106/// ## Errors
107/// - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentScheme::NoArgument],
108/// - [OperationError::MemoryError] if a memory error occurs.
109pub fn jump(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
110 scheme.argument.get_no_argument()?;
111 _jump(cpu, Address::from_word(cpu.bank_get(scheme.target)))
112}
113
114/// # `0x0102` [call]:`address`
115///
116/// ## Target
117/// - The register containing the target address in memory.
118///
119/// ## Arguments
120/// - None.
121///
122/// ## Operation
123/// - `push:current`
124/// - `jump:target`
125///
126/// ## Errors
127/// - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentScheme::NoArgument],
128/// - [OperationError::MemoryError] if a memory error occurs.
129pub fn call(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
130 cpu.stack_push(cpu.state.current.to_word());
131 jump(cpu, scheme)
132}
133
134/// * `0x0103` return ([call_return])
135///
136/// ## Target
137/// - None
138///
139/// ## Arguments
140/// - None.
141///
142/// ## Operation
143/// - `current = pop`
144///
145/// ## Errors
146/// - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentScheme::NoArgument],
147/// - [OperationError::CannotReturnFromEmptyStack] if the stack is empty,
148/// - [OperationError::MemoryError] if a memory error occurs.
149pub fn call_return(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
150 scheme.argument.get_no_argument()?;
151 match cpu.stack_pop() {
152 None => { Err(OperationError::CannotReturnFromEmptyStack) }
153 Some(addr) => {
154 let result = _jump(cpu, Address::from_word(addr));
155 cpu.state.current.increment();
156 result
157 }
158 }
159}
160
161/// # `0x0104` [goto] `direct:32`
162///
163/// Performs a short jump (offset is 32 bits long).
164///
165/// ## Target
166/// - None.
167///
168/// ## Arguments
169/// - direct:**32**
170///
171/// ## Operation
172/// - `current = (current.top) | direct`
173///
174/// ## Errors
175/// - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentType::OneU32],
176/// - [OperationError::MemoryError] if a memory error occurs.
177pub fn goto(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
178 let target = scheme.argument.get_one_u32()?;
179 _goto(cpu, cpu.state.current, target)
180}
181
182/// # `0x0105` [gosub] `direct:32`
183///
184/// Performs a short call (offset is 32 bits long).
185///
186/// ## Target
187/// - None.
188///
189/// ## Arguments
190/// - direct:**32**
191///
192/// ## Operation
193/// - `current = direct`
194///
195/// ## Errors
196/// - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentType::OneU32],
197/// - [OperationError::MemoryError] if a memory error occurs.
198pub fn gosub(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
199 cpu.stack_push(cpu.state.current.to_word());
200 goto(cpu, scheme)
201}
202
203
204/// * `0x0108` loop:`counter_init` ([loop_init])
205///
206/// ## Target
207/// - The register holding the initial value
208///
209/// ## Arguments
210/// - None.
211///
212/// ## Operation
213/// - `counter = r:target`
214///
215/// ## Errors
216/// - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentScheme::NoArgument],
217pub fn loop_init(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
218 scheme.argument.get_no_argument()?;
219 cpu.state.operation.counter = cpu.bank_get(scheme.target);
220 cpu.debug("for", format!("{:016x}", cpu.state.operation.counter.to_u64()), "⚙️".to_string());
221 Ok(())
222}
223
224
225/// * `0x0109` [next] `direct:32`
226///
227/// ## Target
228/// - None.
229///
230/// ## Arguments
231/// - direct:**32**
232///
233/// ## Operation
234/// - `counter -= 1`
235/// - `if counter >= 0 : current = direct`
236///
237/// ## Errors
238/// - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentType::OneU32],
239pub fn next(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
240 cpu.decrease_loop_counter();
241 if cpu.state.operation.counter.to_u64() > 0 {
242 goto(cpu, scheme)?;
243 }
244 Ok(())
245}
246
247fn fn_if(state: i64, operator: u16) -> OperationResult<bool> {
248 let cmp_op = Compare::from(operator);
249 match cmp_op {
250 None => Err(OperationError::Panic(format!("Invalid argument for comparison : {}", operator)))?,
251 Some(op) => Ok(op.is(state))
252 }
253}
254
255
256/// # `0x010A` [goto_if]:`CMP` `direct:32`
257///
258/// Jumps to a target address if the last comparison corresponds the selected operator.
259///
260/// ## Target
261/// - CMP : LT(<) / LE(<=) / NE(!=) / EQ(==) / GE(>=) / GT(>)
262///
263/// ## Arguments
264/// - The address to jump to.
265///
266/// ## Errors
267/// - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentType::OneU32],
268pub fn goto_if(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
269 let target = scheme.argument.get_one_u32()?;
270 let state = cpu.state.operation.compare;
271 if fn_if(state, scheme.target.to_u16())? {
272 _goto(cpu, cpu.state.current, target)
273 } else { Ok(()) }
274}
275
276/// # `0x010B` [gosub_if]:`CMP` `direct:32`
277///
278/// Calls a procedure stored at the given address if the last comparison corresponds the selected operator.
279///
280/// ## Target
281/// - CMP : LT(<) / LE(<=) / NE(!=) / EQ(==) / GE(>=) / GT(>)
282///
283/// ## Arguments
284/// - The address to jump to.
285///
286/// ## Errors
287/// - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentType::OneU32],
288pub fn gosub_if(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
289 cpu.stack_push(cpu.state.current.to_word());
290 goto_if(cpu, scheme)
291}
292
293/// # `0x010C` [goto_check]:`checked` `direct:32`
294///
295/// Skip the next instruction if the register is not FALSE.
296///
297/// ## Target
298/// - The register to check.
299///
300/// ## Arguments
301/// - The address to jump to.
302///
303/// ## Errors
304/// - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentType::OneU32],
305pub fn goto_check(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
306 let argument = scheme.argument.get_one_u32()?;
307 if cpu.bank_get(scheme.target) != FALSE {
308 _goto(cpu, cpu.state.current, argument)?;
309 }
310 Ok(())
311}
312
313/// # `0x010D` [gosub_check]:`checked` `direct:32`
314///
315/// Skip the next instruction if the register is not FALSE.
316///
317/// ## Target
318/// - The register to check.
319///
320/// ## Arguments
321/// - The address to jump to.
322///
323/// ## Errors
324/// - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentType::OneU32],
325pub fn gosub_check(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
326 let argument = scheme.argument.get_one_u32()?;
327 if cpu.bank_get(scheme.target) != FALSE {
328 cpu.stack_push(cpu.state.current.to_word());
329 _goto(cpu, cpu.state.current, argument)?;
330 }
331 Ok(())
332}
333
334/// # `0x010E` [skip_if]:`CMP`
335///
336/// Jumps to a target address if the last comparison corresponds the selected operator.
337///
338/// ## Target
339/// - CMP : LT(<) / LE(<=) / NE(!=) / EQ(==) / GE(>=) / GT(>)
340///
341/// ## Arguments
342/// - None.
343///
344/// ## Errors
345/// - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentType::NoArgument],
346pub fn skip_if(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
347 scheme.argument.get_no_argument()?;
348 let state = cpu.state.operation.compare;
349 if fn_if(state, scheme.target.to_u16())? {
350 cpu.state.flag_skip = true;
351 }
352 Ok(())
353}
354
355/// # `0x010F` [skip_check]:`checked`
356///
357/// Skip the next instruction if the register is not FALSE.
358///
359/// ## Target
360/// - The register to check.
361///
362/// ## Arguments
363/// - None.
364///
365/// ## Errors
366/// - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentScheme::NoArgument],
367pub fn skip_check(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
368 scheme.argument.get_no_argument()?;
369 if cpu.bank_get(scheme.target) != FALSE {
370 cpu.state.flag_skip = true;
371 }
372 Ok(())
373}
374
375/// * `0x01FF` [halt]
376///
377/// ## Target
378/// - None.
379///
380/// ## Arguments
381/// - None.
382///
383/// ## Operation
384/// - `HALT_FLAG = true`
385///
386/// ## Errors
387/// - [OperationError::InvalidArgumentType] if the provided argument is not an [ArgumentScheme::NoArgument],
388pub fn halt(cpu: &mut Cpu, scheme: InstructionScheme) -> OperationResult<()> {
389 scheme.argument.get_no_argument()?;
390 cpu.halt();
391 Ok(())
392}
393
394/// Returns control operations.
395///
396/// ## Operations
397///
398/// * `0x0101` [jump]:`address`
399/// * `0x0102` [call]:`address`
400/// * `0x0103` return ([call_return])
401/// * `0x0104` [goto] `direct:32`
402/// * `0x0105` [gosub] `direct:32`
403/// * `0x0108` loop:`counter_init` ([loop_init])
404/// * `0x0109` [next] `direct:32`
405/// * `0x010A` [goto_if]:`CMP` `direct:32`
406/// * `0x010B` [gosub_if]:`CMP` `direct:32`
407/// * `0x010C` [goto_check]:`checked` `direct:32`
408/// * `0x010D` [gosub_check]:`checked` `direct:32`
409/// * `0x010E` [skip_if]:`CMP`
410/// * `0x010F` [skip_check]:`checked`
411/// * `0x01FF` [halt]
412pub fn operation_set() -> OperationSet {
413 let mut set: OperationSet = HashMap::new();
414 set.insert(
415 JUMP,
416 Operation::new(JUMP, "jump".to_string(), true, ArgumentType::NoArgument, jump),
417 );
418 set.insert(
419 CALL,
420 Operation::new(CALL, "call".to_string(), true, ArgumentType::NoArgument, call),
421 );
422 set.insert(
423 CALL_RETURN,
424 Operation::new(CALL_RETURN, "return".to_string(), false, ArgumentType::NoArgument, call_return),
425 );
426 set.insert(
427 GOTO,
428 Operation::new(GOTO, "goto".to_string(), false, ArgumentType::OneU32, goto),
429 );
430 set.insert(
431 GOSUB,
432 Operation::new(GOSUB, "gosub".to_string(), false, ArgumentType::OneU32, gosub),
433 );
434 set.insert(
435 LOOP,
436 Operation::new(LOOP, "loop".to_string(), true, ArgumentType::NoArgument, loop_init),
437 );
438 set.insert(
439 NEXT,
440 Operation::new(NEXT, "next".to_string(), false, ArgumentType::OneU32, next),
441 );
442 set.insert(
443 GOTO_IF,
444 Operation::new(GOTO_IF, "goto-if".to_string(), true, ArgumentType::OneU32, goto_if),
445 );
446 set.insert(
447 GOSUB_IF,
448 Operation::new(GOSUB_IF, "gosub-if".to_string(), true, ArgumentType::OneU32, gosub_if),
449 );
450 set.insert(
451 GOTO_CHECK,
452 Operation::new(GOTO_CHECK, "goto-check".to_string(), true, ArgumentType::OneU32, goto_check),
453 );
454 set.insert(
455 GOSUB_CHECK,
456 Operation::new(GOSUB_CHECK, "gosub-check".to_string(), true, ArgumentType::OneU32, gosub_check),
457 );
458 set.insert(
459 SKIP_IF,
460 Operation::new(SKIP_IF, "skip-if".to_string(), true, ArgumentType::NoArgument, skip_if),
461 );
462 set.insert(
463 SKIP_CHECK,
464 Operation::new(SKIP_CHECK, "skip-check".to_string(), true, ArgumentType::NoArgument, skip_check),
465 );
466 set.insert(
467 HALT,
468 Operation::new(HALT, "halt".to_string(), false, ArgumentType::NoArgument, halt),
469 );
470 set
471}