Coverage for apio/utils/jsonc.py: 98%
41 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 05:08 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 05:08 +0000
1# This file is part of the Apio project
2# (C) 2016-present FPGAwars
3# Project authors:
4# * Jesús Arroyo (Jesus89) (2016-2019)
5# * Juan Gonzalez (obijuan) (2019-2024)
6# * The Apio Team (2024-present)
7# License: GPLv2
9"""A simple utility to string '//' comments from a json file.
10It allows to add comments in apio resource files.
12Inspired by the state machine in
13https://github.com/xitop/jsonstrip/blob/main/jsonstrip.py
15Json5 could do the job but it's very slow, doubling the execution time of
16'apio system info'.
17"""
19from enum import Enum
20from dataclasses import dataclass
23class _State(Enum):
24 """Represents the parsing state."""
26 IDLE = 1
27 IN_STRING = 2
28 IN_STRING_ESCAPE = 3
29 IN_FIRST_SLASH = 4
30 IN_COMMENT = 5
33class _Action(Enum):
34 """Actions that are performed on the finite state transitions"""
36 NO_ACTION = 1
37 COPY_UP_TO_COMMENT = 2
38 SKIP_TO_CURRENT_LOCATION = 3
41@dataclass(frozen=True)
42class _Transition:
43 new_state: _State
44 action: _Action
47# A key for any other char.
48ELSE = "ELSE"
51# -- A dict from a state to state transitions. Each state transition is a
52# -- dict from an input char to a transition, with ELSE is the transition to
53# -- all other chars.
54STATES_TRANSITIONS = {
55 _State.IDLE: {
56 "/": _Transition(_State.IN_FIRST_SLASH, _Action.NO_ACTION),
57 '"': _Transition(_State.IN_STRING, _Action.NO_ACTION),
58 },
59 _State.IN_STRING: {
60 "\\": _Transition(_State.IN_STRING_ESCAPE, _Action.NO_ACTION),
61 '"': _Transition(_State.IDLE, _Action.NO_ACTION),
62 },
63 _State.IN_STRING_ESCAPE: {
64 ELSE: _Transition(_State.IN_STRING, _Action.NO_ACTION),
65 },
66 _State.IN_FIRST_SLASH: {
67 "/": _Transition(_State.IN_COMMENT, _Action.COPY_UP_TO_COMMENT),
68 ELSE: _Transition(_State.IDLE, _Action.NO_ACTION),
69 },
70 _State.IN_COMMENT: {
71 "\r": _Transition(_State.IDLE, _Action.SKIP_TO_CURRENT_LOCATION),
72 "\n": _Transition(_State.IDLE, _Action.SKIP_TO_CURRENT_LOCATION),
73 },
74}
77def to_json(text: str) -> str:
78 """Convert jasonc input to json by removing '//' comments. Line and
79 number and characters position are preserved to any later json parsing
80 errors meaningful to the user.
81 """
82 output = []
84 # -- Indicates the input position that is already covered by the content
85 # -- in output. It can be larger than the size of the text in output since
86 # -- we drop comment text.
87 output_pos = 0
89 # -- The current state of the state machine.
90 state = _State.IDLE
91 state_transitions = STATES_TRANSITIONS[state]
93 # -- Iterate and process the input chars.
94 for input_pos, ch in enumerate(text):
95 transition: _Transition
97 if ch in state_transitions:
98 # -- Found a transition for this char.
99 transition = state_transitions[ch]
101 elif ELSE in state_transitions:
102 # -- Use the default transition if any.
103 transition = state_transitions[ELSE]
105 else:
106 # -- Otherwise, do nothing. All of our actions are in transitions.
107 continue
109 # -- We found a transition. Apply it.
111 # -- Set the state.
112 state = transition.new_state
113 state_transitions = STATES_TRANSITIONS[state]
115 # -- Perform action, if any.
116 if transition.action is _Action.SKIP_TO_CURRENT_LOCATION:
117 # -- Move output pos to input pos. This is how we skip over
118 # -- comments.
119 output_pos = input_pos
120 continue
122 if transition.action is _Action.COPY_UP_TO_COMMENT:
123 # -- We just entered a comment, copy any pending text from
124 # -- before the '//'.
125 end = input_pos - 1
126 output.append(text[output_pos:end])
127 output_pos = input_pos - 1
128 continue
130 # -- Here when the transition doesn't have an action. Do nothing.
131 assert transition.action is _Action.NO_ACTION
133 # -- Here we reached the end of the input. Copy pending non comment text
134 # -- if any.
135 if state != _State.IN_COMMENT: 135 ↛ 139line 135 didn't jump to line 139 because the condition on line 135 was always true
136 output.append(text[output_pos:])
138 # -- Concatenates the text pieces into a string.
139 return "".join(output)