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 if obj.flags.is_deferred() {
28 obj.flags.clear_deferred();
29 if let Err(e) = ctx.o_stack.push(obj) {
30 dispatch_error(ctx, &e)?;
31 }
32 continue;
33 }
34
35 if obj.flags.is_literal()
37 && !matches!(
38 obj.value,
39 PsValue::Stopped
40 | PsValue::Loop(_)
41 | PsValue::HardReturn
42 | PsValue::DictEnd(_)
43 | PsValue::ExecArray { .. }
44 )
45 {
46 if let Err(e) = ctx.o_stack.push(obj) {
47 dispatch_error(ctx, &e)?;
48 }
49 continue;
50 }
51
52 match eval_one(ctx, obj) {
53 Ok(()) => {}
54 Err(PsError::Quit) => return Ok(()),
55 Err(PsError::Stop) => {
56 if unwind_to_stopped(ctx).is_ok() {
57 if let Err(e) = ctx.o_stack.push(PsObject::bool(true)) {
58 dispatch_error(ctx, &e)?;
59 }
60 } else {
61 return Err(PsError::Stop);
62 }
63 }
64 Err(PsError::Exit) => {
65 if let Err(e) = unwind_to_loop(ctx) {
66 dispatch_error(ctx, &e)?;
67 }
68 }
69 Err(e) => {
70 dispatch_error(ctx, &e)?;
71 }
72 }
73 }
74 Ok(())
75}
76
77pub fn exec_sync(ctx: &mut Context, proc_obj: PsObject) -> Result<(), PsError> {
84 let base_depth = ctx.e_stack.len();
85 ctx.e_stack.push(proc_obj)?;
86
87 while ctx.e_stack.len() > base_depth {
88 if let Some(ref flag) = ctx.interrupt_flag
89 && flag.load(std::sync::atomic::Ordering::Relaxed)
90 {
91 return Err(PsError::Quit);
92 }
93 let Some(mut obj) = ctx.e_stack.try_pop() else {
94 break;
95 };
96
97 if obj.flags.is_deferred() {
98 obj.flags.clear_deferred();
99 ctx.o_stack.push(obj)?;
100 continue;
101 }
102
103 if obj.flags.is_literal()
104 && !matches!(
105 obj.value,
106 PsValue::Stopped
107 | PsValue::Loop(_)
108 | PsValue::HardReturn
109 | PsValue::DictEnd(_)
110 | PsValue::ExecArray { .. }
111 )
112 {
113 ctx.o_stack.push(obj)?;
114 continue;
115 }
116
117 match eval_one(ctx, obj) {
118 Ok(()) => {}
119 Err(PsError::Quit) => return Ok(()),
120 Err(PsError::Stop) => {
121 if unwind_to_stopped_bounded(ctx, base_depth).is_ok() {
123 ctx.o_stack.push(PsObject::bool(true))?;
124 } else {
125 return Err(PsError::Stop);
126 }
127 }
128 Err(PsError::Exit) => {
129 if unwind_to_loop_bounded(ctx, base_depth).is_err() {
130 return Err(PsError::Exit);
131 }
132 }
133 Err(e) => {
134 dispatch_error(ctx, &e)?;
135 }
136 }
137 }
138
139 Ok(())
140}
141
142fn unwind_to_stopped_bounded(ctx: &mut Context, min_depth: usize) -> Result<(), PsError> {
144 while ctx.e_stack.len() > min_depth {
145 if let Some(obj) = ctx.e_stack.try_pop() {
146 match obj.value {
147 PsValue::Stopped => return Ok(()),
148 PsValue::DictEnd(expected) => {
149 pop_dict_end(ctx, expected);
150 }
151 _ => {}
152 }
153 }
154 }
155 Err(PsError::Stop)
156}
157
158fn unwind_to_loop_bounded(ctx: &mut Context, min_depth: usize) -> Result<(), PsError> {
160 while ctx.e_stack.len() > min_depth {
161 if let Some(obj) = ctx.e_stack.try_pop() {
162 match obj.value {
163 PsValue::Loop(_) => return Ok(()),
164 PsValue::Stopped => {
165 ctx.e_stack.push(obj)?;
166 return Err(PsError::InvalidExit);
167 }
168 PsValue::DictEnd(expected) => {
169 pop_dict_end(ctx, expected);
170 }
171 _ => {}
172 }
173 }
174 }
175 Err(PsError::InvalidExit)
176}
177
178fn eval_one(ctx: &mut Context, obj: PsObject) -> Result<(), PsError> {
182 match obj.value {
183 PsValue::Int(_)
185 | PsValue::Real(_)
186 | PsValue::Bool(_)
187 | PsValue::Null
188 | PsValue::Mark
189 | PsValue::DictMark => {
190 ctx.o_stack.push(obj)?;
191 }
192
193 PsValue::Operator(opcode) => {
195 let func = ctx.operators[opcode.0 as usize].func;
196 if let Err(e) = func(ctx) {
197 ctx.current_operator = Some(ctx.operators[opcode.0 as usize].name);
198 return Err(e);
199 }
200 }
201
202 PsValue::Name(name_id) => {
204 let key = DictKey::Name(name_id);
205 match ctx.dict_load(&key) {
206 Some(val) => {
207 if val.flags.is_executable() {
208 ctx.e_stack.push(val)?;
209 } else {
210 ctx.o_stack.push(val)?;
211 }
212 }
213 None => {
214 ctx.current_operator = Some(name_id);
215 return Err(PsError::Undefined);
216 }
217 }
218 }
219
220 PsValue::Array { entity, start, len } => {
222 exec_procedure(ctx, entity, start, len)?;
223 }
224
225 PsValue::String { entity, start, len } => {
230 let bytes = ctx.strings.get(entity, start, len);
231 let (ptr, byte_len) = (bytes.as_ptr(), bytes.len());
232 let bytes = unsafe { std::slice::from_raw_parts(ptr, byte_len) };
233 if let Some((tok_obj, consumed, is_immediate, auto_exec)) =
234 scan_token_from_bytes(ctx, bytes)?
235 {
236 let newlines = count_newlines(&bytes[..consumed]);
237 ctx.current_source_line += newlines;
238
239 let remaining = len - consumed as u32;
241 if remaining > 0 {
242 ctx.e_stack.push(PsObject {
243 value: PsValue::String {
244 entity,
245 start: start + consumed as u32,
246 len: remaining,
247 },
248 flags: ObjFlags::executable_composite(),
249 })?;
250 }
251
252 if auto_exec {
253 ctx.e_stack.push(tok_obj)?;
254 } else {
255 dispatch_scanned_token(ctx, tok_obj, is_immediate)?;
256 }
257 }
258 }
259
260 PsValue::ExecArray {
268 entity,
269 start,
270 len,
271 pos,
272 } => {
273 let mut cur_pos = pos;
274 let ea_flags = obj.flags;
275
276 macro_rules! dispatch_op {
283 ($opcode:expr) => {{
284 let e_depth = ctx.e_stack.len();
285 let func = ctx.operators[$opcode.0 as usize].func;
286 let result = func(ctx);
287 match result {
288 Ok(()) => {
289 if ctx.e_stack.len() > e_depth {
290 if cur_pos < len {
293 ctx.e_stack.insert_at(
294 e_depth,
295 PsObject {
296 value: PsValue::ExecArray {
297 entity,
298 start,
299 len,
300 pos: cur_pos,
301 },
302 flags: ea_flags,
303 },
304 )?;
305 }
306 true } else {
308 false }
310 }
311 Err(e) => {
312 ctx.current_operator = Some(ctx.operators[$opcode.0 as usize].name);
314 if cur_pos < len {
315 ctx.e_stack.push(PsObject {
316 value: PsValue::ExecArray {
317 entity,
318 start,
319 len,
320 pos: cur_pos,
321 },
322 flags: ea_flags,
323 })?;
324 }
325 return Err(e);
326 }
327 }
328 }};
329 }
330
331 'ea_loop: loop {
332 let elem = ctx.arrays.get_element(entity, start + cur_pos);
333 cur_pos += 1;
334
335 match elem.value {
336 PsValue::Operator(opcode) => {
337 if dispatch_op!(opcode) {
338 break 'ea_loop;
339 }
340 }
341
342 PsValue::Name(name_id) if elem.flags.is_executable() => {
343 let idx = name_id.0 as usize;
346 let val = if idx < ctx.name_resolve_cache.len() {
347 let (ver, cached) = ctx.name_resolve_cache[idx];
348 if ver == ctx.dict_version {
349 cached
350 } else {
351 match ctx.dict_load(&DictKey::Name(name_id)) {
352 Some(v) => v,
353 None => {
354 ctx.current_operator = Some(name_id);
355 if cur_pos < len {
356 ctx.e_stack.push(PsObject {
357 value: PsValue::ExecArray {
358 entity,
359 start,
360 len,
361 pos: cur_pos,
362 },
363 flags: ea_flags,
364 })?;
365 }
366 return Err(PsError::Undefined);
367 }
368 }
369 }
370 } else {
371 match ctx.dict_load(&DictKey::Name(name_id)) {
372 Some(v) => v,
373 None => {
374 ctx.current_operator = Some(name_id);
375 if cur_pos < len {
376 ctx.e_stack.push(PsObject {
377 value: PsValue::ExecArray {
378 entity,
379 start,
380 len,
381 pos: cur_pos,
382 },
383 flags: ea_flags,
384 })?;
385 }
386 return Err(PsError::Undefined);
387 }
388 }
389 };
390
391 match val.value {
392 PsValue::Operator(opcode) => {
393 if dispatch_op!(opcode) {
394 break 'ea_loop;
395 }
396 }
397 _ => {
398 if cur_pos < len {
400 ctx.e_stack.push(PsObject {
401 value: PsValue::ExecArray {
402 entity,
403 start,
404 len,
405 pos: cur_pos,
406 },
407 flags: ea_flags,
408 })?;
409 }
410 if val.flags.is_executable() {
411 ctx.e_stack.push(val)?;
412 } else {
413 ctx.o_stack.push(val)?;
414 }
415 break 'ea_loop;
416 }
417 }
418 }
419
420 _ => {
421 if elem.is_array_type() && elem.flags.is_executable() {
422 ctx.o_stack.push(elem)?;
424 } else if matches!(
425 elem.value,
426 PsValue::Int(_)
427 | PsValue::Real(_)
428 | PsValue::Bool(_)
429 | PsValue::Null
430 | PsValue::Mark
431 | PsValue::DictMark
432 ) || elem.flags.is_literal()
433 {
434 ctx.o_stack.push(elem)?;
436 } else {
437 if cur_pos < len {
439 ctx.e_stack.push(PsObject {
440 value: PsValue::ExecArray {
441 entity,
442 start,
443 len,
444 pos: cur_pos,
445 },
446 flags: ea_flags,
447 })?;
448 }
449 ctx.e_stack.push(elem)?;
450 break 'ea_loop;
451 }
452 }
453 }
454
455 if cur_pos >= len {
456 break;
457 }
458 }
459 }
460
461 PsValue::File(file_entity) => {
463 ctx.files.flush_pending_newlines(file_entity);
466
467 let remaining = ctx.files.get_remaining_bytes(file_entity);
473 let (ptr, len) = (remaining.as_ptr(), remaining.len());
474 if len > 0 {
475 let remaining = unsafe { std::slice::from_raw_parts(ptr, len) };
476 if let Some((tok_obj, consumed, is_immediate, auto_exec)) =
477 scan_token_from_bytes(ctx, remaining)?
478 {
479 let newlines = count_newlines(&remaining[..consumed]);
480 ctx.current_source_line += newlines;
481 ctx.files.add_pending_newlines(file_entity, newlines);
482 ctx.files.advance_position(file_entity, consumed);
483 if consumed < remaining.len() {
484 ctx.e_stack.push(obj)?;
485 }
486 if auto_exec {
487 ctx.e_stack.push(tok_obj)?;
488 } else {
489 dispatch_scanned_token(ctx, tok_obj, is_immediate)?;
490 }
491 }
492 } else if ctx.files.is_readable(file_entity) {
493 if let Some((token, newlines)) = stream_next_token(&mut ctx.files, file_entity)? {
495 ctx.current_source_line += newlines;
496 ctx.files.add_pending_newlines(file_entity, newlines);
497 let is_immediate = matches!(token, Token::ImmediateName(_));
498 let (tok_obj, auto_exec) = if let Token::BinaryTokenByte(tag) = token {
499 let result =
500 stet_core::binary_token::parse_from_stream(ctx, tag, file_entity)?;
501 match result {
502 stet_core::binary_token::BinaryTokenResult::Single(o) => (o, false),
503 stet_core::binary_token::BinaryTokenResult::Sequence(o) => (o, true),
504 }
505 } else if matches!(token, Token::ProcBegin) {
506 (stream_parse_procedure(ctx, file_entity)?, false)
507 } else {
508 (token_to_object(ctx, token)?, false)
509 };
510 if ctx.files.is_readable(file_entity) {
511 ctx.e_stack.push(obj)?;
512 }
513 if auto_exec {
514 ctx.e_stack.push(tok_obj)?;
515 } else {
516 dispatch_scanned_token(ctx, tok_obj, is_immediate)?;
517 }
518 }
519 }
520 }
521
522 PsValue::Stopped => {
524 ctx.o_stack.push(PsObject::bool(false))?;
525 }
526
527 PsValue::Loop(loop_entity) => {
529 advance_loop(ctx, loop_entity)?;
530 }
531
532 PsValue::HardReturn => {}
534
535 PsValue::DictEnd(expected) => {
537 pop_dict_end(ctx, expected);
538 }
539
540 _ => {
542 ctx.o_stack.push(obj)?;
543 }
544 }
545 Ok(())
546}
547
548fn exec_procedure(
554 ctx: &mut Context,
555 entity: EntityId,
556 start: u32,
557 len: u32,
558) -> Result<(), PsError> {
559 if len == 0 {
560 return Ok(());
561 }
562 ctx.e_stack.push(PsObject {
563 value: PsValue::ExecArray {
564 entity,
565 start,
566 len,
567 pos: 0,
568 },
569 flags: ObjFlags::executable_composite(),
570 })?;
571 Ok(())
572}
573
574fn scan_token_from_bytes(
579 ctx: &mut Context,
580 bytes: &[u8],
581) -> Result<Option<(PsObject, usize, bool, bool)>, PsError> {
582 let mut tokenizer = Tokenizer::new(bytes);
583 match tokenizer.next_token()? {
584 Some(Token::BinaryTokenByte(tag)) => {
585 let pos = tokenizer.position();
586 let (result, consumed) =
587 stet_core::binary_token::parse_from_slice(ctx, tag, &bytes[pos..])?;
588 let total = pos + consumed;
589 match result {
590 stet_core::binary_token::BinaryTokenResult::Single(obj) => {
591 Ok(Some((obj, total, false, false)))
592 }
593 stet_core::binary_token::BinaryTokenResult::Sequence(obj) => {
594 Ok(Some((obj, total, false, true)))
595 }
596 }
597 }
598 Some(token) => {
599 let is_immediate = matches!(token, Token::ImmediateName(_));
600 let eats_whitespace =
602 matches!(token, Token::Int(_) | Token::Real(_) | Token::Name(_, _));
603 let tok_obj = if matches!(token, Token::ProcBegin) {
604 parse_procedure(ctx, &mut tokenizer)?
605 } else {
606 token_to_object(ctx, token)?
607 };
608 let mut consumed = tokenizer.position();
609 if eats_whitespace && consumed < bytes.len() && is_ps_whitespace(bytes[consumed]) {
610 consumed += 1;
611 }
612 Ok(Some((tok_obj, consumed, is_immediate, false)))
613 }
614 None => Ok(None),
615 }
616}
617
618fn is_ps_whitespace(b: u8) -> bool {
620 matches!(b, b'\0' | b'\t' | b'\n' | 0x0C | b'\r' | b' ')
621}
622
623fn dispatch_scanned_token(
628 ctx: &mut Context,
629 tok_obj: PsObject,
630 is_immediate: bool,
631) -> Result<(), PsError> {
632 if matches!(tok_obj.value, PsValue::Name(_)) && tok_obj.flags.is_executable() && !is_immediate {
633 ctx.e_stack.push(tok_obj)?;
634 } else {
635 ctx.o_stack.push(tok_obj)?;
636 }
637 Ok(())
638}
639
640fn stream_parse_procedure(ctx: &mut Context, file_entity: EntityId) -> Result<PsObject, PsError> {
645 let mut elements = Vec::new();
646
647 loop {
648 match stream_next_token(&mut ctx.files, file_entity)? {
649 None => return Err(PsError::SyntaxError), Some((Token::ProcEnd, _)) => break,
651 Some((Token::ProcBegin, _)) => {
652 let nested = stream_parse_procedure(ctx, file_entity)?;
653 elements.push(nested);
654 }
655 Some((Token::BinaryTokenByte(tag), _)) => {
656 let result = stet_core::binary_token::parse_from_stream(ctx, tag, file_entity)?;
657 let obj = match result {
658 stet_core::binary_token::BinaryTokenResult::Single(o) => o,
659 stet_core::binary_token::BinaryTokenResult::Sequence(o) => o,
660 };
661 elements.push(obj);
662 }
663 Some((token, _)) => {
664 let obj = token_to_object(ctx, token)?;
665 elements.push(obj);
666 }
667 }
668 }
669
670 let len = elements.len();
671 let save_level = ctx.save_stack.current_level();
672 let global = ctx.vm_alloc_mode;
673 let created = ctx.save_stack.last_save_id();
674 let entity = ctx.arrays.allocate_with(len, save_level, global, created);
675 let dest = ctx.arrays.get_mut(entity, 0, len as u32);
676 dest.copy_from_slice(&elements);
677
678 let mut obj = PsObject::procedure(entity, len as u32);
679 if global {
680 obj.flags = ObjFlags::new(ObjFlags::ACCESS_UNLIMITED, true, true, true);
681 }
682 Ok(obj)
683}
684
685fn count_newlines(bytes: &[u8]) -> u32 {
688 let mut count = 0u32;
689 let mut i = 0;
690 while i < bytes.len() {
691 match bytes[i] {
692 b'\r' => {
693 count += 1;
694 if i + 1 < bytes.len() && bytes[i + 1] == b'\n' {
696 i += 1;
697 }
698 }
699 b'\n' | b'\x0c' => {
700 count += 1;
701 }
702 _ => {}
703 }
704 i += 1;
705 }
706 count
707}
708
709fn advance_loop(ctx: &mut Context, loop_entity: EntityId) -> Result<(), PsError> {
711 use stet_core::context::LoopType;
712
713 let loop_state = ctx.get_loop(loop_entity);
714 let loop_type = match loop_state.loop_type {
715 LoopType::For => 0,
716 LoopType::Repeat => 1,
717 LoopType::Loop => 2,
718 LoopType::Forall => 3,
719 LoopType::PathForall => 4,
720 };
721 let proc_entity = loop_state.proc_entity;
722 let proc_start = loop_state.proc_start;
723 let proc_len = loop_state.proc_len;
724
725 match loop_type {
726 0 => {
727 let counter = ctx.get_loop(loop_entity).counter;
729 let increment = ctx.get_loop(loop_entity).increment;
730 let limit = ctx.get_loop(loop_entity).limit;
731 let use_int = ctx.get_loop(loop_entity).use_int;
732
733 let done = if increment > 0.0 {
734 counter > limit
735 } else {
736 counter < limit
737 };
738
739 if done {
740 return Ok(());
741 }
742
743 if use_int {
745 ctx.o_stack.push(PsObject::int(counter as i32))?;
746 } else {
747 ctx.o_stack.push(PsObject::real(counter))?;
748 }
749
750 let new_counter = counter + increment;
752 ctx.get_loop_mut(loop_entity).counter = new_counter;
753
754 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
756 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
757 }
758 1 => {
759 let counter = ctx.get_loop(loop_entity).counter;
761 if counter <= 0.0 {
762 return Ok(());
763 }
764 ctx.get_loop_mut(loop_entity).counter = counter - 1.0;
765 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
766 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
767 }
768 2 => {
769 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
771 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
772 }
773 3 => {
774 advance_forall(ctx, loop_entity, proc_entity, proc_start, proc_len)?;
776 }
777 4 => {
778 advance_pathforall(ctx, loop_entity)?;
780 }
781 _ => unreachable!(),
782 }
783
784 Ok(())
785}
786
787fn advance_forall(
789 ctx: &mut Context,
790 loop_entity: EntityId,
791 proc_entity: EntityId,
792 proc_start: u32,
793 proc_len: u32,
794) -> Result<(), PsError> {
795 let source = ctx.get_loop(loop_entity).source;
796 let index = ctx.get_loop(loop_entity).index;
797
798 match source.value {
799 PsValue::Array { entity, start, len } | PsValue::PackedArray { entity, start, len } => {
800 if index >= len {
801 return Ok(());
802 }
803 let elem = ctx.arrays.get_element(entity, start + index);
804 ctx.o_stack.push(elem)?;
805 ctx.get_loop_mut(loop_entity).index = index + 1;
806 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
807 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
808 }
809 PsValue::String { entity, start, len } => {
810 if index >= len {
811 return Ok(());
812 }
813 let byte = ctx.strings.get_byte(entity, start + index);
814 ctx.o_stack.push(PsObject::int(byte as i32))?;
815 ctx.get_loop_mut(loop_entity).index = index + 1;
816 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
817 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
818 }
819 PsValue::Dict(dict_entity) => {
820 let keys = ctx.get_loop(loop_entity).dict_keys.as_ref().unwrap();
823 if (index as usize) >= keys.len() {
824 return Ok(());
825 }
826 let key = keys[index as usize].clone();
827 let val = ctx.dicts.get(dict_entity, &key).unwrap_or(PsObject::null());
828
829 let key_obj = dict_key_to_object(ctx, &key);
831 ctx.o_stack.push(key_obj)?;
832 ctx.o_stack.push(val)?;
833
834 ctx.get_loop_mut(loop_entity).index = index + 1;
835 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
836 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
837 }
838 _ => return Err(PsError::TypeCheck),
839 }
840
841 Ok(())
842}
843
844fn advance_pathforall(ctx: &mut Context, loop_entity: EntityId) -> Result<(), PsError> {
846 use stet_core::geometry::PathSegment;
847
848 let index = ctx.get_loop(loop_entity).index as usize;
849
850 let seg_len = ctx
852 .get_loop(loop_entity)
853 .path_segments
854 .as_ref()
855 .map_or(0, |s| s.len());
856 if index >= seg_len {
857 return Ok(());
858 }
859
860 let loop_state = ctx.get_loop(loop_entity);
862 let seg = loop_state.path_segments.as_ref().unwrap()[index].clone();
863 let ictm = loop_state.path_ictm.unwrap();
864 let procs = loop_state.path_procs.unwrap();
865
866 let proc = match seg {
868 PathSegment::MoveTo(dx, dy) => {
869 let (ux, uy) = ictm.transform_point(dx, dy);
870 ctx.o_stack.push(PsObject::real(ux))?;
871 ctx.o_stack.push(PsObject::real(uy))?;
872 procs[0] }
874 PathSegment::LineTo(dx, dy) => {
875 let (ux, uy) = ictm.transform_point(dx, dy);
876 ctx.o_stack.push(PsObject::real(ux))?;
877 ctx.o_stack.push(PsObject::real(uy))?;
878 procs[1] }
880 PathSegment::CurveTo {
881 x1,
882 y1,
883 x2,
884 y2,
885 x3,
886 y3,
887 } => {
888 let (ux1, uy1) = ictm.transform_point(x1, y1);
889 let (ux2, uy2) = ictm.transform_point(x2, y2);
890 let (ux3, uy3) = ictm.transform_point(x3, y3);
891 ctx.o_stack.push(PsObject::real(ux1))?;
892 ctx.o_stack.push(PsObject::real(uy1))?;
893 ctx.o_stack.push(PsObject::real(ux2))?;
894 ctx.o_stack.push(PsObject::real(uy2))?;
895 ctx.o_stack.push(PsObject::real(ux3))?;
896 ctx.o_stack.push(PsObject::real(uy3))?;
897 procs[2] }
899 PathSegment::ClosePath => {
900 procs[3] }
902 };
903
904 ctx.get_loop_mut(loop_entity).index = (index + 1) as u32;
906
907 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
909 let (proc_entity, proc_start, proc_len) = match proc.value {
910 PsValue::Array { entity, start, len } => (entity, start, len),
911 _ => return Err(PsError::TypeCheck),
912 };
913 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
914
915 Ok(())
916}
917
918fn dict_key_to_object(ctx: &mut Context, key: &DictKey) -> PsObject {
920 match key {
921 DictKey::Name(id) => PsObject::name_lit(*id),
922 DictKey::Int(v) => PsObject::int(*v),
923 DictKey::Real(bits) => PsObject::real(f64::from_bits(*bits)),
924 DictKey::Bool(v) => PsObject::bool(*v),
925 DictKey::String(bytes) => {
926 let entity = ctx.strings.allocate_from(bytes);
927 PsObject::string(entity, bytes.len() as u32)
928 }
929 DictKey::Operator(op) => {
930 use stet_core::object::OpCode;
931 PsObject::operator(OpCode(*op))
932 }
933 DictKey::Identity(eid, _start, len) => {
934 PsObject::array(EntityId(*eid), *len)
936 }
937 }
938}
939
940fn dispatch_error(ctx: &mut Context, error: &PsError) -> Result<(), PsError> {
947 if ctx.in_error_handler {
949 use std::io::Write;
950 let _ = writeln!(ctx.stdout, "Error (in handler): {}", error);
951 return Ok(());
952 }
953
954 let error_name = error.to_string();
955 let error_name_id = ctx.names.intern(error_name.as_bytes());
956 let error_key = DictKey::Name(error_name_id);
957
958 if let Some(handler) = ctx.dicts.get(ctx.errordict, &error_key)
960 && handler.flags.is_executable()
961 {
962 ctx.in_error_handler = true;
963
964 let cmd_name = ctx.current_operator.unwrap_or(error_name_id);
969 ctx.current_operator = None;
970 ctx.o_stack.push(PsObject::name_lit(cmd_name))?;
971
972 ctx.e_stack.push(handler)?;
977
978 ctx.in_error_handler = false;
979 return Ok(());
980 }
981
982 use std::io::Write;
984 let _ = writeln!(ctx.stdout, "Error: {}", error);
985 Ok(())
986}
987
988fn unwind_to_stopped(ctx: &mut Context) -> Result<(), PsError> {
992 while let Some(obj) = ctx.e_stack.try_pop() {
993 match obj.value {
994 PsValue::Stopped => return Ok(()),
995 PsValue::DictEnd(expected) => {
996 pop_dict_end(ctx, expected);
997 }
998 _ => {}
999 }
1000 }
1001 Err(PsError::Stop)
1003}
1004
1005fn unwind_to_loop(ctx: &mut Context) -> Result<(), PsError> {
1007 while let Some(obj) = ctx.e_stack.try_pop() {
1008 match obj.value {
1009 PsValue::Loop(_) => return Ok(()),
1010 PsValue::Stopped => {
1011 ctx.e_stack.push(obj)?;
1013 return Err(PsError::InvalidExit);
1014 }
1015 PsValue::DictEnd(expected) => {
1016 pop_dict_end(ctx, expected);
1017 }
1018 _ => {}
1019 }
1020 }
1021 Err(PsError::InvalidExit)
1022}
1023
1024fn pop_dict_end(ctx: &mut Context, expected: EntityId) {
1028 if ctx.d_stack.last() == Some(&expected) {
1029 ctx.d_stack.pop();
1030 ctx.invalidate_name_cache();
1031 }
1032}
1033
1034pub fn token_to_object(ctx: &mut Context, token: Token) -> Result<PsObject, PsError> {
1036 ctx.token_to_object(token)
1037}
1038
1039pub fn parse_and_exec(ctx: &mut Context, source: &[u8]) -> Result<(), PsError> {
1049 let file_entity = ctx.files.create_string_source(source.to_vec());
1050 ctx.e_stack.push(PsObject {
1051 value: PsValue::File(file_entity),
1052 flags: ObjFlags::executable_composite(),
1053 })?;
1054 eval(ctx)
1055}
1056
1057pub fn parse_and_exec_file(ctx: &mut Context, source: &[u8], path: &str) -> Result<(), PsError> {
1063 let file_entity = ctx.files.create_string_source(source.to_vec());
1064 let canonical = std::path::Path::new(path)
1066 .canonicalize()
1067 .unwrap_or_else(|_| std::path::PathBuf::from(path));
1068 ctx.files
1069 .set_name(file_entity, canonical.to_string_lossy().to_string());
1070 ctx.e_stack.push(PsObject {
1071 value: PsValue::File(file_entity),
1072 flags: ObjFlags::executable_composite(),
1073 })?;
1074 eval(ctx)
1075}
1076
1077fn parse_procedure(ctx: &mut Context, tokenizer: &mut Tokenizer) -> Result<PsObject, PsError> {
1079 let mut elements = Vec::new();
1080
1081 loop {
1082 match tokenizer.next_token()? {
1083 Some(Token::ProcEnd) => break,
1084 Some(Token::ProcBegin) => {
1085 let nested = parse_procedure(ctx, tokenizer)?;
1086 elements.push(nested);
1087 }
1088 Some(Token::BinaryTokenByte(tag)) => {
1089 let pos = tokenizer.position();
1090 let input_bytes = tokenizer.remaining_from(pos);
1093 let (result, consumed) =
1094 stet_core::binary_token::parse_from_slice(ctx, tag, input_bytes)?;
1095 tokenizer.advance(consumed);
1096 let obj = match result {
1097 stet_core::binary_token::BinaryTokenResult::Single(o) => o,
1098 stet_core::binary_token::BinaryTokenResult::Sequence(o) => o,
1099 };
1100 elements.push(obj);
1101 }
1102 Some(token) => {
1103 let obj = token_to_object(ctx, token)?;
1104 elements.push(obj);
1105 }
1106 None => return Err(PsError::SyntaxError), }
1108 }
1109
1110 let len = elements.len();
1111 let save_level = ctx.save_stack.current_level();
1112 let global = ctx.vm_alloc_mode;
1113 let created = ctx.save_stack.last_save_id();
1114 let entity = ctx.arrays.allocate_with(len, save_level, global, created);
1115 let dest = ctx.arrays.get_mut(entity, 0, len as u32);
1116 dest.copy_from_slice(&elements);
1117
1118 let mut obj = PsObject::procedure(entity, len as u32);
1119 if global {
1120 obj.flags = ObjFlags::new(ObjFlags::ACCESS_UNLIMITED, true, true, true);
1121 }
1122 Ok(obj)
1123}
1124
1125#[cfg(test)]
1126mod tests {
1127 use super::*;
1128 use std::io::Write;
1129
1130 #[test]
1131 fn test_parse_procedure() {
1132 let mut ctx = Context::new();
1133 let mut tokenizer = Tokenizer::new(b"1 2 add }");
1134 let proc_obj = parse_procedure(&mut ctx, &mut tokenizer).unwrap();
1135 assert!(proc_obj.flags.is_executable());
1136 assert!(proc_obj.is_array_type());
1137 match proc_obj.value {
1138 PsValue::Array { len, .. } => assert_eq!(len, 3),
1139 _ => panic!("Expected array"),
1140 }
1141 }
1142
1143 #[test]
1144 fn test_nested_procedure() {
1145 let mut ctx = Context::new();
1146 let mut tokenizer = Tokenizer::new(b"{ 1 add } exec }");
1147 let proc_obj = parse_procedure(&mut ctx, &mut tokenizer).unwrap();
1148 match proc_obj.value {
1149 PsValue::Array { len, .. } => assert_eq!(len, 2), _ => panic!("Expected array"),
1151 }
1152 }
1153
1154 fn setup_ctx() -> (Context, std::sync::Arc<std::sync::Mutex<Vec<u8>>>) {
1157 let buf = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
1158 let writer = buf.clone();
1159
1160 struct ArcWriter(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);
1161 impl Write for ArcWriter {
1162 fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
1163 self.0.lock().unwrap().extend_from_slice(data);
1164 Ok(data.len())
1165 }
1166 fn flush(&mut self) -> std::io::Result<()> {
1167 Ok(())
1168 }
1169 }
1170
1171 let mut ctx = Context::new_with_output(Box::new(ArcWriter(writer)));
1172 stet_ops::build_system_dict(&mut ctx);
1173 (ctx, buf)
1174 }
1175
1176 fn run_ps(source: &[u8]) -> String {
1177 let (mut ctx, buf) = setup_ctx();
1178 parse_and_exec(&mut ctx, source).ok();
1179 String::from_utf8(buf.lock().unwrap().clone()).unwrap()
1180 }
1181
1182 #[test]
1185 fn test_save_restore_reverts_def() {
1186 let (mut ctx, buf) = setup_ctx();
1187 let result = parse_and_exec(&mut ctx, b"save /s exch def /x 1 def s restore");
1189 assert!(result.is_ok());
1190 let output = String::from_utf8(buf.lock().unwrap().clone()).unwrap();
1192 let x_id = ctx.names.intern(b"x");
1194 let key = DictKey::Name(x_id);
1195 assert!(
1196 ctx.dict_load(&key).is_none(),
1197 "x should be undefined after restore"
1198 );
1199 drop(output);
1200 }
1201
1202 #[test]
1205 fn test_save_restore_reverts_array() {
1206 let output = run_ps(b"[1 2 3] /a exch def save /s exch def a 1 99 put s restore a 1 get =");
1207 assert_eq!(output.trim(), "2");
1208 }
1209
1210 #[test]
1212 fn test_file_round_trip() {
1213 let path = std::env::temp_dir()
1218 .join("stet_phase2_file_test.txt")
1219 .to_string_lossy()
1220 .replace('\\', "/");
1221 let source = format!(
1222 "({}) (w) file /f exch def f (hello world) writestring f closefile \
1223 ({}) (r) file /f exch def f 11 string readstring pop print f closefile",
1224 path, path
1225 );
1226 let output = run_ps(source.as_bytes());
1227 assert_eq!(output, "hello world");
1228 std::fs::remove_file(&path).ok();
1229 }
1230
1231 #[test]
1233 fn test_stopped_catches_error() {
1234 let output = run_ps(b"{ 1 0 div } stopped { (caught\n) print } if");
1235 assert_eq!(output.trim(), "caught");
1236 }
1237
1238 #[test]
1240 fn test_setglobal_gcheck() {
1241 let output = run_ps(b"true setglobal 3 array gcheck =");
1242 assert_eq!(output.trim(), "true");
1243 }
1244
1245 #[test]
1247 fn test_vmstatus() {
1248 let output = run_ps(b"vmstatus = = =");
1249 let lines: Vec<&str> = output.trim().lines().collect();
1250 assert_eq!(lines.len(), 3, "vmstatus should push 3 values");
1251 for line in &lines {
1253 assert!(
1254 line.trim().parse::<i32>().is_ok(),
1255 "Expected integer: {}",
1256 line
1257 );
1258 }
1259 }
1260
1261 #[test]
1263 fn test_error_dispatch_stop() {
1264 let output = run_ps(b"{ 1 0 div } stopped { (error caught\n) print } if");
1265 assert!(output.contains("error caught"));
1266 }
1267
1268 #[test]
1270 fn test_nested_save_restore() {
1271 let output = run_ps(
1272 b"/x 10 def \
1273 save /s1 exch def \
1274 /x 20 def \
1275 save /s2 exch def \
1276 /x 30 def \
1277 x = \
1278 s2 restore \
1279 x = \
1280 s1 restore \
1281 x =",
1282 );
1283 let lines: Vec<&str> = output.trim().lines().collect();
1284 assert_eq!(lines, vec!["30", "20", "10"]);
1285 }
1286
1287 #[test]
1291 fn test_global_survives_restore() {
1292 let output = run_ps(
1293 b"true setglobal \
1294 3 array /ga exch def \
1295 false setglobal \
1296 save /s exch def \
1297 ga 0 42 put \
1298 s restore \
1299 ga 0 get =",
1300 );
1301 assert_eq!(output.trim(), "42");
1302 }
1303}