1use stet_core::context::Context;
8use stet_core::dict::DictKey;
9use stet_core::error::PsError;
10use stet_core::object::{EntityId, ObjFlags, PsObject, PsValue};
11use stet_core::tokenizer::{Token, Tokenizer, stream_next_token};
12
13pub fn eval(ctx: &mut Context) -> Result<(), PsError> {
19 while let Some(mut obj) = ctx.e_stack.try_pop() {
20 if let Some(ref flag) = ctx.interrupt_flag
21 && flag.load(std::sync::atomic::Ordering::Relaxed)
22 {
23 return Err(PsError::Quit);
24 }
25 ctx.check_deadline()?;
26 if obj.flags.is_deferred() {
29 obj.flags.clear_deferred();
30 if let Err(e) = ctx.o_stack.push(obj) {
31 dispatch_error(ctx, &e)?;
32 }
33 continue;
34 }
35
36 if obj.flags.is_literal()
38 && !matches!(
39 obj.value,
40 PsValue::Stopped
41 | PsValue::Loop(_)
42 | PsValue::HardReturn
43 | PsValue::DictEnd(_)
44 | PsValue::ExecArray { .. }
45 )
46 {
47 if let Err(e) = ctx.o_stack.push(obj) {
48 dispatch_error(ctx, &e)?;
49 }
50 continue;
51 }
52
53 match eval_one(ctx, obj) {
54 Ok(()) => {}
55 Err(PsError::Quit) => return Ok(()),
56 Err(PsError::Stop) => {
57 if unwind_to_stopped(ctx).is_ok() {
58 if let Err(e) = ctx.o_stack.push(PsObject::bool(true)) {
59 dispatch_error(ctx, &e)?;
60 }
61 } else {
62 return Err(PsError::Stop);
63 }
64 }
65 Err(PsError::Exit) => {
66 if let Err(e) = unwind_to_loop(ctx) {
67 dispatch_error(ctx, &e)?;
68 }
69 }
70 Err(e) => {
71 dispatch_error(ctx, &e)?;
72 }
73 }
74 }
75 Ok(())
76}
77
78pub fn exec_sync(ctx: &mut Context, proc_obj: PsObject) -> Result<(), PsError> {
85 if ctx.exec_sync_depth >= MAX_EXEC_SYNC_DEPTH {
91 return Err(PsError::ExecStackOverflow);
92 }
93 ctx.exec_sync_depth += 1;
94 let result = exec_sync_inner(ctx, proc_obj);
95 ctx.exec_sync_depth -= 1;
97 result
98}
99
100const MAX_EXEC_SYNC_DEPTH: u32 = 100;
107
108fn exec_sync_inner(ctx: &mut Context, proc_obj: PsObject) -> Result<(), PsError> {
109 let base_depth = ctx.e_stack.len();
110 ctx.e_stack.push(proc_obj)?;
111
112 while ctx.e_stack.len() > base_depth {
113 if let Some(ref flag) = ctx.interrupt_flag
114 && flag.load(std::sync::atomic::Ordering::Relaxed)
115 {
116 return Err(PsError::Quit);
117 }
118 ctx.check_deadline()?;
119 let Some(mut obj) = ctx.e_stack.try_pop() else {
120 break;
121 };
122
123 if obj.flags.is_deferred() {
124 obj.flags.clear_deferred();
125 ctx.o_stack.push(obj)?;
126 continue;
127 }
128
129 if obj.flags.is_literal()
130 && !matches!(
131 obj.value,
132 PsValue::Stopped
133 | PsValue::Loop(_)
134 | PsValue::HardReturn
135 | PsValue::DictEnd(_)
136 | PsValue::ExecArray { .. }
137 )
138 {
139 ctx.o_stack.push(obj)?;
140 continue;
141 }
142
143 match eval_one(ctx, obj) {
144 Ok(()) => {}
145 Err(PsError::Quit) => return Ok(()),
146 Err(PsError::Stop) => {
147 if unwind_to_stopped_bounded(ctx, base_depth).is_ok() {
149 ctx.o_stack.push(PsObject::bool(true))?;
150 } else {
151 return Err(PsError::Stop);
152 }
153 }
154 Err(PsError::Exit) => {
155 if unwind_to_loop_bounded(ctx, base_depth).is_err() {
156 return Err(PsError::Exit);
157 }
158 }
159 Err(e) => {
160 dispatch_error(ctx, &e)?;
161 }
162 }
163 }
164
165 Ok(())
166}
167
168fn unwind_to_stopped_bounded(ctx: &mut Context, min_depth: usize) -> Result<(), PsError> {
170 while ctx.e_stack.len() > min_depth {
171 if let Some(obj) = ctx.e_stack.try_pop() {
172 match obj.value {
173 PsValue::Stopped => return Ok(()),
174 PsValue::DictEnd(expected) => {
175 pop_dict_end(ctx, expected);
176 }
177 _ => {}
178 }
179 }
180 }
181 Err(PsError::Stop)
182}
183
184fn unwind_to_loop_bounded(ctx: &mut Context, min_depth: usize) -> Result<(), PsError> {
186 while ctx.e_stack.len() > min_depth {
187 if let Some(obj) = ctx.e_stack.try_pop() {
188 match obj.value {
189 PsValue::Loop(_) => return Ok(()),
190 PsValue::Stopped => {
191 ctx.e_stack.push(obj)?;
192 return Err(PsError::InvalidExit);
193 }
194 PsValue::DictEnd(expected) => {
195 pop_dict_end(ctx, expected);
196 }
197 _ => {}
198 }
199 }
200 }
201 Err(PsError::InvalidExit)
202}
203
204fn eval_one(ctx: &mut Context, obj: PsObject) -> Result<(), PsError> {
208 match obj.value {
209 PsValue::Int(_)
211 | PsValue::Real(_)
212 | PsValue::Bool(_)
213 | PsValue::Null
214 | PsValue::Mark
215 | PsValue::DictMark => {
216 ctx.o_stack.push(obj)?;
217 }
218
219 PsValue::Operator(opcode) => {
221 let func = ctx.operators[opcode.0 as usize].func;
222 if let Err(e) = func(ctx) {
223 ctx.current_operator = Some(ctx.operators[opcode.0 as usize].name);
224 return Err(e);
225 }
226 }
227
228 PsValue::Name(name_id) => {
230 let key = DictKey::Name(name_id);
231 match ctx.dict_load(&key) {
232 Some(val) => {
233 if val.flags.is_executable() {
234 ctx.e_stack.push(val)?;
235 } else {
236 ctx.o_stack.push(val)?;
237 }
238 }
239 None => {
240 ctx.current_operator = Some(name_id);
241 return Err(PsError::Undefined);
242 }
243 }
244 }
245
246 PsValue::Array { entity, start, len } => {
248 exec_procedure(ctx, entity, start, len)?;
249 }
250
251 PsValue::String { entity, start, len } => {
256 let bytes = ctx.strings.get(entity, start, len);
257 let (ptr, byte_len) = (bytes.as_ptr(), bytes.len());
258 let bytes = unsafe { std::slice::from_raw_parts(ptr, byte_len) };
259 if let Some((tok_obj, consumed, is_immediate, auto_exec)) =
260 scan_token_from_bytes(ctx, bytes)?
261 {
262 let newlines = count_newlines(&bytes[..consumed]);
263 ctx.current_source_line += newlines;
264
265 let remaining = len - consumed as u32;
267 if remaining > 0 {
268 ctx.e_stack.push(PsObject {
269 value: PsValue::String {
270 entity,
271 start: start + consumed as u32,
272 len: remaining,
273 },
274 flags: ObjFlags::executable_composite(),
275 })?;
276 }
277
278 if auto_exec {
279 ctx.e_stack.push(tok_obj)?;
280 } else {
281 dispatch_scanned_token(ctx, tok_obj, is_immediate)?;
282 }
283 }
284 }
285
286 PsValue::ExecArray {
294 entity,
295 start,
296 len,
297 pos,
298 } => {
299 let mut cur_pos = pos;
300 let ea_flags = obj.flags;
301
302 macro_rules! dispatch_op {
309 ($opcode:expr) => {{
310 let e_depth = ctx.e_stack.len();
311 let func = ctx.operators[$opcode.0 as usize].func;
312 let result = func(ctx);
313 match result {
314 Ok(()) => {
315 if ctx.e_stack.len() > e_depth {
316 if cur_pos < len {
319 ctx.e_stack.insert_at(
320 e_depth,
321 PsObject {
322 value: PsValue::ExecArray {
323 entity,
324 start,
325 len,
326 pos: cur_pos,
327 },
328 flags: ea_flags,
329 },
330 )?;
331 }
332 true } else {
334 false }
336 }
337 Err(e) => {
338 ctx.current_operator = Some(ctx.operators[$opcode.0 as usize].name);
340 if cur_pos < len {
341 ctx.e_stack.push(PsObject {
342 value: PsValue::ExecArray {
343 entity,
344 start,
345 len,
346 pos: cur_pos,
347 },
348 flags: ea_flags,
349 })?;
350 }
351 return Err(e);
352 }
353 }
354 }};
355 }
356
357 'ea_loop: loop {
358 let elem = ctx.arrays.get_element(entity, start + cur_pos);
359 cur_pos += 1;
360
361 match elem.value {
362 PsValue::Operator(opcode) => {
363 if dispatch_op!(opcode) {
364 break 'ea_loop;
365 }
366 }
367
368 PsValue::Name(name_id) if elem.flags.is_executable() => {
369 let idx = name_id.0 as usize;
372 let val = if idx < ctx.name_resolve_cache.len() {
373 let (ver, cached) = ctx.name_resolve_cache[idx];
374 if ver == ctx.dict_version {
375 cached
376 } else {
377 match ctx.dict_load(&DictKey::Name(name_id)) {
378 Some(v) => v,
379 None => {
380 ctx.current_operator = Some(name_id);
381 if cur_pos < len {
382 ctx.e_stack.push(PsObject {
383 value: PsValue::ExecArray {
384 entity,
385 start,
386 len,
387 pos: cur_pos,
388 },
389 flags: ea_flags,
390 })?;
391 }
392 return Err(PsError::Undefined);
393 }
394 }
395 }
396 } else {
397 match ctx.dict_load(&DictKey::Name(name_id)) {
398 Some(v) => v,
399 None => {
400 ctx.current_operator = Some(name_id);
401 if cur_pos < len {
402 ctx.e_stack.push(PsObject {
403 value: PsValue::ExecArray {
404 entity,
405 start,
406 len,
407 pos: cur_pos,
408 },
409 flags: ea_flags,
410 })?;
411 }
412 return Err(PsError::Undefined);
413 }
414 }
415 };
416
417 match val.value {
418 PsValue::Operator(opcode) => {
419 if dispatch_op!(opcode) {
420 break 'ea_loop;
421 }
422 }
423 _ => {
424 if cur_pos < len {
426 ctx.e_stack.push(PsObject {
427 value: PsValue::ExecArray {
428 entity,
429 start,
430 len,
431 pos: cur_pos,
432 },
433 flags: ea_flags,
434 })?;
435 }
436 if val.flags.is_executable() {
437 ctx.e_stack.push(val)?;
438 } else {
439 ctx.o_stack.push(val)?;
440 }
441 break 'ea_loop;
442 }
443 }
444 }
445
446 _ => {
447 if elem.is_array_type() && elem.flags.is_executable() {
448 ctx.o_stack.push(elem)?;
450 } else if matches!(
451 elem.value,
452 PsValue::Int(_)
453 | PsValue::Real(_)
454 | PsValue::Bool(_)
455 | PsValue::Null
456 | PsValue::Mark
457 | PsValue::DictMark
458 ) || elem.flags.is_literal()
459 {
460 ctx.o_stack.push(elem)?;
462 } else {
463 if cur_pos < len {
465 ctx.e_stack.push(PsObject {
466 value: PsValue::ExecArray {
467 entity,
468 start,
469 len,
470 pos: cur_pos,
471 },
472 flags: ea_flags,
473 })?;
474 }
475 ctx.e_stack.push(elem)?;
476 break 'ea_loop;
477 }
478 }
479 }
480
481 if cur_pos >= len {
482 break;
483 }
484 }
485 }
486
487 PsValue::File(file_entity) => {
489 ctx.files.flush_pending_newlines(file_entity);
492
493 let remaining = ctx.files.get_remaining_bytes(file_entity);
499 let (ptr, len) = (remaining.as_ptr(), remaining.len());
500 if len > 0 {
501 let remaining = unsafe { std::slice::from_raw_parts(ptr, len) };
502 if let Some((tok_obj, consumed, is_immediate, auto_exec)) =
503 scan_token_from_bytes(ctx, remaining)?
504 {
505 let newlines = count_newlines(&remaining[..consumed]);
506 ctx.current_source_line += newlines;
507 ctx.files.add_pending_newlines(file_entity, newlines);
508 ctx.files.advance_position(file_entity, consumed);
509 if consumed < remaining.len() {
510 ctx.e_stack.push(obj)?;
511 }
512 if auto_exec {
513 ctx.e_stack.push(tok_obj)?;
514 } else {
515 dispatch_scanned_token(ctx, tok_obj, is_immediate)?;
516 }
517 }
518 } else if ctx.files.is_readable(file_entity) {
519 ctx.pump_proc_sources(file_entity)?;
523 if let Some((token, newlines)) = stream_next_token(&mut ctx.files, file_entity)? {
524 ctx.current_source_line += newlines;
525 ctx.files.add_pending_newlines(file_entity, newlines);
526 let is_immediate = matches!(token, Token::ImmediateName(_));
527 let (tok_obj, auto_exec) = if let Token::BinaryTokenByte(tag) = token {
528 let result =
529 stet_core::binary_token::parse_from_stream(ctx, tag, file_entity)?;
530 match result {
531 stet_core::binary_token::BinaryTokenResult::Single(o) => (o, false),
532 stet_core::binary_token::BinaryTokenResult::Sequence(o) => (o, true),
533 }
534 } else if matches!(token, Token::ProcBegin) {
535 (stream_parse_procedure(ctx, file_entity)?, false)
536 } else {
537 (token_to_object(ctx, token)?, false)
538 };
539 if ctx.files.is_readable(file_entity) {
540 ctx.e_stack.push(obj)?;
541 }
542 if auto_exec {
543 ctx.e_stack.push(tok_obj)?;
544 } else {
545 dispatch_scanned_token(ctx, tok_obj, is_immediate)?;
546 }
547 }
548 }
549 }
550
551 PsValue::Stopped => {
553 ctx.o_stack.push(PsObject::bool(false))?;
554 }
555
556 PsValue::Loop(loop_entity) => {
558 advance_loop(ctx, loop_entity)?;
559 }
560
561 PsValue::HardReturn => {}
563
564 PsValue::DictEnd(expected) => {
566 pop_dict_end(ctx, expected);
567 }
568
569 _ => {
571 ctx.o_stack.push(obj)?;
572 }
573 }
574 Ok(())
575}
576
577fn exec_procedure(
583 ctx: &mut Context,
584 entity: EntityId,
585 start: u32,
586 len: u32,
587) -> Result<(), PsError> {
588 if len == 0 {
589 return Ok(());
590 }
591 ctx.e_stack.push(PsObject {
592 value: PsValue::ExecArray {
593 entity,
594 start,
595 len,
596 pos: 0,
597 },
598 flags: ObjFlags::executable_composite(),
599 })?;
600 Ok(())
601}
602
603fn scan_token_from_bytes(
608 ctx: &mut Context,
609 bytes: &[u8],
610) -> Result<Option<(PsObject, usize, bool, bool)>, PsError> {
611 let mut tokenizer = Tokenizer::new(bytes);
612 match tokenizer.next_token()? {
613 Some(Token::BinaryTokenByte(tag)) => {
614 let pos = tokenizer.position();
615 let (result, consumed) =
616 stet_core::binary_token::parse_from_slice(ctx, tag, &bytes[pos..])?;
617 let total = pos + consumed;
618 match result {
619 stet_core::binary_token::BinaryTokenResult::Single(obj) => {
620 Ok(Some((obj, total, false, false)))
621 }
622 stet_core::binary_token::BinaryTokenResult::Sequence(obj) => {
623 Ok(Some((obj, total, false, true)))
624 }
625 }
626 }
627 Some(token) => {
628 let is_immediate = matches!(token, Token::ImmediateName(_));
629 let eats_whitespace =
631 matches!(token, Token::Int(_) | Token::Real(_) | Token::Name(_, _));
632 let tok_obj = if matches!(token, Token::ProcBegin) {
633 parse_procedure(ctx, &mut tokenizer)?
634 } else {
635 token_to_object(ctx, token)?
636 };
637 let mut consumed = tokenizer.position();
638 if eats_whitespace && consumed < bytes.len() && is_ps_whitespace(bytes[consumed]) {
639 consumed += 1;
640 }
641 Ok(Some((tok_obj, consumed, is_immediate, false)))
642 }
643 None => Ok(None),
644 }
645}
646
647fn is_ps_whitespace(b: u8) -> bool {
649 matches!(b, b'\0' | b'\t' | b'\n' | 0x0C | b'\r' | b' ')
650}
651
652fn dispatch_scanned_token(
657 ctx: &mut Context,
658 tok_obj: PsObject,
659 is_immediate: bool,
660) -> Result<(), PsError> {
661 if matches!(tok_obj.value, PsValue::Name(_)) && tok_obj.flags.is_executable() && !is_immediate {
662 ctx.e_stack.push(tok_obj)?;
663 } else {
664 ctx.o_stack.push(tok_obj)?;
665 }
666 Ok(())
667}
668
669fn stream_parse_procedure(ctx: &mut Context, file_entity: EntityId) -> Result<PsObject, PsError> {
674 stream_parse_procedure_at(ctx, file_entity, 0)
675}
676
677fn stream_parse_procedure_at(
684 ctx: &mut Context,
685 file_entity: EntityId,
686 depth: u32,
687) -> Result<PsObject, PsError> {
688 if depth >= MAX_PROC_DEPTH {
689 return Err(PsError::LimitCheck);
690 }
691 let mut elements = Vec::new();
692
693 loop {
694 ctx.pump_proc_sources(file_entity)?;
695 match stream_next_token(&mut ctx.files, file_entity)? {
696 None => return Err(PsError::SyntaxError), Some((Token::ProcEnd, _)) => break,
698 Some((Token::ProcBegin, _)) => {
699 let nested = stream_parse_procedure_at(ctx, file_entity, depth + 1)?;
700 elements.push(nested);
701 }
702 Some((Token::BinaryTokenByte(tag), _)) => {
703 let result = stet_core::binary_token::parse_from_stream(ctx, tag, file_entity)?;
704 let obj = match result {
705 stet_core::binary_token::BinaryTokenResult::Single(o) => o,
706 stet_core::binary_token::BinaryTokenResult::Sequence(o) => o,
707 };
708 elements.push(obj);
709 }
710 Some((token, _)) => {
711 let obj = token_to_object(ctx, token)?;
712 elements.push(obj);
713 }
714 }
715 }
716
717 let len = elements.len();
718 let save_level = ctx.save_stack.current_level();
719 let global = ctx.vm_alloc_mode;
720 let created = ctx.save_stack.last_save_id();
721 let entity = ctx.arrays.allocate_with(len, save_level, global, created);
722 let dest = ctx.arrays.get_mut(entity, 0, len as u32);
723 dest.copy_from_slice(&elements);
724
725 let mut obj = PsObject::procedure(entity, len as u32);
726 if global {
727 obj.flags = ObjFlags::new(ObjFlags::ACCESS_UNLIMITED, true, true, true);
728 }
729 Ok(obj)
730}
731
732fn count_newlines(bytes: &[u8]) -> u32 {
735 let mut count = 0u32;
736 let mut i = 0;
737 while i < bytes.len() {
738 match bytes[i] {
739 b'\r' => {
740 count += 1;
741 if i + 1 < bytes.len() && bytes[i + 1] == b'\n' {
743 i += 1;
744 }
745 }
746 b'\n' | b'\x0c' => {
747 count += 1;
748 }
749 _ => {}
750 }
751 i += 1;
752 }
753 count
754}
755
756fn advance_loop(ctx: &mut Context, loop_entity: EntityId) -> Result<(), PsError> {
758 use stet_core::context::LoopType;
759
760 let loop_state = ctx.get_loop(loop_entity);
761 let loop_type = match loop_state.loop_type {
762 LoopType::For => 0,
763 LoopType::Repeat => 1,
764 LoopType::Loop => 2,
765 LoopType::Forall => 3,
766 LoopType::PathForall => 4,
767 };
768 let proc_entity = loop_state.proc_entity;
769 let proc_start = loop_state.proc_start;
770 let proc_len = loop_state.proc_len;
771
772 match loop_type {
773 0 => {
774 let counter = ctx.get_loop(loop_entity).counter;
776 let increment = ctx.get_loop(loop_entity).increment;
777 let limit = ctx.get_loop(loop_entity).limit;
778 let use_int = ctx.get_loop(loop_entity).use_int;
779
780 let done = if increment > 0.0 {
781 counter > limit
782 } else {
783 counter < limit
784 };
785
786 if done {
787 return Ok(());
788 }
789
790 if use_int {
792 ctx.o_stack.push(PsObject::int(counter as i32))?;
793 } else {
794 ctx.o_stack.push(PsObject::real(counter))?;
795 }
796
797 let new_counter = counter + increment;
799 ctx.get_loop_mut(loop_entity).counter = new_counter;
800
801 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
803 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
804 }
805 1 => {
806 let counter = ctx.get_loop(loop_entity).counter;
808 if counter <= 0.0 {
809 return Ok(());
810 }
811 ctx.get_loop_mut(loop_entity).counter = counter - 1.0;
812 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
813 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
814 }
815 2 => {
816 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
818 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
819 }
820 3 => {
821 advance_forall(ctx, loop_entity, proc_entity, proc_start, proc_len)?;
823 }
824 4 => {
825 advance_pathforall(ctx, loop_entity)?;
827 }
828 _ => unreachable!(),
829 }
830
831 Ok(())
832}
833
834fn advance_forall(
836 ctx: &mut Context,
837 loop_entity: EntityId,
838 proc_entity: EntityId,
839 proc_start: u32,
840 proc_len: u32,
841) -> Result<(), PsError> {
842 let source = ctx.get_loop(loop_entity).source;
843 let index = ctx.get_loop(loop_entity).index;
844
845 match source.value {
846 PsValue::Array { entity, start, len } | PsValue::PackedArray { entity, start, len } => {
847 if index >= len {
848 return Ok(());
849 }
850 let elem = ctx.arrays.get_element(entity, start + index);
851 ctx.o_stack.push(elem)?;
852 ctx.get_loop_mut(loop_entity).index = index + 1;
853 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
854 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
855 }
856 PsValue::String { entity, start, len } => {
857 if index >= len {
858 return Ok(());
859 }
860 let byte = ctx.strings.get_byte(entity, start + index);
861 ctx.o_stack.push(PsObject::int(byte as i32))?;
862 ctx.get_loop_mut(loop_entity).index = index + 1;
863 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
864 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
865 }
866 PsValue::Dict(dict_entity) => {
867 let keys = ctx.get_loop(loop_entity).dict_keys.as_ref().unwrap();
870 if (index as usize) >= keys.len() {
871 return Ok(());
872 }
873 let key = keys[index as usize].clone();
874 let val = ctx.dicts.get(dict_entity, &key).unwrap_or(PsObject::null());
875
876 let key_obj = dict_key_to_object(ctx, &key);
878 ctx.o_stack.push(key_obj)?;
879 ctx.o_stack.push(val)?;
880
881 ctx.get_loop_mut(loop_entity).index = index + 1;
882 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
883 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
884 }
885 _ => return Err(PsError::TypeCheck),
886 }
887
888 Ok(())
889}
890
891fn advance_pathforall(ctx: &mut Context, loop_entity: EntityId) -> Result<(), PsError> {
893 use stet_core::geometry::PathSegment;
894
895 let index = ctx.get_loop(loop_entity).index as usize;
896
897 let seg_len = ctx
899 .get_loop(loop_entity)
900 .path_segments
901 .as_ref()
902 .map_or(0, |s| s.len());
903 if index >= seg_len {
904 return Ok(());
905 }
906
907 let loop_state = ctx.get_loop(loop_entity);
909 let seg = loop_state.path_segments.as_ref().unwrap()[index].clone();
910 let ictm = loop_state.path_ictm.unwrap();
911 let procs = loop_state.path_procs.unwrap();
912
913 let proc = match seg {
915 PathSegment::MoveTo(dx, dy) => {
916 let (ux, uy) = ictm.transform_point(dx, dy);
917 ctx.o_stack.push(PsObject::real(ux))?;
918 ctx.o_stack.push(PsObject::real(uy))?;
919 procs[0] }
921 PathSegment::LineTo(dx, dy) => {
922 let (ux, uy) = ictm.transform_point(dx, dy);
923 ctx.o_stack.push(PsObject::real(ux))?;
924 ctx.o_stack.push(PsObject::real(uy))?;
925 procs[1] }
927 PathSegment::CurveTo {
928 x1,
929 y1,
930 x2,
931 y2,
932 x3,
933 y3,
934 } => {
935 let (ux1, uy1) = ictm.transform_point(x1, y1);
936 let (ux2, uy2) = ictm.transform_point(x2, y2);
937 let (ux3, uy3) = ictm.transform_point(x3, y3);
938 ctx.o_stack.push(PsObject::real(ux1))?;
939 ctx.o_stack.push(PsObject::real(uy1))?;
940 ctx.o_stack.push(PsObject::real(ux2))?;
941 ctx.o_stack.push(PsObject::real(uy2))?;
942 ctx.o_stack.push(PsObject::real(ux3))?;
943 ctx.o_stack.push(PsObject::real(uy3))?;
944 procs[2] }
946 PathSegment::ClosePath => {
947 procs[3] }
949 };
950
951 ctx.get_loop_mut(loop_entity).index = (index + 1) as u32;
953
954 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
956 let (proc_entity, proc_start, proc_len) = match proc.value {
957 PsValue::Array { entity, start, len } => (entity, start, len),
958 _ => return Err(PsError::TypeCheck),
959 };
960 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
961
962 Ok(())
963}
964
965fn dict_key_to_object(ctx: &mut Context, key: &DictKey) -> PsObject {
967 match key {
968 DictKey::Name(id) => PsObject::name_lit(*id),
969 DictKey::Int(v) => PsObject::int(*v),
970 DictKey::Real(bits) => PsObject::real(f64::from_bits(*bits)),
971 DictKey::Bool(v) => PsObject::bool(*v),
972 DictKey::String(bytes) => {
973 let entity = stet_ops::vm_ops::alloc_string(ctx, bytes);
974 PsObject::string(entity, bytes.len() as u32)
975 }
976 DictKey::Operator(op) => {
977 use stet_core::object::OpCode;
978 PsObject::operator(OpCode(*op))
979 }
980 DictKey::Identity(eid, _start, len) => {
981 PsObject::array(EntityId(*eid), *len)
983 }
984 }
985}
986
987fn dispatch_error(ctx: &mut Context, error: &PsError) -> Result<(), PsError> {
994 if ctx.in_error_handler {
996 use std::io::Write;
997 let _ = writeln!(ctx.stdout, "Error (in handler): {}", error);
998 return Ok(());
999 }
1000
1001 let error_name = error.to_string();
1002 let error_name_id = ctx.names.intern(error_name.as_bytes());
1003 let error_key = DictKey::Name(error_name_id);
1004
1005 if let Some(handler) = ctx.dicts.get(ctx.errordict, &error_key)
1007 && handler.flags.is_executable()
1008 {
1009 ctx.in_error_handler = true;
1010
1011 let cmd_name = ctx.current_operator.unwrap_or(error_name_id);
1016 ctx.current_operator = None;
1017 ctx.o_stack.push(PsObject::name_lit(cmd_name))?;
1018
1019 ctx.e_stack.push(handler)?;
1024
1025 ctx.in_error_handler = false;
1026 return Ok(());
1027 }
1028
1029 use std::io::Write;
1031 let _ = writeln!(ctx.stdout, "Error: {}", error);
1032 Ok(())
1033}
1034
1035fn unwind_to_stopped(ctx: &mut Context) -> Result<(), PsError> {
1039 while let Some(obj) = ctx.e_stack.try_pop() {
1040 match obj.value {
1041 PsValue::Stopped => return Ok(()),
1042 PsValue::DictEnd(expected) => {
1043 pop_dict_end(ctx, expected);
1044 }
1045 _ => {}
1046 }
1047 }
1048 Err(PsError::Stop)
1050}
1051
1052fn unwind_to_loop(ctx: &mut Context) -> Result<(), PsError> {
1054 while let Some(obj) = ctx.e_stack.try_pop() {
1055 match obj.value {
1056 PsValue::Loop(_) => return Ok(()),
1057 PsValue::Stopped => {
1058 ctx.e_stack.push(obj)?;
1060 return Err(PsError::InvalidExit);
1061 }
1062 PsValue::DictEnd(expected) => {
1063 pop_dict_end(ctx, expected);
1064 }
1065 _ => {}
1066 }
1067 }
1068 Err(PsError::InvalidExit)
1069}
1070
1071fn pop_dict_end(ctx: &mut Context, expected: EntityId) {
1075 if ctx.d_stack.last() == Some(&expected) {
1076 ctx.d_stack.pop();
1077 ctx.invalidate_name_cache();
1078 }
1079}
1080
1081pub fn token_to_object(ctx: &mut Context, token: Token) -> Result<PsObject, PsError> {
1083 ctx.token_to_object(token)
1084}
1085
1086pub fn parse_and_exec(ctx: &mut Context, source: &[u8]) -> Result<(), PsError> {
1096 let file_entity = ctx.files.create_string_source(source.to_vec());
1097 ctx.e_stack.push(PsObject {
1098 value: PsValue::File(file_entity),
1099 flags: ObjFlags::executable_composite(),
1100 })?;
1101 eval(ctx)
1102}
1103
1104pub fn parse_and_exec_file(ctx: &mut Context, source: &[u8], path: &str) -> Result<(), PsError> {
1110 let file_entity = ctx.files.create_string_source(source.to_vec());
1111 let canonical = std::path::Path::new(path)
1113 .canonicalize()
1114 .unwrap_or_else(|_| std::path::PathBuf::from(path));
1115 ctx.files
1116 .set_name(file_entity, canonical.to_string_lossy().to_string());
1117 ctx.e_stack.push(PsObject {
1118 value: PsValue::File(file_entity),
1119 flags: ObjFlags::executable_composite(),
1120 })?;
1121 eval(ctx)
1122}
1123
1124fn parse_procedure(ctx: &mut Context, tokenizer: &mut Tokenizer) -> Result<PsObject, PsError> {
1126 parse_procedure_at(ctx, tokenizer, 0)
1127}
1128
1129const MAX_PROC_DEPTH: u32 = 100;
1137
1138fn parse_procedure_at(
1139 ctx: &mut Context,
1140 tokenizer: &mut Tokenizer,
1141 depth: u32,
1142) -> Result<PsObject, PsError> {
1143 if depth >= MAX_PROC_DEPTH {
1144 return Err(PsError::LimitCheck);
1145 }
1146 let mut elements = Vec::new();
1147
1148 loop {
1149 match tokenizer.next_token()? {
1150 Some(Token::ProcEnd) => break,
1151 Some(Token::ProcBegin) => {
1152 let nested = parse_procedure_at(ctx, tokenizer, depth + 1)?;
1153 elements.push(nested);
1154 }
1155 Some(Token::BinaryTokenByte(tag)) => {
1156 let pos = tokenizer.position();
1157 let input_bytes = tokenizer.remaining_from(pos);
1160 let (result, consumed) =
1161 stet_core::binary_token::parse_from_slice(ctx, tag, input_bytes)?;
1162 tokenizer.advance(consumed);
1163 let obj = match result {
1164 stet_core::binary_token::BinaryTokenResult::Single(o) => o,
1165 stet_core::binary_token::BinaryTokenResult::Sequence(o) => o,
1166 };
1167 elements.push(obj);
1168 }
1169 Some(token) => {
1170 let obj = token_to_object(ctx, token)?;
1171 elements.push(obj);
1172 }
1173 None => return Err(PsError::SyntaxError), }
1175 }
1176
1177 let len = elements.len();
1178 let save_level = ctx.save_stack.current_level();
1179 let global = ctx.vm_alloc_mode;
1180 let created = ctx.save_stack.last_save_id();
1181 let entity = ctx.arrays.allocate_with(len, save_level, global, created);
1182 let dest = ctx.arrays.get_mut(entity, 0, len as u32);
1183 dest.copy_from_slice(&elements);
1184
1185 let mut obj = PsObject::procedure(entity, len as u32);
1186 if global {
1187 obj.flags = ObjFlags::new(ObjFlags::ACCESS_UNLIMITED, true, true, true);
1188 }
1189 Ok(obj)
1190}
1191
1192#[cfg(test)]
1193mod tests {
1194 use super::*;
1195 use std::io::Write;
1196
1197 #[test]
1198 fn test_parse_procedure() {
1199 let mut ctx = Context::new();
1200 let mut tokenizer = Tokenizer::new(b"1 2 add }");
1201 let proc_obj = parse_procedure(&mut ctx, &mut tokenizer).unwrap();
1202 assert!(proc_obj.flags.is_executable());
1203 assert!(proc_obj.is_array_type());
1204 match proc_obj.value {
1205 PsValue::Array { len, .. } => assert_eq!(len, 3),
1206 _ => panic!("Expected array"),
1207 }
1208 }
1209
1210 #[test]
1211 fn test_nested_procedure() {
1212 let mut ctx = Context::new();
1213 let mut tokenizer = Tokenizer::new(b"{ 1 add } exec }");
1214 let proc_obj = parse_procedure(&mut ctx, &mut tokenizer).unwrap();
1215 match proc_obj.value {
1216 PsValue::Array { len, .. } => assert_eq!(len, 2), _ => panic!("Expected array"),
1218 }
1219 }
1220
1221 fn setup_ctx() -> (Context, std::sync::Arc<std::sync::Mutex<Vec<u8>>>) {
1224 let buf = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
1225 let writer = buf.clone();
1226
1227 struct ArcWriter(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);
1228 impl Write for ArcWriter {
1229 fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
1230 self.0.lock().unwrap().extend_from_slice(data);
1231 Ok(data.len())
1232 }
1233 fn flush(&mut self) -> std::io::Result<()> {
1234 Ok(())
1235 }
1236 }
1237
1238 let mut ctx = Context::new_with_output(Box::new(ArcWriter(writer)));
1239 stet_ops::build_system_dict(&mut ctx);
1240 (ctx, buf)
1241 }
1242
1243 fn run_ps(source: &[u8]) -> String {
1244 let (mut ctx, buf) = setup_ctx();
1245 parse_and_exec(&mut ctx, source).ok();
1246 String::from_utf8(buf.lock().unwrap().clone()).unwrap()
1247 }
1248
1249 #[test]
1252 fn test_save_restore_reverts_def() {
1253 let (mut ctx, buf) = setup_ctx();
1254 let result = parse_and_exec(&mut ctx, b"save /s exch def /x 1 def s restore");
1256 assert!(result.is_ok());
1257 let output = String::from_utf8(buf.lock().unwrap().clone()).unwrap();
1259 let x_id = ctx.names.intern(b"x");
1261 let key = DictKey::Name(x_id);
1262 assert!(
1263 ctx.dict_load(&key).is_none(),
1264 "x should be undefined after restore"
1265 );
1266 drop(output);
1267 }
1268
1269 #[test]
1272 fn test_save_restore_reverts_array() {
1273 let output = run_ps(b"[1 2 3] /a exch def save /s exch def a 1 99 put s restore a 1 get =");
1274 assert_eq!(output.trim(), "2");
1275 }
1276
1277 #[test]
1279 fn test_file_round_trip() {
1280 let path = std::env::temp_dir()
1285 .join("stet_phase2_file_test.txt")
1286 .to_string_lossy()
1287 .replace('\\', "/");
1288 let source = format!(
1289 "({}) (w) file /f exch def f (hello world) writestring f closefile \
1290 ({}) (r) file /f exch def f 11 string readstring pop print f closefile",
1291 path, path
1292 );
1293 let output = run_ps(source.as_bytes());
1294 assert_eq!(output, "hello world");
1295 std::fs::remove_file(&path).ok();
1296 }
1297
1298 #[test]
1300 fn test_stopped_catches_error() {
1301 let output = run_ps(b"{ 1 0 div } stopped { (caught\n) print } if");
1302 assert_eq!(output.trim(), "caught");
1303 }
1304
1305 #[test]
1307 fn test_setglobal_gcheck() {
1308 let output = run_ps(b"true setglobal 3 array gcheck =");
1309 assert_eq!(output.trim(), "true");
1310 }
1311
1312 #[test]
1314 fn test_vmstatus() {
1315 let output = run_ps(b"vmstatus = = =");
1316 let lines: Vec<&str> = output.trim().lines().collect();
1317 assert_eq!(lines.len(), 3, "vmstatus should push 3 values");
1318 for line in &lines {
1320 assert!(
1321 line.trim().parse::<i32>().is_ok(),
1322 "Expected integer: {}",
1323 line
1324 );
1325 }
1326 }
1327
1328 #[test]
1330 fn test_error_dispatch_stop() {
1331 let output = run_ps(b"{ 1 0 div } stopped { (error caught\n) print } if");
1332 assert!(output.contains("error caught"));
1333 }
1334
1335 #[test]
1337 fn test_nested_save_restore() {
1338 let output = run_ps(
1339 b"/x 10 def \
1340 save /s1 exch def \
1341 /x 20 def \
1342 save /s2 exch def \
1343 /x 30 def \
1344 x = \
1345 s2 restore \
1346 x = \
1347 s1 restore \
1348 x =",
1349 );
1350 let lines: Vec<&str> = output.trim().lines().collect();
1351 assert_eq!(lines, vec!["30", "20", "10"]);
1352 }
1353
1354 #[test]
1358 fn test_global_survives_restore() {
1359 let output = run_ps(
1360 b"true setglobal \
1361 3 array /ga exch def \
1362 false setglobal \
1363 save /s exch def \
1364 ga 0 42 put \
1365 s restore \
1366 ga 0 get =",
1367 );
1368 assert_eq!(output.trim(), "42");
1369 }
1370}