Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/fpy/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import Union
from dataclasses import dataclass, field

import fpy.types

Check notice

Code scanning / CodeQL

Module is imported with 'import' and 'import from' Note

Module 'fpy.types' is imported with both 'import' and 'import from'.
from fpy.dictionary import json_default_to_fpy_value, load_dictionary
from fpy.error import CompileError, CompileWarning, DictionaryError, WarningType
from fpy.ir import Ir, IrLabel
Expand Down Expand Up @@ -38,6 +39,8 @@
BOOL,
CHECK_STATE,
CMD_RESPONSE,
DEFAULT_FW_SERIALIZE_FALSE_VALUE,
DEFAULT_FW_SERIALIZE_TRUE_VALUE,
DEFAULT_MAX_DIRECTIVES_COUNT,
DEFAULT_MAX_DIRECTIVE_SIZE,
FLAGS_TYPE,
Expand Down Expand Up @@ -601,6 +604,14 @@
), f"Expected int for constant {key}, got {type(val.val)}"
return val.val

# Override the live boolean wire-format constants (read directly by the stateless FpyValue.serialize()) from the dictionary.
fpy.types.FW_SERIALIZE_TRUE_VALUE = _const_int(
"FW_SERIALIZE_TRUE_VALUE", DEFAULT_FW_SERIALIZE_TRUE_VALUE
)
fpy.types.FW_SERIALIZE_FALSE_VALUE = _const_int(
"FW_SERIALIZE_FALSE_VALUE", DEFAULT_FW_SERIALIZE_FALSE_VALUE
)

# Make copies of the scopes since we'll mutate them during compilation
# (e.g., adding user-defined functions to callable_scope, variables to values_scope)
# if we don't make copies, then the lru cache will return the modified versions, causing
Expand Down
12 changes: 9 additions & 3 deletions src/fpy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,15 @@

COMPILER_MAX_STRING_SIZE = 128

# FPP wire-format constants for boolean serialization
FW_SERIALIZE_TRUE_VALUE = 0xFF
FW_SERIALIZE_FALSE_VALUE = 0x00
# FPP wire-format constants for boolean serialization.
# The live FW_SERIALIZE_* values may be overridden from the dictionary at
# compile time (see get_base_compile_state); the DEFAULT_* values are the
# framework fallbacks used when the dictionary does not define them.
DEFAULT_FW_SERIALIZE_TRUE_VALUE = 0xFF
DEFAULT_FW_SERIALIZE_FALSE_VALUE = 0x00

FW_SERIALIZE_TRUE_VALUE = DEFAULT_FW_SERIALIZE_TRUE_VALUE
FW_SERIALIZE_FALSE_VALUE = DEFAULT_FW_SERIALIZE_FALSE_VALUE


class TypeKind(str, Enum):
Expand Down
93 changes: 93 additions & 0 deletions test/fpy/test_compiler_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import pytest

import fpy.error
import fpy.types

Check notice

Code scanning / CodeQL

Module is imported with 'import' and 'import from' Note test

Module 'fpy.types' is imported with both 'import' and 'import from'.
from fpy.compiler import (
text_to_ast,
analyze_ast,
Expand Down Expand Up @@ -275,6 +276,98 @@
_clear_caches()


# ============================================================================
# FW_SERIALIZE_TRUE_VALUE / FW_SERIALIZE_FALSE_VALUE loading tests
# ============================================================================


def fw_serialize_constant(name: str, value: int) -> dict:
"""Build a dictionary constant descriptor for a FW_SERIALIZE_* value."""
return {
"kind": "constant",
"qualifiedName": name,
"type": {"name": "U64", "kind": "integer", "size": 64, "signed": False},
"value": value,
"annotation": f"Custom {name} for testing",
}


def test_load_fw_serialize_from_default_dictionary():
"""Test that boolean wire-format values are loaded from the standard dictionary."""
_clear_caches()
from fpy.types import BOOL, FpyValue

get_base_compile_state(DEFAULT_DICTIONARY, {})

# The RefTopologyDictionary.json has FW_SERIALIZE_TRUE_VALUE=255, FALSE=0
assert fpy.types.FW_SERIALIZE_TRUE_VALUE == 0xFF
assert fpy.types.FW_SERIALIZE_FALSE_VALUE == 0x00
assert FpyValue(BOOL, True).serialize() == b"\xff"
assert FpyValue(BOOL, False).serialize() == b"\x00"


def test_custom_fw_serialize_values():
"""Test that custom boolean wire-format values from the dictionary are used."""
_clear_caches()
from fpy.types import (
BOOL,
DEFAULT_FW_SERIALIZE_FALSE_VALUE,
DEFAULT_FW_SERIALIZE_TRUE_VALUE,
FpyValue,
)

dict_path = create_test_dictionary(
[
fw_serialize_constant("FW_SERIALIZE_TRUE_VALUE", 1),
fw_serialize_constant("FW_SERIALIZE_FALSE_VALUE", 2),
]
)

try:
get_base_compile_state(dict_path, {})
assert fpy.types.FW_SERIALIZE_TRUE_VALUE == 1
assert fpy.types.FW_SERIALIZE_FALSE_VALUE == 2
# Booleans now serialize using the dictionary-provided wire values.
assert FpyValue(BOOL, True).serialize() == b"\x01"
assert FpyValue(BOOL, False).serialize() == b"\x02"
finally:
# Restore the framework defaults so we don't leak into other tests.
fpy.types.FW_SERIALIZE_TRUE_VALUE = DEFAULT_FW_SERIALIZE_TRUE_VALUE
fpy.types.FW_SERIALIZE_FALSE_VALUE = DEFAULT_FW_SERIALIZE_FALSE_VALUE
Path(dict_path).unlink()
_clear_caches()


def test_missing_fw_serialize_use_defaults():
"""Test that missing FW_SERIALIZE constants fall back to framework defaults."""
_clear_caches()
from fpy.types import (
DEFAULT_FW_SERIALIZE_FALSE_VALUE,
DEFAULT_FW_SERIALIZE_TRUE_VALUE,
)

dict_path = create_test_dictionary([])

# Remove the FW_SERIALIZE constants from the dictionary.
with open(dict_path, "r") as f:
dict_json = json.load(f)
dict_json["constants"] = [
c
for c in dict_json.get("constants", [])
if not c.get("qualifiedName", "").startswith("FW_SERIALIZE_")
]
with open(dict_path, "w") as f:
json.dump(dict_json, f)

try:
get_base_compile_state(dict_path, {})
assert fpy.types.FW_SERIALIZE_TRUE_VALUE == DEFAULT_FW_SERIALIZE_TRUE_VALUE
assert fpy.types.FW_SERIALIZE_FALSE_VALUE == DEFAULT_FW_SERIALIZE_FALSE_VALUE
finally:
Path(dict_path).unlink()
_clear_caches()


# ============================================================================
# TimeBase dictionary loading tests
# ============================================================================
Expand Down
Loading