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
8+ from pathlib import Path
69from typing import Any , Sequence
710
811from diopter .compiler import (
1215 CompilationSetting ,
1316 ExeCompilationOutput ,
1417 Language ,
18+ Source ,
19+ SourceFile ,
1520 SourceProgram ,
1621)
1722from program_markers .markers import (
@@ -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,12 @@ 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.
173-
174- Returns:
175- str:
176- the source code including the necessary preprocessor directives
177- """
178-
179- return self .generate_preprocessor_directives () + "\n " + self .code
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+ )
180180
181181 def find_non_eliminated_markers (
182182 self , compilation_setting : CompilationSetting
@@ -197,7 +197,7 @@ def find_non_eliminated_markers(
197197 The non_eliminated markers for the given compilation setting.
198198 """
199199 asm = compilation_setting .compile_program (
200- self , ASMCompilationOutput ()
200+ self . self_as_source () , ASMCompilationOutput ()
201201 ).output .read ()
202202 non_eliminated_markers = find_non_eliminated_markers_impl (
203203 asm , self .enabled_markers (), self .marker_strategy
@@ -228,6 +228,32 @@ def find_eliminated_markers(
228228 eliminated_markers = eliminated_markers & set (self .enabled_markers ())
229229 return tuple (eliminated_markers )
230230
231+
232+ @dataclass (frozen = True , kw_only = True )
233+ class InstrumentedProgram (SourceProgram , InstrumentedSourceMixin ):
234+ marker_strategy : MarkerDetectionStrategy
235+ markers : tuple [Marker , ...]
236+ directive_emitters : dict [Marker , MarkerDirectiveEmitter ]
237+
238+ def self_as_source (self ) -> SourceProgram :
239+ return self
240+
241+ def get_modified_code (self ) -> str :
242+ """Returns the necessary preprocessor directives for markers + self.code.
243+
244+ Only directives for the enabled, disabled and made unreachable markers
245+ are added.
246+
247+ If any markers have not been enabled, disabled, or made unreachable,
248+ then the code not compilable but it can be preprocessed.
249+
250+ Returns:
251+ str:
252+ the source code including the necessary preprocessor directives
253+ """
254+
255+ return self .generate_preprocessor_directives () + "\n " + self .code
256+
231257 def replace_markers (self , new_markers : tuple [Marker , ...]) -> InstrumentedProgram :
232258 """Replaces the markers in the program with the new ones.
233259 Each original marker whose id matches one of the new ones is replaced,
@@ -275,13 +301,6 @@ def compile_program_for_tracking(
275301 tracked_program = replace (self , directive_emitters = new_emitters )
276302 return setting .compile_program (tracked_program , output , timeout = timeout )
277303
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-
285304 def track_reachable_markers (
286305 self ,
287306 args : tuple [str , ...],
@@ -667,3 +686,103 @@ def from_json_dict_impl(
667686 markers = markers ,
668687 directive_emitters = directive_emitters ,
669688 )
689+
690+
691+ @dataclass (frozen = True , kw_only = True )
692+ class InstrumentedFile (SourceFile , InstrumentedSourceMixin ):
693+ marker_strategy : MarkerDetectionStrategy
694+ markers : tuple [Marker , ...]
695+ directive_emitters : dict [Marker , MarkerDirectiveEmitter ]
696+ directives_include_file : Path # this should be included
697+ directives_json_file : Path
698+ debug : bool = True
699+
700+ def self_as_source (self ) -> SourceFile :
701+ return self
702+
703+ def __post_init__ (self ) -> None :
704+ assert self .directives_include_file .is_absolute ()
705+ assert self .directives_json_file .is_absolute ()
706+ assert self .directives_include_file .exists ()
707+ assert self .directives_json_file .exists ()
708+ assert f"-include { str (self .directives_include_file )} " in self .flags , self .flags
709+
710+ if self .debug :
711+ with open (self .directives_json_file ) as f :
712+ j = json .load (f )
713+ j ["strategy" ] = self .marker_strategy .to_json_dict ()
714+ directive_emitters = {
715+ Marker .from_json_dict (m ): MarkerDirectiveEmitter .from_json_dict (
716+ directive
717+ )
718+ for m , directive in j ["directive_emitters" ]
719+ }
720+ assert directive_emitters == self .directive_emitters , (
721+ directive_emitters ,
722+ self .directive_emitters ,
723+ )
724+
725+ markers = tuple (Marker .from_json_dict (m ) for m in j ["markers" ])
726+ assert markers == self .markers
727+ with open (self .directives_include_file ) as f :
728+ assert f .read () == self .generate_preprocessor_directives ()
729+
730+ def generate_tracking (self ) -> InstrumentedFile :
731+ new_emitters = self .directive_emitters .copy ()
732+ tracked_markers = self .enabled_markers ()
733+ te = TrackingEmitter ()
734+ for marker in tracked_markers :
735+ new_emitters [marker ] = te
736+
737+ new_stem = self .directives_include_file .stem + "_tracking"
738+ inc_file = self .directives_include_file .with_stem (new_stem )
739+ with open (inc_file , "w" ) as f :
740+ f .write (
741+ "\n " .join (
742+ emitter .emit_directive (marker )
743+ for marker , emitter in new_emitters .items ()
744+ )
745+ )
746+
747+ json_file = self .directives_json_file .with_stem (new_stem )
748+
749+ with open (json_file , "w" ) as f :
750+ json .dump (
751+ {
752+ "marker_strategy" : self .marker_strategy .to_json_dict (),
753+ "directive_emitters" : [
754+ (m .to_json_dict (), e .to_json_dict ())
755+ for m , e in new_emitters .items ()
756+ ],
757+ "markers" : [m .to_json_dict () for m in self .markers ],
758+ },
759+ f ,
760+ )
761+
762+ flags = tuple (
763+ f
764+ for f in self .flags
765+ if f != f"-include { str (self .directives_include_file )} "
766+ ) + (f"-include { str (inc_file )} " ,)
767+
768+ return replace (
769+ self ,
770+ directive_emitters = new_emitters ,
771+ flags = flags ,
772+ directives_include_file = inc_file ,
773+ directives_json_file = json_file ,
774+ )
775+
776+ def generate_tracking_for_refinement_version (self ) -> InstrumentedFile :
777+ pass
778+
779+ def refine_markers (self , markers : tuple [Marker , ...]) -> InstrumentedFile :
780+ pass
781+
782+ def make_markers_unreachable (self , markers : tuple [Marker , ...]) -> InstrumentedFile :
783+ pass
784+
785+ def disable_remaining_markers (
786+ self , do_not_disable : Sequence [Marker ] = tuple ()
787+ ) -> InstrumentedFile :
788+ pass
0 commit comments