1use std::io::Write;
8
9use crate::device::OutputDevice;
10use crate::dict::DictKey;
11use crate::display_list::DisplayList;
12use crate::dual_array_store::DualArrayStore;
13use crate::dual_dict_store::DualDictStore;
14use crate::dual_string_store::DualStringStore;
15use crate::error::PsError;
16use crate::file_store::FileStore;
17use crate::graphics_state::{GraphicsState, Matrix, PathSegment, PatternData};
18use crate::name::NameTable;
19use crate::object::{EntityId, NameId, ObjFlags, PsObject, PsValue, SaveLevel};
20use crate::save_stack::{SaveRecord, SaveStack, StoreType};
21use crate::stack::Stack;
22
23pub struct OpEntry {
25 pub func: fn(&mut Context) -> Result<(), PsError>,
26 pub name: NameId,
27}
28
29pub struct NameCache {
31 pub n_def: NameId,
32 pub n_true: NameId,
33 pub n_false: NameId,
34 pub n_null: NameId,
35 pub n_mark: NameId,
36 pub n_font_name: NameId,
38 pub n_font_type: NameId,
39 pub n_font_matrix: NameId,
40 pub n_font_bbox: NameId,
41 pub n_encoding: NameId,
42 pub n_char_strings: NameId,
43 pub n_private: NameId,
44 pub n_fid: NameId,
45 pub n_paint_type: NameId,
46 pub n_subrs: NameId,
47 pub n_len_iv: NameId,
48 pub n_notdef: NameId,
49 pub n_metrics: NameId,
50 pub n_font_directory: NameId,
51 pub n_find_resource: NameId,
53 pub n_define_resource: NameId,
54 pub n_undef_resource: NameId,
55 pub n_resource_status: NameId,
56 pub n_resource_for_all: NameId,
57 pub n_category: NameId,
58 pub n_instance_type: NameId,
59 pub n_resource_dir: NameId,
60 pub n_resource_ext: NameId,
61 pub n_build_char: NameId,
63 pub n_build_glyph: NameId,
64 pub n_stroke_width: NameId,
66 pub n_wmode: NameId,
67}
68
69pub struct LoopState {
71 pub loop_type: LoopType,
72 pub proc_entity: EntityId,
73 pub proc_start: u32,
74 pub proc_len: u32,
75
76 pub counter: f64,
78 pub increment: f64,
79 pub limit: f64,
80 pub use_int: bool,
81
82 pub source: PsObject,
84 pub index: u32,
85 pub dict_keys: Option<Vec<DictKey>>,
87
88 pub path_segments: Option<Vec<PathSegment>>,
90 pub path_procs: Option<[PsObject; 4]>, pub path_ictm: Option<Matrix>,
92}
93
94pub enum LoopType {
96 For,
97 Repeat,
98 Loop,
99 Forall,
100 PathForall,
101}
102
103pub type ExecSyncFn = fn(&mut Context, PsObject) -> Result<(), PsError>;
106
107pub struct Context {
108 pub o_stack: Stack,
110 pub e_stack: Stack,
111 pub d_stack: Vec<EntityId>,
112
113 pub strings: DualStringStore,
115 pub arrays: DualArrayStore,
116 pub dicts: DualDictStore,
117 pub names: NameTable,
118 pub files: FileStore,
119
120 pub loops: Vec<LoopState>,
122
123 pub operators: Vec<OpEntry>,
125
126 pub systemdict: EntityId,
128 pub globaldict: EntityId,
129 pub userdict: EntityId,
130 pub errordict: EntityId,
131 pub dollar_error: EntityId,
132
133 pub rand_state: u64,
135 pub rand_seed: i32,
136 pub current_source_line: u32,
138 pub packing_mode: bool,
140 pub echo: bool,
142
143 pub name_cache: NameCache,
145
146 pub stdout: Box<dyn Write>,
148
149 pub save_stack: SaveStack,
151 pub job_start_save_depth: usize,
153
154 pub vm_alloc_mode: bool,
156
157 pub object_format: i32,
159
160 pub current_operator: Option<NameId>,
162 pub in_error_handler: bool,
163 pub initializing: bool,
165 pub allow_ps_resolution: bool,
168
169 pub gstate: GraphicsState,
171 pub gstate_stack: Vec<crate::graphics_state::GstateEntry>,
172 pub gstate_store: Vec<GraphicsState>,
174 pub device: Option<Box<dyn OutputDevice>>,
175 pub display_list: DisplayList,
176 pub capture_display_lists: Option<Vec<(DisplayList, f64)>>,
180 pub display_list_sender: Option<
184 std::sync::mpsc::Sender<(DisplayList, f64, u32, u32, Option<std::sync::Arc<Vec<u8>>>)>,
185 >,
186 pub page_width: u32,
187 pub page_height: u32,
188 pub output_path: Option<String>,
189 pub page_filter: Option<std::collections::HashSet<i32>>,
191 #[allow(clippy::type_complexity)]
193 pub device_factory: Option<Box<dyn Fn(u32, u32) -> Box<dyn OutputDevice>>>,
194
195 pub font_directory: EntityId,
197 pub font_resource_path: Option<String>,
198 pub next_fid: i32,
199
200 pub global_resources: EntityId,
202 pub local_resources: EntityId,
203 pub category_registry: EntityId,
204 pub resource_base_path: Option<String>,
205
206 pub user_params: EntityId,
208 pub system_params: EntityId,
209
210 pub internaldict: Option<EntityId>,
212
213 pub icc_cache: crate::icc::IccCache,
215
216 pub exec_sync_fn: Option<ExecSyncFn>,
218
219 pub char_width: Option<(f64, f64)>,
221 pub char_width_mode1: Option<((f64, f64), (f64, f64))>,
223
224 pub glyph_caches: rustc_hash::FxHashMap<EntityId, crate::glyph_cache::GlyphCache>,
226 pub char_cache_mode: Option<crate::glyph_cache::Type3CacheMode>,
228
229 pub cshow_pending_cid: Option<i32>,
231
232 pub pattern_store: Vec<PatternData>,
235 pub form_cache: rustc_hash::FxHashMap<EntityId, DisplayList>,
237
238 pub start_time: Option<std::time::Instant>,
240
241 pub dict_version: u64,
243 pub name_resolve_cache: Vec<(u64, PsObject)>,
246
247 pub interrupt_flag: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
251
252 pub yield_after_showpage: bool,
259}
260
261impl Context {
262 pub fn exec_sync(&mut self, proc_obj: PsObject) -> Result<(), PsError> {
264 let f = self.exec_sync_fn.expect("exec_sync not initialized");
265 f(self, proc_obj)
266 }
267
268 pub fn new() -> Self {
271 let mut names = NameTable::new();
272
273 let name_cache = NameCache {
274 n_def: names.intern(b"def"),
275 n_true: names.intern(b"true"),
276 n_false: names.intern(b"false"),
277 n_null: names.intern(b"null"),
278 n_mark: names.intern(b"mark"),
279 n_font_name: names.intern(b"FontName"),
280 n_font_type: names.intern(b"FontType"),
281 n_font_matrix: names.intern(b"FontMatrix"),
282 n_font_bbox: names.intern(b"FontBBox"),
283 n_encoding: names.intern(b"Encoding"),
284 n_char_strings: names.intern(b"CharStrings"),
285 n_private: names.intern(b"Private"),
286 n_fid: names.intern(b"FID"),
287 n_paint_type: names.intern(b"PaintType"),
288 n_subrs: names.intern(b"Subrs"),
289 n_len_iv: names.intern(b"lenIV"),
290 n_notdef: names.intern(b".notdef"),
291 n_metrics: names.intern(b"Metrics"),
292 n_font_directory: names.intern(b"FontDirectory"),
293 n_find_resource: names.intern(b"FindResource"),
295 n_define_resource: names.intern(b"DefineResource"),
296 n_undef_resource: names.intern(b"UndefineResource"),
297 n_resource_status: names.intern(b"ResourceStatus"),
298 n_resource_for_all: names.intern(b"ResourceForAll"),
299 n_category: names.intern(b"Category"),
300 n_instance_type: names.intern(b"InstanceType"),
301 n_resource_dir: names.intern(b"ResourceDir"),
302 n_resource_ext: names.intern(b"ResourceExtension"),
303 n_build_char: names.intern(b"BuildChar"),
304 n_build_glyph: names.intern(b"BuildGlyph"),
305 n_stroke_width: names.intern(b"StrokeWidth"),
306 n_wmode: names.intern(b"WMode"),
307 };
308
309 let mut strings = DualStringStore::new();
310 let mut dicts = DualDictStore::new();
311
312 let systemdict = dicts.allocate_with(400, b"systemdict", 0, true, 0);
316 let globaldict = dicts.allocate_with(100, b"globaldict", 0, true, 0);
317 let userdict = dicts.allocate(200, b"userdict");
318 let errordict = dicts.allocate(50, b"errordict");
319 let dollar_error = dicts.allocate(20, b"$error");
320 let font_directory = dicts.allocate(50, b"FontDirectory");
321
322 let global_resources = dicts.allocate_with(20, b"GlobalResources", 0, true, 0);
324 let local_resources = dicts.allocate(20, b"LocalResources");
325 let category_registry = dicts.allocate_with(30, b"CategoryRegistry", 0, true, 0);
326
327 let user_params = dicts.allocate(25, b"UserParams");
331 for key_name in [
332 "MaxDictStack",
333 "MaxExecStack",
334 "MaxOpStack",
335 "MaxFontItem",
336 "MaxFormItem",
337 "MaxPatternItem",
338 "MaxUPathItem",
339 "MaxScreenItem",
340 "MaxSuperScreen",
341 "MinFontCompress",
342 "MaxLocalVM",
343 "VMReclaim",
344 "VMThreshold",
345 "UCacheBLimit",
346 ] {
347 dicts.put(
348 user_params,
349 DictKey::Name(names.intern(key_name.as_bytes())),
350 PsObject::int(0),
351 );
352 }
353 dicts.put(
354 user_params,
355 DictKey::Name(names.intern(b"JobName")),
356 PsObject::string(strings.allocate_from(b""), 0),
357 );
358 dicts.put(
359 user_params,
360 DictKey::Name(names.intern(b"ExecutionHistory")),
361 PsObject::bool(false),
362 );
363 dicts.put(
364 user_params,
365 DictKey::Name(names.intern(b"ExecutionHistorySize")),
366 PsObject::int(20),
367 );
368 dicts.put(
369 user_params,
370 DictKey::Name(names.intern(b"IdiomRecognition")),
371 PsObject::bool(true),
372 );
373 dicts.put(
374 user_params,
375 DictKey::Name(names.intern(b"AccurateScreens")),
376 PsObject::bool(false),
377 );
378 dicts.put(
379 user_params,
380 DictKey::Name(names.intern(b"HalftoneMode")),
381 PsObject::int(0),
382 );
383
384 let system_params = dicts.allocate(30, b"SystemParams");
385 for (key, val) in [
387 ("MaxFontCache", 67108864),
388 ("MaxFormCache", 131072),
389 ("MaxPatternCache", 131072),
390 ("MaxUPathCache", 131072),
391 ("MaxScreenStorage", 524288),
392 ("MaxDisplayList", 2097152),
393 ("MaxDisplayAndSourceList", 4194304),
394 ("MaxSourceList", 2097152),
395 ("MaxImageBuffer", 524288),
396 ("MaxOutlineCache", 65536),
397 ("MaxStoredScreenCache", 0),
398 ("CurFontCache", 0),
400 ("CurFormCache", 0),
401 ("CurPatternCache", 0),
402 ("CurUPathCache", 0),
403 ("CurScreenStorage", 0),
404 ("CurSourceList", 0),
405 ("CurStoredScreenCache", 0),
406 ("CurOutlineCache", 0),
407 ("PageCount", 0),
408 ("Revision", 1),
409 ] {
410 dicts.put(
411 system_params,
412 DictKey::Name(names.intern(key.as_bytes())),
413 PsObject::int(val),
414 );
415 }
416 let printer_str = strings.allocate_from(b"stet");
417 dicts.put(
418 system_params,
419 DictKey::Name(names.intern(b"PrinterName")),
420 PsObject::string(printer_str, 6),
421 );
422 let realfmt_str = strings.allocate_from(b"IEE");
423 dicts.put(
424 system_params,
425 DictKey::Name(names.intern(b"RealFormat")),
426 PsObject::string(realfmt_str, 3),
427 );
428 let pw_str = strings.allocate_from(b"0");
429 dicts.put(
430 system_params,
431 DictKey::Name(names.intern(b"SystemParamsPassword")),
432 PsObject::string(pw_str, 1),
433 );
434 let pw_str2 = strings.allocate_from(b"0");
435 dicts.put(
436 system_params,
437 DictKey::Name(names.intern(b"StartJobPassword")),
438 PsObject::string(pw_str2, 1),
439 );
440 dicts.put(
441 system_params,
442 DictKey::Name(names.intern(b"LicenseID")),
443 PsObject::int(0),
444 );
445
446 let sd_obj = PsObject::dict(systemdict);
448 dicts.put(
449 systemdict,
450 DictKey::Name(names.intern(b"systemdict")),
451 sd_obj,
452 );
453
454 let ud_obj = PsObject::dict(userdict);
455 dicts.put(systemdict, DictKey::Name(names.intern(b"userdict")), ud_obj);
456
457 let gd_obj = PsObject::dict(globaldict);
458 dicts.put(
459 systemdict,
460 DictKey::Name(names.intern(b"globaldict")),
461 gd_obj,
462 );
463
464 let ed_obj = PsObject::dict(errordict);
465 dicts.put(
466 systemdict,
467 DictKey::Name(names.intern(b"errordict")),
468 ed_obj,
469 );
470
471 let de_obj = PsObject::dict(dollar_error);
472 dicts.put(systemdict, DictKey::Name(names.intern(b"$error")), de_obj);
473
474 let fd_obj = PsObject::dict(font_directory);
475 dicts.put(
476 systemdict,
477 DictKey::Name(name_cache.n_font_directory),
478 fd_obj,
479 );
480
481 dicts.put(
483 systemdict,
484 DictKey::Name(names.intern(b"true")),
485 PsObject::bool(true),
486 );
487 dicts.put(
488 systemdict,
489 DictKey::Name(names.intern(b"false")),
490 PsObject::bool(false),
491 );
492 dicts.put(
493 systemdict,
494 DictKey::Name(names.intern(b"null")),
495 PsObject::null(),
496 );
497
498 dicts.put(
500 systemdict,
501 DictKey::Name(names.intern(b"mark")),
502 PsObject::mark(),
503 );
504
505 dicts.put(
507 systemdict,
508 DictKey::Name(names.intern(b"[")),
509 PsObject::mark(),
510 );
511
512 dicts.put(
514 systemdict,
515 DictKey::Name(names.intern(b"<<")),
516 PsObject::dict_mark(),
517 );
518
519 dicts.put(
521 systemdict,
522 DictKey::Name(names.intern(b"languagelevel")),
523 PsObject::int(3),
524 );
525
526 let d_stack = vec![systemdict, globaldict, userdict];
528
529 Self {
530 o_stack: Stack::new(500),
531 e_stack: Stack::new(250),
532 d_stack,
533 strings,
534 arrays: DualArrayStore::new(),
535 dicts,
536 names,
537 files: FileStore::new(),
538 loops: Vec::new(),
539 operators: Vec::new(),
540 systemdict,
541 globaldict,
542 userdict,
543 errordict,
544 dollar_error,
545 rand_state: 0,
546 rand_seed: 0,
547 current_source_line: 1,
548 packing_mode: false,
549 echo: false,
550 name_cache,
551 stdout: Box::new(std::io::stdout()),
552 save_stack: SaveStack::new(),
553 job_start_save_depth: 0,
554 vm_alloc_mode: false,
555 object_format: 0,
556 current_operator: None,
557 in_error_handler: false,
558 initializing: true,
559 allow_ps_resolution: false,
560 gstate: GraphicsState::new(),
561 gstate_stack: Vec::new(),
562 gstate_store: Vec::new(),
563 device: None,
564 display_list: DisplayList::new(),
565 capture_display_lists: None,
566 display_list_sender: None,
567 page_width: 612,
568 page_height: 792,
569 output_path: None,
570 page_filter: None,
571 device_factory: None,
572 font_directory,
573 font_resource_path: None,
574 next_fid: 0,
575 global_resources,
576 local_resources,
577 category_registry,
578 resource_base_path: None,
579 user_params,
580 system_params,
581 internaldict: None,
582 icc_cache: crate::icc::IccCache::new(),
583 exec_sync_fn: None,
584 char_width: None,
585 char_width_mode1: None,
586 glyph_caches: rustc_hash::FxHashMap::default(),
587 char_cache_mode: None,
588 cshow_pending_cid: None,
589 pattern_store: Vec::new(),
590 form_cache: rustc_hash::FxHashMap::default(),
591 #[cfg(not(target_arch = "wasm32"))]
592 start_time: Some(std::time::Instant::now()),
593 #[cfg(target_arch = "wasm32")]
594 start_time: None,
595 dict_version: 0,
596 name_resolve_cache: Vec::new(),
597 interrupt_flag: None,
598 yield_after_showpage: false,
599 }
600 }
601
602 pub fn new_with_output(output: Box<dyn Write>) -> Self {
604 let mut ctx = Self::new();
605 ctx.stdout = output;
606 ctx
607 }
608
609 #[inline]
613 pub fn dict_load(&mut self, key: &DictKey) -> Option<PsObject> {
614 if let DictKey::Name(name_id) = key {
616 let idx = name_id.0 as usize;
617 if idx < self.name_resolve_cache.len() {
618 let (ver, obj) = self.name_resolve_cache[idx];
619 if ver == self.dict_version {
620 return Some(obj);
621 }
622 }
623 }
624
625 for &dict_id in self.d_stack.iter().rev() {
627 if let Some(val) = self.dicts.get(dict_id, key) {
628 if let DictKey::Name(name_id) = key {
630 let idx = name_id.0 as usize;
631 if idx >= self.name_resolve_cache.len() {
632 self.name_resolve_cache
633 .resize(idx + 64, (u64::MAX, PsObject::null()));
634 }
635 self.name_resolve_cache[idx] = (self.dict_version, val);
636 }
637 return Some(val);
638 }
639 }
640 None
641 }
642
643 #[inline]
645 pub fn invalidate_name_cache(&mut self) {
646 self.dict_version = self.dict_version.wrapping_add(1);
647 }
648
649 pub fn dict_where(&self, key: &DictKey) -> Option<(EntityId, PsObject)> {
651 for &dict_id in self.d_stack.iter().rev() {
652 if let Some(val) = self.dicts.get(dict_id, key) {
653 return Some((dict_id, val));
654 }
655 }
656 None
657 }
658
659 pub fn dict_def(&mut self, key: DictKey, value: PsObject) -> Result<(), PsError> {
661 let current = *self.d_stack.last().ok_or(PsError::DictStackUnderflow)?;
662 self.cow_check_dict(current);
663 self.invalidate_name_cache();
664 self.dicts.put(current, key, value);
665 Ok(())
666 }
667
668 pub fn dict_store(&mut self, key: DictKey, value: PsObject) -> Result<(), PsError> {
670 self.invalidate_name_cache();
671 for &dict_id in self.d_stack.iter().rev() {
672 if self.dicts.known(dict_id, &key) {
673 self.cow_check_dict(dict_id);
674 self.dicts.put(dict_id, key, value);
675 return Ok(());
676 }
677 }
678 self.dict_def(key, value)
680 }
681
682 pub fn make_dict_key(&mut self, obj: &PsObject) -> Result<DictKey, PsError> {
684 match obj.value {
685 PsValue::Name(id) => Ok(DictKey::Name(id)),
686 PsValue::Int(v) => Ok(DictKey::Int(v)),
687 PsValue::Real(v) => Ok(DictKey::Real(v.to_bits())),
688 PsValue::Bool(v) => Ok(DictKey::Bool(v)),
689 PsValue::String { entity, start, len } => {
690 let bytes = self.strings.get(entity, start, len).to_vec();
693 let name_id = self.names.intern(&bytes);
694 Ok(DictKey::Name(name_id))
695 }
696 PsValue::Operator(op) => Ok(DictKey::Operator(op.0)),
697 PsValue::Array { entity, start, len } | PsValue::PackedArray { entity, start, len } => {
698 Ok(DictKey::Identity(entity.0, start, len))
699 }
700 PsValue::Dict(entity) => Ok(DictKey::Identity(entity.0, 0, 0)),
701 PsValue::Null => Err(PsError::TypeCheck),
702 _ => Err(PsError::TypeCheck),
703 }
704 }
705
706 pub fn alloc_loop(&mut self, state: LoopState) -> EntityId {
708 let id = EntityId(self.loops.len() as u32);
709 self.loops.push(state);
710 id
711 }
712
713 pub fn get_loop(&self, entity: EntityId) -> &LoopState {
715 &self.loops[entity.0 as usize]
716 }
717
718 pub fn get_loop_mut(&mut self, entity: EntityId) -> &mut LoopState {
720 &mut self.loops[entity.0 as usize]
721 }
722
723 pub fn take_display_list(&mut self) -> DisplayList {
729 if self.capture_display_lists.is_some() {
730 let dpi = self.current_page_dpi();
731 if let Some(ref mut captures) = self.capture_display_lists {
732 captures.push((self.display_list.clone(), dpi));
733 }
734 }
735 if let Some(ref sender) = self.display_list_sender {
736 let dpi = self.current_page_dpi();
737 let (w, h) = self
740 .device
741 .as_ref()
742 .map(|d| d.page_size())
743 .unwrap_or((self.page_width, self.page_height));
744 let _ = sender.send((self.display_list.clone(), dpi, w, h, None));
747 }
748 if self.yield_after_showpage
752 && let Some(ref flag) = self.interrupt_flag
753 {
754 flag.store(true, std::sync::atomic::Ordering::Relaxed);
755 }
756 std::mem::take(&mut self.display_list)
757 }
758
759 pub fn current_page_dpi(&self) -> f64 {
761 use crate::dict::DictKey;
762 if let Some(pd) = self.gstate.page_device
763 && let Some(name_id) = self.names.find(b"HWResolution")
764 && let Some(obj) = self.dicts.get(pd, &DictKey::Name(name_id))
765 && let PsValue::Array { entity, .. } = obj.value
766 {
767 let first = self.arrays.get_element(entity, 0);
768 return match first.value {
769 PsValue::Real(r) => r,
770 PsValue::Int(i) => i as f64,
771 _ => 72.0,
772 };
773 }
774 72.0
775 }
776
777 pub fn vm_save(&mut self) -> PsObject {
782 let d_depth = self.d_stack.len();
783 let gstate_snapshot = self.gstate.clone();
784 let gstate_stack_snapshot = self.gstate_stack.clone();
785 let (_level, save_id) = self.save_stack.save(
786 d_depth,
787 self.packing_mode,
788 self.vm_alloc_mode,
789 self.object_format,
790 gstate_snapshot,
791 gstate_stack_snapshot,
792 );
793
794 self.gstate_stack.push(crate::graphics_state::GstateEntry {
797 state: self.gstate.clone(),
798 saved_by_save: true,
799 });
800
801 PsObject {
802 value: PsValue::Save(SaveLevel(save_id)),
803 flags: crate::object::ObjFlags::literal(),
804 }
805 }
806
807 pub fn vm_restore(&mut self, save_id: u32) -> Result<(), PsError> {
809 if !self.save_stack.is_valid(save_id) {
811 return Err(PsError::InvalidRestore);
812 }
813
814 let levels = self
819 .save_stack
820 .restore_to(save_id)
821 .ok_or(PsError::InvalidRestore)?;
822
823 for level in levels.iter().rev() {
828 for record in level.records.iter().rev() {
829 match record.store_type {
830 StoreType::String => {
831 self.strings.swap_offsets(record.src, record.copy);
832 self.strings.entity_meta_mut(record.src).save_level = 0;
833 }
834 StoreType::Array => {
835 self.arrays.swap_offsets(record.src, record.copy);
836 self.arrays.entity_meta_mut(record.src).save_level = 0;
837 }
838 StoreType::Dict => {
839 self.dicts.swap_offsets(record.src, record.copy);
840 self.dicts.entity_meta_mut(record.src).save_level = 0;
841 }
842 }
843 }
844 }
845
846 let target = &levels[0];
848 self.packing_mode = target.packing_mode;
849 self.vm_alloc_mode = target.vm_alloc_mode;
850 self.object_format = target.object_format;
851
852 self.gstate = target.gstate.clone();
854 self.gstate_stack = target.gstate_stack.clone();
855
856 self.d_stack.truncate(target.d_stack_depth);
858
859 self.invalidate_name_cache();
860 Ok(())
861 }
862
863 pub fn cow_check_string(&mut self, entity: EntityId) {
868 let current_level = self.save_stack.current_level();
869 if current_level == 0 {
870 return; }
872
873 if entity.is_global() {
874 return; }
876 let meta = self.strings.entity_meta(entity);
877 if meta.save_level >= current_level {
878 return; }
880
881 let copy_id = self.strings.cow_copy(entity);
883 self.strings.entity_meta_mut(entity).save_level = current_level;
884
885 self.save_stack.add_record(SaveRecord {
886 src: entity,
887 copy: copy_id,
888 store_type: StoreType::String,
889 });
890 }
891
892 pub fn cow_check_array(&mut self, entity: EntityId) {
894 let current_level = self.save_stack.current_level();
895 if current_level == 0 {
896 return;
897 }
898
899 if entity.is_global() {
900 return;
901 }
902 let meta = self.arrays.entity_meta(entity);
903 if meta.save_level >= current_level {
904 return;
905 }
906
907 let copy_id = self.arrays.cow_copy(entity);
908 self.arrays.entity_meta_mut(entity).save_level = current_level;
909
910 self.save_stack.add_record(SaveRecord {
911 src: entity,
912 copy: copy_id,
913 store_type: StoreType::Array,
914 });
915 }
916
917 pub fn cow_check_dict(&mut self, entity: EntityId) {
919 let current_level = self.save_stack.current_level();
920 if current_level == 0 {
921 return;
922 }
923
924 if entity.is_global() {
925 return;
926 }
927 let meta = self.dicts.entity_meta(entity);
928 if meta.save_level >= current_level {
929 return;
930 }
931
932 let copy_id = self.dicts.cow_copy(entity);
933 self.dicts.entity_meta_mut(entity).save_level = current_level;
934
935 self.save_stack.add_record(SaveRecord {
936 src: entity,
937 copy: copy_id,
938 store_type: StoreType::Dict,
939 });
940 }
941
942 pub fn token_to_object(&mut self, token: crate::tokenizer::Token) -> Result<PsObject, PsError> {
946 use crate::tokenizer::Token;
947 match token {
948 Token::Int(v) => Ok(PsObject::int(v)),
949 Token::Real(v) => Ok(PsObject::real(v)),
950 Token::String(bytes) => {
951 let save_level = self.save_stack.current_level();
952 let global = self.vm_alloc_mode;
953 let created = self.save_stack.last_save_id();
954 let entity = self
955 .strings
956 .allocate_with(bytes.len(), save_level, global, created);
957 self.strings
958 .get_mut(entity, 0, bytes.len() as u32)
959 .copy_from_slice(&bytes);
960 let mut obj = PsObject::string(entity, bytes.len() as u32);
961 if global {
962 obj.flags = ObjFlags::new(ObjFlags::ACCESS_UNLIMITED, false, true, true);
963 }
964 Ok(obj)
965 }
966 Token::Name(bytes, is_exec) => {
967 let id = self.names.intern(&bytes);
968 if is_exec {
969 Ok(PsObject::name_exec(id))
970 } else {
971 Ok(PsObject::name_lit(id))
972 }
973 }
974 Token::LiteralName(bytes) => {
975 let id = self.names.intern(&bytes);
976 Ok(PsObject::name_lit(id))
977 }
978 Token::ImmediateName(bytes) => {
979 let id = self.names.intern(&bytes);
980 let key = DictKey::Name(id);
981 self.dict_load(&key).ok_or(PsError::Undefined)
982 }
983 Token::ArrayBegin => {
984 let id = self.names.intern(b"[");
985 Ok(PsObject::name_exec(id))
986 }
987 Token::ArrayEnd => {
988 let id = self.names.intern(b"]");
989 Ok(PsObject::name_exec(id))
990 }
991 Token::DictBegin => {
992 let id = self.names.intern(b"<<");
993 Ok(PsObject::name_exec(id))
994 }
995 Token::DictEnd => {
996 let id = self.names.intern(b">>");
997 Ok(PsObject::name_exec(id))
998 }
999 Token::ProcBegin | Token::ProcEnd | Token::Eof | Token::BinaryTokenByte(_) => {
1000 Err(PsError::SyntaxError)
1001 }
1002 }
1003 }
1004
1005 pub fn reset_local_vm(&mut self) {
1008 self.strings.reset_local();
1009 self.arrays.reset_local();
1010 self.dicts.reset_local();
1011 }
1012}
1013
1014impl Default for Context {
1015 fn default() -> Self {
1016 Self::new()
1017 }
1018}
1019
1020#[cfg(test)]
1021mod tests {
1022 use super::*;
1023
1024 #[test]
1025 fn test_context_creation() {
1026 let ctx = Context::new();
1027 assert!(ctx.o_stack.is_empty());
1028 assert!(ctx.e_stack.is_empty());
1029 assert_eq!(ctx.d_stack.len(), 3); }
1031
1032 #[test]
1033 fn test_dict_def_and_load() {
1034 let mut ctx = Context::new();
1035 let key = DictKey::Name(ctx.names.intern(b"foo"));
1036 ctx.dict_def(key.clone(), PsObject::int(42)).unwrap();
1037
1038 let val = ctx.dict_load(&key).unwrap();
1039 assert_eq!(val.as_i32(), Some(42));
1040 }
1041
1042 #[test]
1043 fn test_dict_where() {
1044 let mut ctx = Context::new();
1045 let key = DictKey::Name(ctx.names.intern(b"true"));
1046 let result = ctx.dict_where(&key);
1047 assert!(result.is_some());
1048 let (dict_id, val) = result.unwrap();
1049 assert_eq!(dict_id, ctx.systemdict);
1050 assert!(matches!(val.value, PsValue::Bool(true)));
1051 }
1052
1053 #[test]
1054 fn test_dict_store_existing() {
1055 let mut ctx = Context::new();
1056 let key = DictKey::Name(ctx.names.intern(b"myvar"));
1057
1058 ctx.dict_def(key.clone(), PsObject::int(1)).unwrap();
1060
1061 ctx.dict_store(key.clone(), PsObject::int(2)).unwrap();
1063
1064 let val = ctx.dict_load(&key).unwrap();
1065 assert_eq!(val.as_i32(), Some(2));
1066 }
1067
1068 #[test]
1069 fn test_save_restore_basic() {
1070 let mut ctx = Context::new();
1071 let key = DictKey::Name(ctx.names.intern(b"testvar"));
1072
1073 ctx.dict_def(key.clone(), PsObject::int(1)).unwrap();
1075
1076 let save_obj = ctx.vm_save();
1078 let save_id = match save_obj.value {
1079 PsValue::Save(SaveLevel(id)) => id,
1080 _ => panic!("Expected Save"),
1081 };
1082
1083 ctx.dict_def(key.clone(), PsObject::int(2)).unwrap();
1085 assert_eq!(ctx.dict_load(&key).unwrap().as_i32(), Some(2));
1086
1087 ctx.vm_restore(save_id).unwrap();
1089 assert_eq!(ctx.dict_load(&key).unwrap().as_i32(), Some(1));
1090 }
1091
1092 #[test]
1093 fn test_save_restore_string() {
1094 let mut ctx = Context::new();
1095
1096 let entity = ctx.strings.allocate_from(b"hello");
1097
1098 let save_obj = ctx.vm_save();
1100 let save_id = match save_obj.value {
1101 PsValue::Save(SaveLevel(id)) => id,
1102 _ => panic!("Expected Save"),
1103 };
1104
1105 ctx.cow_check_string(entity);
1107 ctx.strings.put_byte(entity, 0, b'H');
1108 assert_eq!(ctx.strings.get(entity, 0, 5), b"Hello");
1109
1110 ctx.vm_restore(save_id).unwrap();
1112 assert_eq!(ctx.strings.get(entity, 0, 5), b"hello");
1113 }
1114
1115 #[test]
1116 fn test_save_restore_array() {
1117 let mut ctx = Context::new();
1118
1119 let items = [PsObject::int(1), PsObject::int(2), PsObject::int(3)];
1120 let entity = ctx.arrays.allocate_from(&items);
1121
1122 let save_obj = ctx.vm_save();
1123 let save_id = match save_obj.value {
1124 PsValue::Save(SaveLevel(id)) => id,
1125 _ => panic!("Expected Save"),
1126 };
1127
1128 ctx.cow_check_array(entity);
1129 ctx.arrays.set_element(entity, 1, PsObject::int(99));
1130 assert_eq!(ctx.arrays.get_element(entity, 1).as_i32(), Some(99));
1131
1132 ctx.vm_restore(save_id).unwrap();
1133 assert_eq!(ctx.arrays.get_element(entity, 1).as_i32(), Some(2));
1134 }
1135
1136 #[test]
1137 fn test_invalid_restore() {
1138 let mut ctx = Context::new();
1139 assert_eq!(ctx.vm_restore(999), Err(PsError::InvalidRestore));
1141 }
1142}