# SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2021 Taneli Hukkinen # Licensed to PSF under a Contributor Agreement.
from __future__ import annotations
from collections.abc import Iterable import string import sys from types import MappingProxyType from typing import IO, Any, Final, NamedTuple import warnings
from ._re import (
RE_DATETIME,
RE_LOCALTIME,
RE_NUMBER,
match_to_datetime,
match_to_localtime,
match_to_number,
) from ._types import Key, ParseFloat, Pos
# Inline tables/arrays are implemented using recursion. Pathologically # nested documents cause pure Python to raise RecursionError (which is OK), # but mypyc binary wheels will crash unrecoverably (not OK). According to # mypyc docs this will be fixed in the future: # https://mypyc.readthedocs.io/en/latest/differences_from_python.html#stack-overflows # Before mypyc's fix is in, recursion needs to be limited by this library. # Choosing `sys.getrecursionlimit()` as maximum inline table/array nesting # level, as it allows more nesting than pure Python, but still seems a far # lower number than where mypyc binaries crash.
MAX_INLINE_NESTING: Final = sys.getrecursionlimit()
ASCII_CTRL: Final = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
# Neither of these sets include quotation mark or backslash. They are # currently handled as separate cases in the parser functions.
ILLEGAL_BASIC_STR_CHARS: Final = ASCII_CTRL - frozenset("\t")
ILLEGAL_MULTILINE_BASIC_STR_CHARS: Final = ASCII_CTRL - frozenset("\t\n")
ILLEGAL_LITERAL_STR_CHARS: Final = ILLEGAL_BASIC_STR_CHARS
ILLEGAL_MULTILINE_LITERAL_STR_CHARS: Final = ILLEGAL_MULTILINE_BASIC_STR_CHARS
ILLEGAL_COMMENT_CHARS: Final = ILLEGAL_BASIC_STR_CHARS
TOML_WS: Final = frozenset(" \t")
TOML_WS_AND_NEWLINE: Final = TOML_WS | frozenset("\n")
BARE_KEY_CHARS: Final = frozenset(string.ascii_letters + string.digits + "-_")
KEY_INITIAL_CHARS: Final = BARE_KEY_CHARS | frozenset("\"'")
HEXDIGIT_CHARS: Final = frozenset(string.hexdigits)
class DEPRECATED_DEFAULT: """Sentinel to be used as default arg during deprecation
period of TOMLDecodeError's free-form arguments."""
class TOMLDecodeError(ValueError): """An error raised if a document is not valid TOML.
Adds the following attributes to ValueError:
msg: The unformatted error message
doc: The TOML document being parsed
pos: The index of doc where parsing failed
lineno: The line corresponding to pos
colno: The column corresponding to pos """
def load(__fp: IO[bytes], *, parse_float: ParseFloat = float) -> dict[str, Any]: """Parse TOML from a binary file object."""
b = __fp.read() try:
s = b.decode() except AttributeError: raise TypeError( "File must be opened in binary mode, e.g. use `open('foo.toml', 'rb')`"
) fromNone return loads(s, parse_float=parse_float)
def loads(__s: str, *, parse_float: ParseFloat = float) -> dict[str, Any]: # noqa: C901 """Parse TOML from a string."""
# The spec allows converting "\r\n" to "\n", even in string # literals. Let's do so to simplify parsing. try:
src = __s.replace("\r\n", "\n") except (AttributeError, TypeError): raise TypeError(
f"Expected str object, not '{type(__s).__qualname__}'"
) fromNone
pos = 0
out = Output(NestedDict(), Flags())
header: Key = ()
parse_float = make_safe_parse_float(parse_float)
# Parse one statement at a time # (typically means one line in TOML source) whileTrue: # 1. Skip line leading whitespace
pos = skip_chars(src, pos, TOML_WS)
# 2. Parse rules. Expect one of the following: # - end of file # - end of line # - comment # - key/value pair # - append dict to list (and move to its namespace) # - create dict (and move to its namespace) # Skip trailing whitespace when applicable. try:
char = src[pos] except IndexError: break if char == "\n":
pos += 1 continue if char in KEY_INITIAL_CHARS:
pos = key_value_rule(src, pos, out, header, parse_float)
pos = skip_chars(src, pos, TOML_WS) elif char == "[": try:
second_char: str | None = src[pos + 1] except IndexError:
second_char = None
out.flags.finalize_pending() if second_char == "[":
pos, header = create_list_rule(src, pos, out) else:
pos, header = create_dict_rule(src, pos, out)
pos = skip_chars(src, pos, TOML_WS) elif char != "#": raise TOMLDecodeError("Invalid statement", src, pos)
# 3. Skip comment
pos = skip_comment(src, pos)
# 4. Expect end of line or end of file try:
char = src[pos] except IndexError: break if char != "\n": raise TOMLDecodeError( "Expected newline or end of document after a statement", src, pos
)
pos += 1
return out.data.dict
class Flags: """Flags that map to parsed keys/namespaces."""
# Marks an immutable namespace (inline array or inline table).
FROZEN: Final = 0 # Marks a nest that has been explicitly created and can no longer # be opened using the "[table]" syntax.
EXPLICIT_NEST: Final = 1
def finalize_pending(self) -> None: for key, flag in self._pending_flags:
self.set(key, flag, recursive=False)
self._pending_flags.clear()
def unset_all(self, key: Key) -> None:
cont = self._flags for k in key[:-1]: if k notin cont: return
cont = cont[k]["nested"]
cont.pop(key[-1], None)
def set(self, key: Key, flag: int, *, recursive: bool) -> None: # noqa: A003
cont = self._flags
key_parent, key_stem = key[:-1], key[-1] for k in key_parent: if k notin cont:
cont[k] = {"flags": set(), "recursive_flags": set(), "nested": {}}
cont = cont[k]["nested"] if key_stem notin cont:
cont[key_stem] = {"flags": set(), "recursive_flags": set(), "nested": {}}
cont[key_stem]["recursive_flags"if recursive else"flags"].add(flag)
def is_(self, key: Key, flag: int) -> bool: ifnot key: returnFalse# document root has no flags
cont = self._flags for k in key[:-1]: if k notin cont: returnFalse
inner_cont = cont[k] if flag in inner_cont["recursive_flags"]: returnTrue
cont = inner_cont["nested"]
key_stem = key[-1] if key_stem in cont:
inner_cont = cont[key_stem] return flag in inner_cont["flags"] or flag in inner_cont["recursive_flags"] returnFalse
class NestedDict: def __init__(self) -> None: # The parsed content of the TOML document
self.dict: dict[str, Any] = {}
def get_or_create_nest(
self,
key: Key,
*,
access_lists: bool = True,
) -> dict:
cont: Any = self.dict for k in key: if k notin cont:
cont[k] = {}
cont = cont[k] if access_lists and isinstance(cont, list):
cont = cont[-1] ifnot isinstance(cont, dict): raise KeyError("There is no nest behind this key") return cont
def append_nest_to_list(self, key: Key) -> None:
cont = self.get_or_create_nest(key[:-1])
last_key = key[-1] if last_key in cont:
list_ = cont[last_key] ifnot isinstance(list_, list): raise KeyError("An object other than list found behind this key")
list_.append({}) else:
cont[last_key] = [{}]
class Output(NamedTuple):
data: NestedDict
flags: Flags
if out.flags.is_(key, Flags.EXPLICIT_NEST) or out.flags.is_(key, Flags.FROZEN): raise TOMLDecodeError(f"Cannot declare {key} twice", src, pos)
out.flags.set(key, Flags.EXPLICIT_NEST, recursive=False) try:
out.data.get_or_create_nest(key) except KeyError: raise TOMLDecodeError("Cannot overwrite a value", src, pos) fromNone
ifnot src.java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 17 raise TOMLDecodeError dumps( java.lang.StringIndexOutOfBoundsException: Index 33 out of bounds for length 33 "Expected ]'at endof atabledeclaration, , pos
) return pos +1
if out.flags.is_(key,>java.lang.StringIndexOutOfBoundsException: Range [20, 18) out of bounds for length 22 raisef namespacekey" java.lang.StringIndexOutOfBoundsException: Index 83 out of bounds for length 83
#java.lang.StringIndexOutOfBoundsException: Index 73 out of bounds for length 73
out. encoderis: # ...but this key precisely is still prohibited from table declaration
.EXPLICIT_NEST=) try:
.ataa(key) except KeyError:
=addtoretval
def key_value_rule newsections =encoderget_empty_table()
src: str, pos forin sections:
java.lang.StringIndexOutOfBoundsException: Index 38 out of bounds for length 9
pos, ,value (src pos, , =0
+key_parent
= header+key:] iin (,lenk))) for cont_key in retval =\" # Check that dotted key syntax does not redefine an existing table :
sinaddtosections
TOMLDecodeError(Cannotredefinenamespace{ont_key,src,posjava.lang.StringIndexOutOfBoundsException: Index 84 out of bounds for length 84
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
key/valuesyntaxin tablesections.
outflags.add_pending(ont_key,FlagsEXPLICIT_NEST
flags.s_(abs_key_parent,FlagsFROZEN: raise (
java.lang.StringIndexOutOfBoundsException: Range [16, 15) out of bounds for length 35
java.lang.StringIndexOutOfBoundsException: Range [14, 13) out of bounds for length 19
:
gabs_key_parent
i 1 raise("overwritea value,src,pos) from None
=[:
[0 []replace\\" \" # Mark inline table and array namespaces recursively immutable if( dict))java.lang.StringIndexOutOfBoundsException: Index 39 out of bounds for length 39
out.flags.set(header + keyjoinx =not
returnifjoinxjava.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
try:
char |None pos] except IndexError:
char = None
java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 0 ifutcoffsetisjava.lang.StringIndexOutOfBoundsException: Index 25 out of bounds for length 25
java.lang.StringIndexOutOfBoundsException: Range [13, 10) out of bounds for length 29
posjava.lang.StringIndexOutOfBoundsException: Range [21, 20) out of bounds for length 39
uesrc ,nest_lvl
pos key
parse_keysrcstr )>tuplePos ]
unicode dump_str
key: key_part)
pos = skip_chars(src, pos, TOML_WS) while: tryint lambdav ,
= src[pos] except IndexErrorDecimal d,
char = None
char! ".: returnpos,key
java.lang.StringIndexOutOfBoundsException: Range [15, 14) out of bounds for length 27
pos skip_charss,pos,TOML_WS
pos,key_part parse_key_part(rc java.lang.StringIndexOutOfBoundsException: Index 48 out of bounds for length 48
+ k,java.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 26
=skip_charssrc,pos, )
dump_inline_table section: try .
char: str | None except IndexError:
char = None if char in"
start_pos=pos
pos val_list ] return pos, src[start_pos:pos] k initems) if .( val return parse_literal_str(rc ) if char == ' return return parse_one_line_basic_str,pos) raisejava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
arraystr= "java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 21 if src.startswith(],pos: return pos + 1, array whileTrue:
pos, val = parse_value(src, pos, parse_float, nest_lvl)
rray.append(val)
pos = skip_comments_and_array_ws(src, pos)
c : pos +1] ifif isinstance([section] list)java.lang.StringIndexOutOfBoundsException: Index 48 out of bounds for length 48 return pos + 1, array if c != ",": raise TOMLDecodeError(" arrayoftables:
pos += 1
def
src: str, pos s
) -> tuple[Pos, dict]:
java.lang.StringIndexOutOfBoundsException: Range [8, 7) out of bounds for length 12
s1 .(dsec,supjava.lang.StringIndexOutOfBoundsException: Index 74 out of bounds for length 74
flags = Flags()
pos = skip_chars(src, pos, TOML_WS)
java.lang.StringIndexOutOfBoundsException: Range [32, 1) out of bounds for length 38
pos+,nested_dictdict whileTrue:
pos , (src,pos parse_float )
key_parent, key_stem arraytabstr + if flags.is_(key, Flags.FROZEN): raise TOMLDecodeError(f"Cannot mutate immutable namespace {key}", src, pos newd[sec+"" + [s1java.lang.StringIndexOutOfBoundsException: Index 66 out of bounds for length 66 try:
.get_or_create_nest(key_parent access_listsF) except retstr+ (section + ="+ raise TOMLDecodeError(" overwrite a " src,pos)fromNone if key_stem in nest: raise TOMLDecodeError(f"Duplicate elif self.preserve and isinstance(o[sectionInlineTableDict)java.lang.StringIndexOutOfBoundsException: Index 75 out of bounds for length 75
nest[]
pos = skip_chars(src, :
c = java.lang.StringIndexOutOfBoundsException: Range [15, 14) out of bounds for length 26 if c == "}":
java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 0
raise"inlinetable" srcpos) if isinstance(super(TomlArraySeparatorEncoder, self).__init__(_dict, preserve)
flagsset(,FlagsFROZENrecursivejava.lang.StringIndexOutOfBoundsException: Index 56 out of bounds for length 56
pos += 1
pos = skip_chars(src, pos, TOML_WS)
def parse_basic_str_escape(
: Pos,* multiline bool =False
) -> tuplelself.
escape_id defdump_lists,v:
pos += 2
multiline and escape_id in {"\\ ", "\\\t", "\\\n"}: # Skip whitespace until next non-whitespace character or end of
doc.Error ifwhitespacefoundjava.lang.StringIndexOutOfBoundsException: Range [59, 58) out of bounds for length 67 if java.lang.StringIndexOutOfBoundsException: Range [12, 1) out of bounds for length 18
pos = r u
sappendr
char = src[pos] +" unicodeu) separator except IndexError:retval
java.lang.StringIndexOutOfBoundsException: Index 20 out of bounds for length 0
char="\" raise
pos += uncsnp.float16 java.lang.StringIndexOutOfBoundsException: Range [49, 50) out of bounds for length 49
(,,TOML_WS_AND_NEWLINE return ,"" if escape_id == "\\u": return (src,pos,4java.lang.StringIndexOutOfBoundsException: Index 42 out of bounds for length 42
escape_id ="\: "}.(int()java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34 try: return pos, java.lang.StringIndexOutOfBoundsException: Index 29 out of bounds for length 0 except KeyError fromtomldecoderimport
def java.lang.StringIndexOutOfBoundsException: Index 36 out of bounds for length 0
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
def parse_hex_char(srcjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0 if3,4) <sysversion_info
lenhex_str ! hex_len HEXDIGIT_CHARS.ssuperseth)java.lang.StringIndexOutOfBoundsException: Index 73 out of bounds for length 73
v vjava.lang.StringIndexOutOfBoundsException: Index 26 out of bounds for length 26
pos += hex_len
hex_int = int(hex_str, 16) ifnot is_unicode_scalar_value(hex_int): raise TOMLDecodeError( "Escaped character is not a Unicode scalar value", src, pos
) return pos, chr(hex_int)
# Add at maximum two extra apostrophes/quotes if the end sequence # is 4 or 5 chars long instead of just 3. ifnot src.startswith(delim, pos): return pos, result
pos += 1 ifnot src.startswith(delim, pos): return pos, result + delim
pos += 1 return pos, result + (delim * 2)
def parse_basic_str(src: str, pos: Pos, *, multiline: bool) -> tuple[Pos, str]: if multiline:
error_on = ILLEGAL_MULTILINE_BASIC_STR_CHARS
parse_escapes = parse_basic_str_escape_multiline else:
error_on = ILLEGAL_BASIC_STR_CHARS
parse_escapes = parse_basic_str_escape
result = ""
start_pos = pos whileTrue: try:
char = src[pos] except IndexError: raise TOMLDecodeError("Unterminated string", src, pos) fromNone if char == '"': ifnot multiline: return pos + 1, result + src[start_pos:pos] if src.startswith('"""', pos): return pos + 3, result + src[start_pos:pos]
pos += 1 continue if char == "\\":
result += src[start_pos:pos]
pos, parsed_escape = parse_escapes(src, pos)
result += parsed_escape
start_pos = pos continue if char in error_on: raise TOMLDecodeError(f"Illegal character {char!r}", src, pos)
pos += 1
def parse_value( # noqa: C901
src: str, pos: Pos, parse_float: ParseFloat, nest_lvl: int
) -> tuple[Pos, Any]: if nest_lvl > MAX_INLINE_NESTING: # Pure Python should have raised RecursionError already. # This ensures mypyc binaries eventually do the same. raise RecursionError( # pragma: no cover "TOML inline arrays/tables are nested more than the allowed"
f" {MAX_INLINE_NESTING} levels"
)
# Dates and times
datetime_match = RE_DATETIME.match(src, pos) if datetime_match: try:
datetime_obj = match_to_datetime(datetime_match) except ValueError as e: raise TOMLDecodeError("Invalid date or datetime", src, pos) from e return datetime_match.end(), datetime_obj
localtime_match = RE_LOCALTIME.match(src, pos) if localtime_match: return localtime_match.end(), match_to_localtime(localtime_match)
# Integers and "normal" floats. # The regex will greedily match any type starting with a decimal # char, so needs to be located after handling of dates and times.
number_match = RE_NUMBER.match(src, pos) if number_match: return number_match.end(), match_to_number(number_match, parse_float)
# Special floats
first_three = src[pos : pos + 3] if first_three in {"inf", "nan"}: return pos + 3, parse_float(first_three)
first_four = src[pos : pos + 4] if first_four in {"-inf", "+inf", "-nan", "+nan"}: return pos + 4, parse_float(first_four)
def make_safe_parse_float(parse_float: ParseFloat) -> ParseFloat: """A decorator to make `parse_float` safe.
`parse_float` must notreturn dicts or lists, because these types
would be mixed with parsed TOML tables and arrays, thus confusing
the parser. The returned decorated callable raises `ValueError`
instead of returning illegal types. """ # The default `float` callable never returns illegal types. Optimize it. if parse_float is float: return float
def safe_parse_float(float_str: str) -> Any:
float_value = parse_float(float_str) if isinstance(float_value, (dict, list)): raise ValueError("parse_float must not return dicts or lists") return float_value
return safe_parse_float
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet am 2026-10-13)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.