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 ctx.pump_proc_sources(file_entity)?;
497 if let Some((token, newlines)) = stream_next_token(&mut ctx.files, file_entity)? {
498 ctx.current_source_line += newlines;
499 ctx.files.add_pending_newlines(file_entity, newlines);
500 let is_immediate = matches!(token, Token::ImmediateName(_));
501 let (tok_obj, auto_exec) = if let Token::BinaryTokenByte(tag) = token {
502 let result =
503 stet_core::binary_token::parse_from_stream(ctx, tag, file_entity)?;
504 match result {
505 stet_core::binary_token::BinaryTokenResult::Single(o) => (o, false),
506 stet_core::binary_token::BinaryTokenResult::Sequence(o) => (o, true),
507 }
508 } else if matches!(token, Token::ProcBegin) {
509 (stream_parse_procedure(ctx, file_entity)?, false)
510 } else {
511 (token_to_object(ctx, token)?, false)
512 };
513 if ctx.files.is_readable(file_entity) {
514 ctx.e_stack.push(obj)?;
515 }
516 if auto_exec {
517 ctx.e_stack.push(tok_obj)?;
518 } else {
519 dispatch_scanned_token(ctx, tok_obj, is_immediate)?;
520 }
521 }
522 }
523 }
524
525 PsValue::Stopped => {
527 ctx.o_stack.push(PsObject::bool(false))?;
528 }
529
530 PsValue::Loop(loop_entity) => {
532 advance_loop(ctx, loop_entity)?;
533 }
534
535 PsValue::HardReturn => {}
537
538 PsValue::DictEnd(expected) => {
540 pop_dict_end(ctx, expected);
541 }
542
543 _ => {
545 ctx.o_stack.push(obj)?;
546 }
547 }
548 Ok(())
549}
550
551fn exec_procedure(
557 ctx: &mut Context,
558 entity: EntityId,
559 start: u32,
560 len: u32,
561) -> Result<(), PsError> {
562 if len == 0 {
563 return Ok(());
564 }
565 ctx.e_stack.push(PsObject {
566 value: PsValue::ExecArray {
567 entity,
568 start,
569 len,
570 pos: 0,
571 },
572 flags: ObjFlags::executable_composite(),
573 })?;
574 Ok(())
575}
576
577fn scan_token_from_bytes(
582 ctx: &mut Context,
583 bytes: &[u8],
584) -> Result<Option<(PsObject, usize, bool, bool)>, PsError> {
585 let mut tokenizer = Tokenizer::new(bytes);
586 match tokenizer.next_token()? {
587 Some(Token::BinaryTokenByte(tag)) => {
588 let pos = tokenizer.position();
589 let (result, consumed) =
590 stet_core::binary_token::parse_from_slice(ctx, tag, &bytes[pos..])?;
591 let total = pos + consumed;
592 match result {
593 stet_core::binary_token::BinaryTokenResult::Single(obj) => {
594 Ok(Some((obj, total, false, false)))
595 }
596 stet_core::binary_token::BinaryTokenResult::Sequence(obj) => {
597 Ok(Some((obj, total, false, true)))
598 }
599 }
600 }
601 Some(token) => {
602 let is_immediate = matches!(token, Token::ImmediateName(_));
603 let eats_whitespace =
605 matches!(token, Token::Int(_) | Token::Real(_) | Token::Name(_, _));
606 let tok_obj = if matches!(token, Token::ProcBegin) {
607 parse_procedure(ctx, &mut tokenizer)?
608 } else {
609 token_to_object(ctx, token)?
610 };
611 let mut consumed = tokenizer.position();
612 if eats_whitespace && consumed < bytes.len() && is_ps_whitespace(bytes[consumed]) {
613 consumed += 1;
614 }
615 Ok(Some((tok_obj, consumed, is_immediate, false)))
616 }
617 None => Ok(None),
618 }
619}
620
621fn is_ps_whitespace(b: u8) -> bool {
623 matches!(b, b'\0' | b'\t' | b'\n' | 0x0C | b'\r' | b' ')
624}
625
626fn dispatch_scanned_token(
631 ctx: &mut Context,
632 tok_obj: PsObject,
633 is_immediate: bool,
634) -> Result<(), PsError> {
635 if matches!(tok_obj.value, PsValue::Name(_)) && tok_obj.flags.is_executable() && !is_immediate {
636 ctx.e_stack.push(tok_obj)?;
637 } else {
638 ctx.o_stack.push(tok_obj)?;
639 }
640 Ok(())
641}
642
643fn stream_parse_procedure(ctx: &mut Context, file_entity: EntityId) -> Result<PsObject, PsError> {
648 let mut elements = Vec::new();
649
650 loop {
651 ctx.pump_proc_sources(file_entity)?;
652 match stream_next_token(&mut ctx.files, file_entity)? {
653 None => return Err(PsError::SyntaxError), Some((Token::ProcEnd, _)) => break,
655 Some((Token::ProcBegin, _)) => {
656 let nested = stream_parse_procedure(ctx, file_entity)?;
657 elements.push(nested);
658 }
659 Some((Token::BinaryTokenByte(tag), _)) => {
660 let result = stet_core::binary_token::parse_from_stream(ctx, tag, file_entity)?;
661 let obj = match result {
662 stet_core::binary_token::BinaryTokenResult::Single(o) => o,
663 stet_core::binary_token::BinaryTokenResult::Sequence(o) => o,
664 };
665 elements.push(obj);
666 }
667 Some((token, _)) => {
668 let obj = token_to_object(ctx, token)?;
669 elements.push(obj);
670 }
671 }
672 }
673
674 let len = elements.len();
675 let save_level = ctx.save_stack.current_level();
676 let global = ctx.vm_alloc_mode;
677 let created = ctx.save_stack.last_save_id();
678 let entity = ctx.arrays.allocate_with(len, save_level, global, created);
679 let dest = ctx.arrays.get_mut(entity, 0, len as u32);
680 dest.copy_from_slice(&elements);
681
682 let mut obj = PsObject::procedure(entity, len as u32);
683 if global {
684 obj.flags = ObjFlags::new(ObjFlags::ACCESS_UNLIMITED, true, true, true);
685 }
686 Ok(obj)
687}
688
689fn count_newlines(bytes: &[u8]) -> u32 {
692 let mut count = 0u32;
693 let mut i = 0;
694 while i < bytes.len() {
695 match bytes[i] {
696 b'\r' => {
697 count += 1;
698 if i + 1 < bytes.len() && bytes[i + 1] == b'\n' {
700 i += 1;
701 }
702 }
703 b'\n' | b'\x0c' => {
704 count += 1;
705 }
706 _ => {}
707 }
708 i += 1;
709 }
710 count
711}
712
713fn advance_loop(ctx: &mut Context, loop_entity: EntityId) -> Result<(), PsError> {
715 use stet_core::context::LoopType;
716
717 let loop_state = ctx.get_loop(loop_entity);
718 let loop_type = match loop_state.loop_type {
719 LoopType::For => 0,
720 LoopType::Repeat => 1,
721 LoopType::Loop => 2,
722 LoopType::Forall => 3,
723 LoopType::PathForall => 4,
724 };
725 let proc_entity = loop_state.proc_entity;
726 let proc_start = loop_state.proc_start;
727 let proc_len = loop_state.proc_len;
728
729 match loop_type {
730 0 => {
731 let counter = ctx.get_loop(loop_entity).counter;
733 let increment = ctx.get_loop(loop_entity).increment;
734 let limit = ctx.get_loop(loop_entity).limit;
735 let use_int = ctx.get_loop(loop_entity).use_int;
736
737 let done = if increment > 0.0 {
738 counter > limit
739 } else {
740 counter < limit
741 };
742
743 if done {
744 return Ok(());
745 }
746
747 if use_int {
749 ctx.o_stack.push(PsObject::int(counter as i32))?;
750 } else {
751 ctx.o_stack.push(PsObject::real(counter))?;
752 }
753
754 let new_counter = counter + increment;
756 ctx.get_loop_mut(loop_entity).counter = new_counter;
757
758 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
760 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
761 }
762 1 => {
763 let counter = ctx.get_loop(loop_entity).counter;
765 if counter <= 0.0 {
766 return Ok(());
767 }
768 ctx.get_loop_mut(loop_entity).counter = counter - 1.0;
769 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
770 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
771 }
772 2 => {
773 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
775 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
776 }
777 3 => {
778 advance_forall(ctx, loop_entity, proc_entity, proc_start, proc_len)?;
780 }
781 4 => {
782 advance_pathforall(ctx, loop_entity)?;
784 }
785 _ => unreachable!(),
786 }
787
788 Ok(())
789}
790
791fn advance_forall(
793 ctx: &mut Context,
794 loop_entity: EntityId,
795 proc_entity: EntityId,
796 proc_start: u32,
797 proc_len: u32,
798) -> Result<(), PsError> {
799 let source = ctx.get_loop(loop_entity).source;
800 let index = ctx.get_loop(loop_entity).index;
801
802 match source.value {
803 PsValue::Array { entity, start, len } | PsValue::PackedArray { entity, start, len } => {
804 if index >= len {
805 return Ok(());
806 }
807 let elem = ctx.arrays.get_element(entity, start + index);
808 ctx.o_stack.push(elem)?;
809 ctx.get_loop_mut(loop_entity).index = index + 1;
810 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
811 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
812 }
813 PsValue::String { entity, start, len } => {
814 if index >= len {
815 return Ok(());
816 }
817 let byte = ctx.strings.get_byte(entity, start + index);
818 ctx.o_stack.push(PsObject::int(byte as i32))?;
819 ctx.get_loop_mut(loop_entity).index = index + 1;
820 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
821 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
822 }
823 PsValue::Dict(dict_entity) => {
824 let keys = ctx.get_loop(loop_entity).dict_keys.as_ref().unwrap();
827 if (index as usize) >= keys.len() {
828 return Ok(());
829 }
830 let key = keys[index as usize].clone();
831 let val = ctx.dicts.get(dict_entity, &key).unwrap_or(PsObject::null());
832
833 let key_obj = dict_key_to_object(ctx, &key);
835 ctx.o_stack.push(key_obj)?;
836 ctx.o_stack.push(val)?;
837
838 ctx.get_loop_mut(loop_entity).index = index + 1;
839 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
840 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
841 }
842 _ => return Err(PsError::TypeCheck),
843 }
844
845 Ok(())
846}
847
848fn advance_pathforall(ctx: &mut Context, loop_entity: EntityId) -> Result<(), PsError> {
850 use stet_core::geometry::PathSegment;
851
852 let index = ctx.get_loop(loop_entity).index as usize;
853
854 let seg_len = ctx
856 .get_loop(loop_entity)
857 .path_segments
858 .as_ref()
859 .map_or(0, |s| s.len());
860 if index >= seg_len {
861 return Ok(());
862 }
863
864 let loop_state = ctx.get_loop(loop_entity);
866 let seg = loop_state.path_segments.as_ref().unwrap()[index].clone();
867 let ictm = loop_state.path_ictm.unwrap();
868 let procs = loop_state.path_procs.unwrap();
869
870 let proc = match seg {
872 PathSegment::MoveTo(dx, dy) => {
873 let (ux, uy) = ictm.transform_point(dx, dy);
874 ctx.o_stack.push(PsObject::real(ux))?;
875 ctx.o_stack.push(PsObject::real(uy))?;
876 procs[0] }
878 PathSegment::LineTo(dx, dy) => {
879 let (ux, uy) = ictm.transform_point(dx, dy);
880 ctx.o_stack.push(PsObject::real(ux))?;
881 ctx.o_stack.push(PsObject::real(uy))?;
882 procs[1] }
884 PathSegment::CurveTo {
885 x1,
886 y1,
887 x2,
888 y2,
889 x3,
890 y3,
891 } => {
892 let (ux1, uy1) = ictm.transform_point(x1, y1);
893 let (ux2, uy2) = ictm.transform_point(x2, y2);
894 let (ux3, uy3) = ictm.transform_point(x3, y3);
895 ctx.o_stack.push(PsObject::real(ux1))?;
896 ctx.o_stack.push(PsObject::real(uy1))?;
897 ctx.o_stack.push(PsObject::real(ux2))?;
898 ctx.o_stack.push(PsObject::real(uy2))?;
899 ctx.o_stack.push(PsObject::real(ux3))?;
900 ctx.o_stack.push(PsObject::real(uy3))?;
901 procs[2] }
903 PathSegment::ClosePath => {
904 procs[3] }
906 };
907
908 ctx.get_loop_mut(loop_entity).index = (index + 1) as u32;
910
911 ctx.e_stack.push(PsObject::loop_mark(loop_entity))?;
913 let (proc_entity, proc_start, proc_len) = match proc.value {
914 PsValue::Array { entity, start, len } => (entity, start, len),
915 _ => return Err(PsError::TypeCheck),
916 };
917 exec_procedure(ctx, proc_entity, proc_start, proc_len)?;
918
919 Ok(())
920}
921
922fn dict_key_to_object(ctx: &mut Context, key: &DictKey) -> PsObject {
924 match key {
925 DictKey::Name(id) => PsObject::name_lit(*id),
926 DictKey::Int(v) => PsObject::int(*v),
927 DictKey::Real(bits) => PsObject::real(f64::from_bits(*bits)),
928 DictKey::Bool(v) => PsObject::bool(*v),
929 DictKey::String(bytes) => {
930 let entity = stet_ops::vm_ops::alloc_string(ctx, bytes);
931 PsObject::string(entity, bytes.len() as u32)
932 }
933 DictKey::Operator(op) => {
934 use stet_core::object::OpCode;
935 PsObject::operator(OpCode(*op))
936 }
937 DictKey::Identity(eid, _start, len) => {
938 PsObject::array(EntityId(*eid), *len)
940 }
941 }
942}
943
944fn dispatch_error(ctx: &mut Context, error: &PsError) -> Result<(), PsError> {
951 if ctx.in_error_handler {
953 use std::io::Write;
954 let _ = writeln!(ctx.stdout, "Error (in handler): {}", error);
955 return Ok(());
956 }
957
958 let error_name = error.to_string();
959 let error_name_id = ctx.names.intern(error_name.as_bytes());
960 let error_key = DictKey::Name(error_name_id);
961
962 if let Some(handler) = ctx.dicts.get(ctx.errordict, &error_key)
964 && handler.flags.is_executable()
965 {
966 ctx.in_error_handler = true;
967
968 let cmd_name = ctx.current_operator.unwrap_or(error_name_id);
973 ctx.current_operator = None;
974 ctx.o_stack.push(PsObject::name_lit(cmd_name))?;
975
976 ctx.e_stack.push(handler)?;
981
982 ctx.in_error_handler = false;
983 return Ok(());
984 }
985
986 use std::io::Write;
988 let _ = writeln!(ctx.stdout, "Error: {}", error);
989 Ok(())
990}
991
992fn unwind_to_stopped(ctx: &mut Context) -> Result<(), PsError> {
996 while let Some(obj) = ctx.e_stack.try_pop() {
997 match obj.value {
998 PsValue::Stopped => return Ok(()),
999 PsValue::DictEnd(expected) => {
1000 pop_dict_end(ctx, expected);
1001 }
1002 _ => {}
1003 }
1004 }
1005 Err(PsError::Stop)
1007}
1008
1009fn unwind_to_loop(ctx: &mut Context) -> Result<(), PsError> {
1011 while let Some(obj) = ctx.e_stack.try_pop() {
1012 match obj.value {
1013 PsValue::Loop(_) => return Ok(()),
1014 PsValue::Stopped => {
1015 ctx.e_stack.push(obj)?;
1017 return Err(PsError::InvalidExit);
1018 }
1019 PsValue::DictEnd(expected) => {
1020 pop_dict_end(ctx, expected);
1021 }
1022 _ => {}
1023 }
1024 }
1025 Err(PsError::InvalidExit)
1026}
1027
1028fn pop_dict_end(ctx: &mut Context, expected: EntityId) {
1032 if ctx.d_stack.last() == Some(&expected) {
1033 ctx.d_stack.pop();
1034 ctx.invalidate_name_cache();
1035 }
1036}
1037
1038pub fn token_to_object(ctx: &mut Context, token: Token) -> Result<PsObject, PsError> {
1040 ctx.token_to_object(token)
1041}
1042
1043pub fn parse_and_exec(ctx: &mut Context, source: &[u8]) -> Result<(), PsError> {
1053 let file_entity = ctx.files.create_string_source(source.to_vec());
1054 ctx.e_stack.push(PsObject {
1055 value: PsValue::File(file_entity),
1056 flags: ObjFlags::executable_composite(),
1057 })?;
1058 eval(ctx)
1059}
1060
1061pub fn parse_and_exec_file(ctx: &mut Context, source: &[u8], path: &str) -> Result<(), PsError> {
1067 let file_entity = ctx.files.create_string_source(source.to_vec());
1068 let canonical = std::path::Path::new(path)
1070 .canonicalize()
1071 .unwrap_or_else(|_| std::path::PathBuf::from(path));
1072 ctx.files
1073 .set_name(file_entity, canonical.to_string_lossy().to_string());
1074 ctx.e_stack.push(PsObject {
1075 value: PsValue::File(file_entity),
1076 flags: ObjFlags::executable_composite(),
1077 })?;
1078 eval(ctx)
1079}
1080
1081fn parse_procedure(ctx: &mut Context, tokenizer: &mut Tokenizer) -> Result<PsObject, PsError> {
1083 let mut elements = Vec::new();
1084
1085 loop {
1086 match tokenizer.next_token()? {
1087 Some(Token::ProcEnd) => break,
1088 Some(Token::ProcBegin) => {
1089 let nested = parse_procedure(ctx, tokenizer)?;
1090 elements.push(nested);
1091 }
1092 Some(Token::BinaryTokenByte(tag)) => {
1093 let pos = tokenizer.position();
1094 let input_bytes = tokenizer.remaining_from(pos);
1097 let (result, consumed) =
1098 stet_core::binary_token::parse_from_slice(ctx, tag, input_bytes)?;
1099 tokenizer.advance(consumed);
1100 let obj = match result {
1101 stet_core::binary_token::BinaryTokenResult::Single(o) => o,
1102 stet_core::binary_token::BinaryTokenResult::Sequence(o) => o,
1103 };
1104 elements.push(obj);
1105 }
1106 Some(token) => {
1107 let obj = token_to_object(ctx, token)?;
1108 elements.push(obj);
1109 }
1110 None => return Err(PsError::SyntaxError), }
1112 }
1113
1114 let len = elements.len();
1115 let save_level = ctx.save_stack.current_level();
1116 let global = ctx.vm_alloc_mode;
1117 let created = ctx.save_stack.last_save_id();
1118 let entity = ctx.arrays.allocate_with(len, save_level, global, created);
1119 let dest = ctx.arrays.get_mut(entity, 0, len as u32);
1120 dest.copy_from_slice(&elements);
1121
1122 let mut obj = PsObject::procedure(entity, len as u32);
1123 if global {
1124 obj.flags = ObjFlags::new(ObjFlags::ACCESS_UNLIMITED, true, true, true);
1125 }
1126 Ok(obj)
1127}
1128
1129#[cfg(test)]
1130mod tests {
1131 use super::*;
1132 use std::io::Write;
1133
1134 #[test]
1135 fn test_parse_procedure() {
1136 let mut ctx = Context::new();
1137 let mut tokenizer = Tokenizer::new(b"1 2 add }");
1138 let proc_obj = parse_procedure(&mut ctx, &mut tokenizer).unwrap();
1139 assert!(proc_obj.flags.is_executable());
1140 assert!(proc_obj.is_array_type());
1141 match proc_obj.value {
1142 PsValue::Array { len, .. } => assert_eq!(len, 3),
1143 _ => panic!("Expected array"),
1144 }
1145 }
1146
1147 #[test]
1148 fn test_nested_procedure() {
1149 let mut ctx = Context::new();
1150 let mut tokenizer = Tokenizer::new(b"{ 1 add } exec }");
1151 let proc_obj = parse_procedure(&mut ctx, &mut tokenizer).unwrap();
1152 match proc_obj.value {
1153 PsValue::Array { len, .. } => assert_eq!(len, 2), _ => panic!("Expected array"),
1155 }
1156 }
1157
1158 fn setup_ctx() -> (Context, std::sync::Arc<std::sync::Mutex<Vec<u8>>>) {
1161 let buf = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
1162 let writer = buf.clone();
1163
1164 struct ArcWriter(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);
1165 impl Write for ArcWriter {
1166 fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
1167 self.0.lock().unwrap().extend_from_slice(data);
1168 Ok(data.len())
1169 }
1170 fn flush(&mut self) -> std::io::Result<()> {
1171 Ok(())
1172 }
1173 }
1174
1175 let mut ctx = Context::new_with_output(Box::new(ArcWriter(writer)));
1176 stet_ops::build_system_dict(&mut ctx);
1177 (ctx, buf)
1178 }
1179
1180 fn run_ps(source: &[u8]) -> String {
1181 let (mut ctx, buf) = setup_ctx();
1182 parse_and_exec(&mut ctx, source).ok();
1183 String::from_utf8(buf.lock().unwrap().clone()).unwrap()
1184 }
1185
1186 #[test]
1189 fn test_save_restore_reverts_def() {
1190 let (mut ctx, buf) = setup_ctx();
1191 let result = parse_and_exec(&mut ctx, b"save /s exch def /x 1 def s restore");
1193 assert!(result.is_ok());
1194 let output = String::from_utf8(buf.lock().unwrap().clone()).unwrap();
1196 let x_id = ctx.names.intern(b"x");
1198 let key = DictKey::Name(x_id);
1199 assert!(
1200 ctx.dict_load(&key).is_none(),
1201 "x should be undefined after restore"
1202 );
1203 drop(output);
1204 }
1205
1206 #[test]
1209 fn test_save_restore_reverts_array() {
1210 let output = run_ps(b"[1 2 3] /a exch def save /s exch def a 1 99 put s restore a 1 get =");
1211 assert_eq!(output.trim(), "2");
1212 }
1213
1214 #[test]
1216 fn test_file_round_trip() {
1217 let path = std::env::temp_dir()
1222 .join("stet_phase2_file_test.txt")
1223 .to_string_lossy()
1224 .replace('\\', "/");
1225 let source = format!(
1226 "({}) (w) file /f exch def f (hello world) writestring f closefile \
1227 ({}) (r) file /f exch def f 11 string readstring pop print f closefile",
1228 path, path
1229 );
1230 let output = run_ps(source.as_bytes());
1231 assert_eq!(output, "hello world");
1232 std::fs::remove_file(&path).ok();
1233 }
1234
1235 #[test]
1237 fn test_stopped_catches_error() {
1238 let output = run_ps(b"{ 1 0 div } stopped { (caught\n) print } if");
1239 assert_eq!(output.trim(), "caught");
1240 }
1241
1242 #[test]
1244 fn test_setglobal_gcheck() {
1245 let output = run_ps(b"true setglobal 3 array gcheck =");
1246 assert_eq!(output.trim(), "true");
1247 }
1248
1249 #[test]
1251 fn test_vmstatus() {
1252 let output = run_ps(b"vmstatus = = =");
1253 let lines: Vec<&str> = output.trim().lines().collect();
1254 assert_eq!(lines.len(), 3, "vmstatus should push 3 values");
1255 for line in &lines {
1257 assert!(
1258 line.trim().parse::<i32>().is_ok(),
1259 "Expected integer: {}",
1260 line
1261 );
1262 }
1263 }
1264
1265 #[test]
1267 fn test_error_dispatch_stop() {
1268 let output = run_ps(b"{ 1 0 div } stopped { (error caught\n) print } if");
1269 assert!(output.contains("error caught"));
1270 }
1271
1272 #[test]
1274 fn test_nested_save_restore() {
1275 let output = run_ps(
1276 b"/x 10 def \
1277 save /s1 exch def \
1278 /x 20 def \
1279 save /s2 exch def \
1280 /x 30 def \
1281 x = \
1282 s2 restore \
1283 x = \
1284 s1 restore \
1285 x =",
1286 );
1287 let lines: Vec<&str> = output.trim().lines().collect();
1288 assert_eq!(lines, vec!["30", "20", "10"]);
1289 }
1290
1291 #[test]
1295 fn test_global_survives_restore() {
1296 let output = run_ps(
1297 b"true setglobal \
1298 3 array /ga exch def \
1299 false setglobal \
1300 save /s exch def \
1301 ga 0 42 put \
1302 s restore \
1303 ga 0 get =",
1304 );
1305 assert_eq!(output.trim(), "42");
1306 }
1307}