#!/usr/bin/env python3
# encoding=utf-8
# 描述:校验 .sr 文件语法(支持 C 风格注释)
# Copyright (c) 2024 Huawei Technologies Co., Ltd.
# openUBMC is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#         http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.
"""
Validate .sr files (openUBMC CSR/PSR self-description records).

.sr files are JSON with C-style comments (JSONC). This hook strips
// line comments and /* */ block comments using a state machine that
respects string literals, then parses the result with json.loads.

Pure standard library only — no json5/jsonc dependencies.

Usage: check_sr.py <file1.sr> [file2.sr ...]
"""
import json
import sys
from typing import Sequence

# 6-state finite state machine constants
_NORMAL = 0
_IN_STRING = 1
_IN_STRING_ESCAPE = 2
_IN_LINE_COMMENT = 3
_IN_BLOCK_COMMENT = 4
_IN_BLOCK_COMMENT_STAR = 5


def strip_comments(text: str) -> str:
    """
    Strip // line comments and /* */ block comments from JSON-like text.

    Uses a state machine to correctly skip comment markers that appear
    inside string literals (e.g. "https://example.com" must be preserved).
    Newlines are preserved even inside stripped comments so that json
    error messages stay aligned with the original file's line numbers.
    """
    out = []
    state = _NORMAL
    i = 0
    n = len(text)
    while i < n:
        ch = text[i]
        nxt = text[i + 1] if i + 1 < n else ""
        if state == _NORMAL:
            if ch == '"':
                out.append(ch)
                state = _IN_STRING
            elif ch == "/" and nxt == "/":
                state = _IN_LINE_COMMENT
                i += 1  # consume second '/'
            elif ch == "/" and nxt == "*":
                state = _IN_BLOCK_COMMENT
                i += 1  # consume '*'
            else:
                out.append(ch)
        elif state == _IN_STRING:
            out.append(ch)
            if ch == "\\":
                state = _IN_STRING_ESCAPE
            elif ch == '"':
                state = _NORMAL
        elif state == _IN_STRING_ESCAPE:
            # After a backslash, the next character is literal (handles \" and \\).
            out.append(ch)
            state = _IN_STRING
        elif state == _IN_LINE_COMMENT:
            # Discard everything until end of line; keep the newline for line numbers.
            if ch == "\n":
                out.append("\n")
                state = _NORMAL
        elif state == _IN_BLOCK_COMMENT:
            if ch == "*":
                state = _IN_BLOCK_COMMENT_STAR
            # else: discard (including newlines; line drift is acceptable for block comments)
        else:  # _IN_BLOCK_COMMENT_STAR
            if ch == "/":
                state = _NORMAL
            elif ch == "*":
                pass  # stay in _IN_BLOCK_COMMENT_STAR (handles ***/ )
            else:
                state = _IN_BLOCK_COMMENT
        i += 1
    return "".join(out)


def main(argv: Sequence[str] | None = None) -> int:
    if argv is None:
        argv = sys.argv[1:]
    if not argv:
        return 0
    retval = 0
    for path in argv:
        try:
            with open(path, encoding="utf-8-sig") as f:
                raw = f.read()
            json.loads(strip_comments(raw))
        except (OSError, ValueError) as e:
            print(f"{path}: {e}")
            retval = 1
    return retval


if __name__ == "__main__":
    raise SystemExit(main())