11from __future__ import annotations
22
3+ import json
34import re
5+ from abc import ABC , abstractmethod
46from collections import defaultdict
57from dataclasses import dataclass , replace
68from typing import Any , Sequence
9+ from pathlib import Path
710
811from diopter .compiler import (
12+ Source ,
913 ASMCompilationOutput ,
1014 CompilationOutputType ,
1115 CompilationResult ,
1216 CompilationSetting ,
1317 ExeCompilationOutput ,
1418 Language ,
1519 SourceProgram ,
20+ SourceFile ,
1621)
1722from program_markers .markers import (
1823 AbortEmitter ,
@@ -113,11 +118,15 @@ def collect_markers(
113118
114119
115120@dataclass (frozen = True , kw_only = True )
116- class InstrumentedProgram ( SourceProgram ):
121+ class InstrumentedSourceMixin ( ABC ):
117122 marker_strategy : MarkerDetectionStrategy
118123 markers : tuple [Marker , ...]
119124 directive_emitters : dict [Marker , MarkerDirectiveEmitter ]
120125
126+ @abstractmethod
127+ def self_as_source (self ) -> Source :
128+ raise NotImplementedError
129+
121130 def __post_init__ (self ) -> None :
122131 # All markers ids are unique
123132 marker_ids = set ()
@@ -162,21 +171,14 @@ def generate_preprocessor_directives(self) -> str:
162171 for marker , emitter in self .directive_emitters .items ()
163172 )
164173
165- def get_modified_code (self ) -> str :
166- """Returns the necessary preprocessor directives for markers + self.code.
167-
168- Only directives for the enabled, disabled and made unreachable markers
169- are added.
170-
171- If any markers have not been enabled, disabled, or made unreachable,
172- then the code not compilable but it can be preprocessed.
174+ def process_tracking_reachable_markers_output (
175+ self , output : str
176+ ) -> tuple [Marker , ...]:
177+ return tuple (
178+ marker for marker in self .enabled_markers () if marker .name in output
179+ )
173180
174- Returns:
175- str:
176- the source code including the necessary preprocessor directives
177- """
178181
179- return self .generate_preprocessor_directives () + "\n " + self .code
180182
181183 def find_non_eliminated_markers (
182184 self , compilation_setting : CompilationSetting
@@ -197,7 +199,7 @@ def find_non_eliminated_markers(
197199 The non_eliminated markers for the given compilation setting.
198200 """
199201 asm = compilation_setting .compile_program (
200- self , ASMCompilationOutput ()
202+ self . self_as_source () , ASMCompilationOutput ()
201203 ).output .read ()
202204 non_eliminated_markers = find_non_eliminated_markers_impl (
203205 asm , self .enabled_markers (), self .marker_strategy
@@ -228,6 +230,32 @@ def find_eliminated_markers(
228230 eliminated_markers = eliminated_markers & set (self .enabled_markers ())
229231 return tuple (eliminated_markers )
230232
233+
234+ @dataclass (frozen = True , kw_only = True )
235+ class InstrumentedProgram (SourceProgram , InstrumentedSourceMixin ):
236+ marker_strategy : MarkerDetectionStrategy
237+ markers : tuple [Marker , ...]
238+ directive_emitters : dict [Marker , MarkerDirectiveEmitter ]
239+
240+ def self_as_source (self ) -> SourceProgram :
241+ return self
242+
243+ def get_modified_code (self ) -> str :
244+ """Returns the necessary preprocessor directives for markers + self.code.
245+
246+ Only directives for the enabled, disabled and made unreachable markers
247+ are added.
248+
249+ If any markers have not been enabled, disabled, or made unreachable,
250+ then the code not compilable but it can be preprocessed.
251+
252+ Returns:
253+ str:
254+ the source code including the necessary preprocessor directives
255+ """
256+
257+ return self .generate_preprocessor_directives () + "\n " + self .code
258+
231259 def replace_markers (self , new_markers : tuple [Marker , ...]) -> InstrumentedProgram :
232260 """Replaces the markers in the program with the new ones.
233261 Each original marker whose id matches one of the new ones is replaced,
@@ -275,13 +303,6 @@ def compile_program_for_tracking(
275303 tracked_program = replace (self , directive_emitters = new_emitters )
276304 return setting .compile_program (tracked_program , output , timeout = timeout )
277305
278- def process_tracking_reachable_markers_output (
279- self , output : str
280- ) -> tuple [Marker , ...]:
281- return tuple (
282- marker for marker in self .enabled_markers () if marker .name in output
283- )
284-
285306 def track_reachable_markers (
286307 self ,
287308 args : tuple [str , ...],
@@ -667,3 +688,100 @@ def from_json_dict_impl(
667688 markers = markers ,
668689 directive_emitters = directive_emitters ,
669690 )
691+
692+
693+ @dataclass (frozen = True , kw_only = True )
694+ class InstrumentedFile (SourceFile , InstrumentedSourceMixin ):
695+ marker_strategy : MarkerDetectionStrategy
696+ markers : tuple [Marker , ...]
697+ directive_emitters : dict [Marker , MarkerDirectiveEmitter ]
698+ directives_include_file : Path # this should be included
699+ directives_json_file : Path
700+ debug : bool = True
701+
702+ def self_as_source (self ) -> SourceFile :
703+ return self
704+
705+ def __post_init__ (self ) -> None :
706+ assert self .directives_include_file .is_absolute ()
707+ assert self .directives_json_file .is_absolute ()
708+ assert self .directives_include_file .exists ()
709+ assert self .directives_json_file .exists ()
710+ assert f"-include { str (self .directives_include_file )} " in self .flags , self .flags
711+
712+ if self .debug :
713+ with open (self .directives_json_file ) as f :
714+ j = json .load (f )
715+ j ["strategy" ] = self .marker_strategy .to_json_dict ()
716+ directive_emitters = {
717+ Marker .from_json_dict (m ): MarkerDirectiveEmitter .from_json_dict (
718+ directive
719+ )
720+ for m , directive in j ["directive_emitters" ]
721+ }
722+ assert directive_emitters == self .directive_emitters , (directive_emitters , self .directive_emitters )
723+
724+ markers = tuple (Marker .from_json_dict (m ) for m in j ["markers" ])
725+ assert markers == self .markers
726+ with open (self .directives_include_file ) as f :
727+ assert f .read () == self .generate_preprocessor_directives ()
728+
729+ def generate_tracking (self ) -> InstrumentedFile :
730+ new_emitters = self .directive_emitters .copy ()
731+ tracked_markers = self .enabled_markers ()
732+ te = TrackingEmitter ()
733+ for marker in tracked_markers :
734+ new_emitters [marker ] = te
735+
736+ new_stem = self .directives_include_file .stem + "_tracking"
737+ inc_file = self .directives_include_file .with_stem (new_stem )
738+ with open (inc_file , "w" ) as f :
739+ f .write (
740+ "\n " .join (
741+ emitter .emit_directive (marker )
742+ for marker , emitter in new_emitters .items ()
743+ )
744+ )
745+
746+ json_file = self .directives_json_file .with_stem (new_stem )
747+
748+ with open (json_file , "w" ) as f :
749+ json .dump (
750+ {
751+ "marker_strategy" : self .marker_strategy .to_json_dict (),
752+ "directive_emitters" : [
753+ (m .to_json_dict (), e .to_json_dict ())
754+ for m , e in new_emitters .items ()
755+ ],
756+ "markers" : [m .to_json_dict () for m in self .markers ],
757+ },
758+ f ,
759+ )
760+
761+ flags = tuple (
762+ f
763+ for f in self .flags
764+ if f != f"-include { str (self .directives_include_file )} "
765+ ) + (f"-include { str (inc_file )} " ,)
766+
767+ return replace (
768+ self ,
769+ directive_emitters = new_emitters ,
770+ flags = flags ,
771+ directives_include_file = inc_file ,
772+ directives_json_file = json_file ,
773+ )
774+
775+ def generate_tracking_for_refinement_version (self ) -> InstrumentedFile :
776+ pass
777+
778+ def refine_markers (self , markers : tuple [Marker , ...]) -> InstrumentedFile :
779+ pass
780+
781+ def make_markers_unreachable (self , markers : tuple [Marker , ...]) -> InstrumentedFile :
782+ pass
783+
784+ def disable_remaining_markers (
785+ self , do_not_disable : Sequence [Marker ] = tuple ()
786+ ) -> InstrumentedFile :
787+ pass
0 commit comments