tiferet-ly 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. tiferet_ly-1.0.0/LICENSE +21 -0
  2. tiferet_ly-1.0.0/PKG-INFO +47 -0
  3. tiferet_ly-1.0.0/README.md +28 -0
  4. tiferet_ly-1.0.0/pyproject.toml +39 -0
  5. tiferet_ly-1.0.0/setup.cfg +4 -0
  6. tiferet_ly-1.0.0/tiferet_ly/__init__.py +6 -0
  7. tiferet_ly-1.0.0/tiferet_ly/assets/__init__.py +9 -0
  8. tiferet_ly-1.0.0/tiferet_ly/assets/error.py +12 -0
  9. tiferet_ly-1.0.0/tiferet_ly/assets/grammar.py +21 -0
  10. tiferet_ly-1.0.0/tiferet_ly/assets/reader.py +12 -0
  11. tiferet_ly-1.0.0/tiferet_ly/assets/translation.py +9 -0
  12. tiferet_ly-1.0.0/tiferet_ly/domain/__init__.py +30 -0
  13. tiferet_ly-1.0.0/tiferet_ly/domain/ast.py +53 -0
  14. tiferet_ly-1.0.0/tiferet_ly/domain/grammar.py +42 -0
  15. tiferet_ly-1.0.0/tiferet_ly/domain/lexeme.py +44 -0
  16. tiferet_ly-1.0.0/tiferet_ly/domain/production.py +68 -0
  17. tiferet_ly-1.0.0/tiferet_ly/domain/token.py +68 -0
  18. tiferet_ly-1.0.0/tiferet_ly/events/__init__.py +1 -0
  19. tiferet_ly-1.0.0/tiferet_ly/events/grammar.py +469 -0
  20. tiferet_ly-1.0.0/tiferet_ly/events/production.py +729 -0
  21. tiferet_ly-1.0.0/tiferet_ly/events/reader.py +165 -0
  22. tiferet_ly-1.0.0/tiferet_ly/events/render.py +48 -0
  23. tiferet_ly-1.0.0/tiferet_ly/events/token.py +416 -0
  24. tiferet_ly-1.0.0/tiferet_ly/interfaces/__init__.py +5 -0
  25. tiferet_ly-1.0.0/tiferet_ly/interfaces/grammar.py +87 -0
  26. tiferet_ly-1.0.0/tiferet_ly/interfaces/lexer.py +52 -0
  27. tiferet_ly-1.0.0/tiferet_ly/interfaces/parser.py +52 -0
  28. tiferet_ly-1.0.0/tiferet_ly/interfaces/production.py +155 -0
  29. tiferet_ly-1.0.0/tiferet_ly/interfaces/token.py +91 -0
  30. tiferet_ly-1.0.0/tiferet_ly/mappers/__init__.py +1 -0
  31. tiferet_ly-1.0.0/tiferet_ly/mappers/ast.py +168 -0
  32. tiferet_ly-1.0.0/tiferet_ly/mappers/core.py +98 -0
  33. tiferet_ly-1.0.0/tiferet_ly/mappers/grammar.py +101 -0
  34. tiferet_ly-1.0.0/tiferet_ly/mappers/lexeme.py +49 -0
  35. tiferet_ly-1.0.0/tiferet_ly/mappers/production.py +221 -0
  36. tiferet_ly-1.0.0/tiferet_ly/mappers/token.py +221 -0
  37. tiferet_ly-1.0.0/tiferet_ly/repos/__init__.py +5 -0
  38. tiferet_ly-1.0.0/tiferet_ly/repos/grammar.py +187 -0
  39. tiferet_ly-1.0.0/tiferet_ly/repos/production.py +410 -0
  40. tiferet_ly-1.0.0/tiferet_ly/repos/token.py +289 -0
  41. tiferet_ly-1.0.0/tiferet_ly/utils/__init__.py +1 -0
  42. tiferet_ly-1.0.0/tiferet_ly/utils/core.py +214 -0
  43. tiferet_ly-1.0.0/tiferet_ly/utils/grammar.py +216 -0
  44. tiferet_ly-1.0.0/tiferet_ly/utils/lex.py +143 -0
  45. tiferet_ly-1.0.0/tiferet_ly/utils/parse.py +275 -0
  46. tiferet_ly-1.0.0/tiferet_ly/utils/render.py +43 -0
  47. tiferet_ly-1.0.0/tiferet_ly/utils/translation.py +284 -0
  48. tiferet_ly-1.0.0/tiferet_ly.egg-info/PKG-INFO +47 -0
  49. tiferet_ly-1.0.0/tiferet_ly.egg-info/SOURCES.txt +50 -0
  50. tiferet_ly-1.0.0/tiferet_ly.egg-info/dependency_links.txt +1 -0
  51. tiferet_ly-1.0.0/tiferet_ly.egg-info/requires.txt +7 -0
  52. tiferet_ly-1.0.0/tiferet_ly.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Great Strength Systems
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: tiferet-ly
3
+ Version: 1.0.0
4
+ Summary: A Lex/Yacc wrapper built with Tiferet.
5
+ Author-email: "Andrew Shatz, Great Strength Systems" <andrew@greatstrength.me>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/greatstrength/tiferet-ly
8
+ Project-URL: Repository, https://github.com/greatstrength/tiferet-ly
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: tiferet
13
+ Requires-Dist: pydantic>=2.6
14
+ Requires-Dist: ply>=3.11
15
+ Provides-Extra: test
16
+ Requires-Dist: pytest; extra == "test"
17
+ Requires-Dist: PyYAML; extra == "test"
18
+ Dynamic: license-file
19
+
20
+ # tiferet-ly
21
+ A Lex/Yacc Wrapper built with Tiferet
22
+
23
+ ## Overview
24
+ tiferet-ly lets you describe a small language — its words and the sentence
25
+ patterns it accepts — as a declared list instead of hand-written code. Hand
26
+ tiferet-ly that declaration and get back a working reader for text written in
27
+ that language, built on Python's well-established Lex/Yacc engine
28
+ ([PLY](https://github.com/dabeaz/ply)). Adding a new keyword or sentence
29
+ pattern becomes an edit to the declaration rather than a new function buried
30
+ in a growing file, and the pieces around it can be tested without invoking
31
+ the real parsing engine at all.
32
+
33
+ ## Documentation
34
+ Declare a language's words and sentence patterns as data. Translate that
35
+ declaration into the form the Lex/Yacc engine expects. Assemble those
36
+ translated rules into a reader. Read text written in the declared language
37
+ and hand back the result.
38
+
39
+ - [Quick-Start](docs/quick-start.md) — run the checked-in calculator from
40
+ the repository root.
41
+ - [Domain Vision Statement](docs/domain-vision.md) — what tiferet-ly is for
42
+ and the value it aims to provide.
43
+ - [Core Domain Distillation](docs/core-domain-distillation.md) — the
44
+ technical breakdown of its intended vocabulary, behaviors, and design
45
+ commitments.
46
+ - [AGENTS.md](AGENTS.md) — orientation for contributors and AI agents
47
+ working in this repository.
@@ -0,0 +1,28 @@
1
+ # tiferet-ly
2
+ A Lex/Yacc Wrapper built with Tiferet
3
+
4
+ ## Overview
5
+ tiferet-ly lets you describe a small language — its words and the sentence
6
+ patterns it accepts — as a declared list instead of hand-written code. Hand
7
+ tiferet-ly that declaration and get back a working reader for text written in
8
+ that language, built on Python's well-established Lex/Yacc engine
9
+ ([PLY](https://github.com/dabeaz/ply)). Adding a new keyword or sentence
10
+ pattern becomes an edit to the declaration rather than a new function buried
11
+ in a growing file, and the pieces around it can be tested without invoking
12
+ the real parsing engine at all.
13
+
14
+ ## Documentation
15
+ Declare a language's words and sentence patterns as data. Translate that
16
+ declaration into the form the Lex/Yacc engine expects. Assemble those
17
+ translated rules into a reader. Read text written in the declared language
18
+ and hand back the result.
19
+
20
+ - [Quick-Start](docs/quick-start.md) — run the checked-in calculator from
21
+ the repository root.
22
+ - [Domain Vision Statement](docs/domain-vision.md) — what tiferet-ly is for
23
+ and the value it aims to provide.
24
+ - [Core Domain Distillation](docs/core-domain-distillation.md) — the
25
+ technical breakdown of its intended vocabulary, behaviors, and design
26
+ commitments.
27
+ - [AGENTS.md](AGENTS.md) — orientation for contributors and AI agents
28
+ working in this repository.
@@ -0,0 +1,39 @@
1
+ [project]
2
+ name = "tiferet-ly"
3
+ dynamic = ["version"]
4
+ description = "A Lex/Yacc wrapper built with Tiferet."
5
+ authors = [
6
+ { name = "Andrew Shatz, Great Strength Systems", email = "andrew@greatstrength.me" }
7
+ ]
8
+ license = { text = "MIT" }
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "tiferet",
13
+ "pydantic>=2.6",
14
+ "ply>=3.11",
15
+ ]
16
+
17
+ [project.urls]
18
+ Homepage = "https://github.com/greatstrength/tiferet-ly"
19
+ Repository = "https://github.com/greatstrength/tiferet-ly"
20
+
21
+ [project.optional-dependencies]
22
+ test = [
23
+ "pytest",
24
+ "PyYAML",
25
+ ]
26
+
27
+ [build-system]
28
+ requires = ["setuptools>=61.0", "wheel"]
29
+ build-backend = "setuptools.build_meta"
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["."]
33
+ include = ["tiferet_ly*"]
34
+
35
+ [tool.setuptools.dynamic]
36
+ version = { attr = "tiferet_ly.__version__" }
37
+
38
+ [tool.pytest.ini_options]
39
+ addopts = "--import-mode=importlib"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,6 @@
1
+ """Tiferet Ly"""
2
+
3
+ # *** constants
4
+
5
+ # ** constant: version
6
+ __version__ = '1.0.0'
@@ -0,0 +1,9 @@
1
+ """Tiferet Ly Assets"""
2
+
3
+ # *** imports
4
+
5
+ # ** app
6
+ from . import error
7
+ from . import grammar
8
+ from . import reader
9
+ from . import translation
@@ -0,0 +1,12 @@
1
+ """Tiferet Ly Error Assets"""
2
+
3
+ # *** constants (ids)
4
+
5
+ # ** constant: token_already_exists_id
6
+ TOKEN_ALREADY_EXISTS_ID = 'TOKEN_ALREADY_EXISTS'
7
+
8
+ # ** constant: token_not_found_id
9
+ TOKEN_NOT_FOUND_ID = 'TOKEN_NOT_FOUND'
10
+
11
+ # ** constant: token_action_not_supported_id
12
+ TOKEN_ACTION_NOT_SUPPORTED_ID = 'TOKEN_ACTION_NOT_SUPPORTED'
@@ -0,0 +1,21 @@
1
+ """Tiferet Ly Grammar Assets"""
2
+
3
+ # *** constants (ids)
4
+
5
+ # ** constant: grammar_already_exists_id
6
+ GRAMMAR_ALREADY_EXISTS_ID = 'GRAMMAR_ALREADY_EXISTS'
7
+
8
+ # ** constant: grammar_not_found_id
9
+ GRAMMAR_NOT_FOUND_ID = 'GRAMMAR_NOT_FOUND'
10
+
11
+ # ** constant: grammar_cycle_detected_id
12
+ GRAMMAR_CYCLE_DETECTED_ID = 'GRAMMAR_CYCLE_DETECTED'
13
+
14
+ # ** constant: grammar_start_not_found_id
15
+ GRAMMAR_START_NOT_FOUND_ID = 'GRAMMAR_START_NOT_FOUND'
16
+
17
+ # ** constant: grammar_parent_not_found_id
18
+ GRAMMAR_PARENT_NOT_FOUND_ID = 'GRAMMAR_PARENT_NOT_FOUND'
19
+
20
+ # ** constant: grammar_still_referenced_id
21
+ GRAMMAR_STILL_REFERENCED_ID = 'GRAMMAR_STILL_REFERENCED'
@@ -0,0 +1,12 @@
1
+ """Tiferet Ly Reader Assets"""
2
+
3
+ # *** constants (ids)
4
+
5
+ # ** constant: lex_error_id
6
+ LEX_ERROR_ID = 'LEX_ERROR'
7
+
8
+ # ** constant: parse_error_id
9
+ PARSE_ERROR_ID = 'PARSE_ERROR'
10
+
11
+ # ** constant: reader_build_failed_id
12
+ READER_BUILD_FAILED_ID = 'READER_BUILD_FAILED'
@@ -0,0 +1,9 @@
1
+ """Tiferet Ly Translation Error Assets"""
2
+
3
+ # *** constants (ids)
4
+
5
+ # ** constant: action_compilation_failed_id
6
+ ACTION_COMPILATION_FAILED_ID = 'ACTION_COMPILATION_FAILED'
7
+
8
+ # ** constant: rule_pattern_invalid_id
9
+ RULE_PATTERN_INVALID_ID = 'RULE_PATTERN_INVALID'
@@ -0,0 +1,30 @@
1
+ """Tiferet Ly Domain"""
2
+
3
+ # *** imports
4
+
5
+ # ** app
6
+ from .token import (
7
+ TokenRule,
8
+ SimpleTokenRule,
9
+ ComplexTokenRule,
10
+ )
11
+ from .production import (
12
+ ProductionRule,
13
+ SimpleProductionRule,
14
+ ComplexProductionRule,
15
+ )
16
+ from .grammar import Grammar
17
+ from .ast import AstNode
18
+
19
+ # *** exports
20
+
21
+ __all__ = [
22
+ 'TokenRule',
23
+ 'SimpleTokenRule',
24
+ 'ComplexTokenRule',
25
+ 'ProductionRule',
26
+ 'SimpleProductionRule',
27
+ 'ComplexProductionRule',
28
+ 'Grammar',
29
+ 'AstNode',
30
+ ]
@@ -0,0 +1,53 @@
1
+ """Tiferet Ly AST Domain Models"""
2
+
3
+ # *** imports
4
+
5
+ # ** core
6
+ from typing import Any
7
+
8
+ # ** infra
9
+ from pydantic import Field
10
+
11
+ # ** app
12
+ from tiferet.domain.core import DomainObject
13
+
14
+ # *** models
15
+
16
+ # ** model: ast_node
17
+ class AstNode(DomainObject):
18
+ '''
19
+ An optional generic node for a parse that chooses to keep structure.
20
+
21
+ The node stores a kind, children, a value, and a source span. It is
22
+ not a required parse result.
23
+ '''
24
+
25
+ # * attribute: kind
26
+ kind: str = Field(
27
+ ...,
28
+ description='The kind of generic node.',
29
+ )
30
+
31
+ # * attribute: children
32
+ children: list = Field(
33
+ default_factory=list,
34
+ description='The child nodes stored under this node.',
35
+ )
36
+
37
+ # * attribute: value
38
+ value: Any | None = Field(
39
+ default=None,
40
+ description='The optional value carried by this node.',
41
+ )
42
+
43
+ # * attribute: lineno
44
+ lineno: int | None = Field(
45
+ default=None,
46
+ description='The optional source line of this node.',
47
+ )
48
+
49
+ # * attribute: lexpos
50
+ lexpos: int | None = Field(
51
+ default=None,
52
+ description='The optional source character offset of this node.',
53
+ )
@@ -0,0 +1,42 @@
1
+ """Tiferet Ly Grammar Domain Models"""
2
+
3
+ # *** imports
4
+
5
+ # ** core
6
+ from typing import List
7
+
8
+ # ** infra
9
+ from pydantic import Field
10
+
11
+ # ** app
12
+ from tiferet.domain.core import DomainObject
13
+
14
+ # *** models
15
+
16
+ # ** model: grammar
17
+ class Grammar(DomainObject):
18
+ '''
19
+ The identity and composition of one declared grammar.
20
+
21
+ A grammar names itself, the grammars it composes, and the production
22
+ it starts from. It does not own a rule catalogue or check those facts
23
+ against any other aggregate.
24
+ '''
25
+
26
+ # * attribute: id
27
+ id: str = Field(
28
+ ...,
29
+ description='The grammar identifier.',
30
+ )
31
+
32
+ # * attribute: parent_ids
33
+ parent_ids: List[str] = Field(
34
+ default_factory=list,
35
+ description='The ordered identifiers of the grammars this grammar composes.',
36
+ )
37
+
38
+ # * attribute: start
39
+ start: str = Field(
40
+ ...,
41
+ description='The declared start-production name.',
42
+ )
@@ -0,0 +1,44 @@
1
+ """Tiferet Ly Lexeme Domain Models"""
2
+
3
+ # *** imports
4
+
5
+ # ** infra
6
+ from pydantic import Field
7
+
8
+ # ** app
9
+ from tiferet.domain.core import DomainObject
10
+
11
+ # *** models
12
+
13
+ # ** model: lexeme
14
+ class Lexeme(DomainObject):
15
+ '''
16
+ A recognized word and the source span where it was read.
17
+
18
+ The value is structural. Later AST work can reuse the same type,
19
+ text, line, and position without depending on a reader token.
20
+ '''
21
+
22
+ # * attribute: type
23
+ type: str = Field(
24
+ ...,
25
+ description='The token type name of the recognized word.',
26
+ )
27
+
28
+ # * attribute: value
29
+ value: str = Field(
30
+ ...,
31
+ description='The matched text of the recognized word.',
32
+ )
33
+
34
+ # * attribute: lineno
35
+ lineno: int = Field(
36
+ ...,
37
+ description='The source line of the match.',
38
+ )
39
+
40
+ # * attribute: lexpos
41
+ lexpos: int = Field(
42
+ ...,
43
+ description='The source character offset of the match.',
44
+ )
@@ -0,0 +1,68 @@
1
+ """Tiferet Ly Production Domain Models"""
2
+
3
+ # *** imports
4
+
5
+ # ** infra
6
+ from pydantic import Field
7
+
8
+ # ** app
9
+ from tiferet.domain.core import DomainObject
10
+
11
+ # *** models
12
+
13
+ # ** model: production_rule
14
+ class ProductionRule(DomainObject):
15
+ '''
16
+ A declared sentence rule, owned by exactly one grammar.
17
+
18
+ A repeated name is a legal alternative, not a duplicate declaration.
19
+ Specification and action source belong to the Simple and Complex variants.
20
+ '''
21
+
22
+ # * attribute: name
23
+ name: str = Field(
24
+ ...,
25
+ description='The declared production name.',
26
+ )
27
+
28
+ # * attribute: grammar_id
29
+ grammar_id: str = Field(
30
+ ...,
31
+ description='The identifier of the one grammar that owns the production.',
32
+ )
33
+
34
+ # ** model: simple_production_rule
35
+ class SimpleProductionRule(ProductionRule):
36
+ '''
37
+ A production whose declaration is a grammar specification only.
38
+
39
+ Simple productions store no action source and do not themselves
40
+ implement a translation-time pass-through.
41
+ '''
42
+
43
+ # * attribute: spec
44
+ spec: str = Field(
45
+ ...,
46
+ description='The declared grammar specification for the production.',
47
+ )
48
+
49
+ # ** model: complex_production_rule
50
+ class ComplexProductionRule(ProductionRule):
51
+ '''
52
+ A production that declares both a specification and action source.
53
+
54
+ The action is stored as source text. Compiling or executing it is
55
+ outside the domain model.
56
+ '''
57
+
58
+ # * attribute: spec
59
+ spec: str = Field(
60
+ ...,
61
+ description='The declared grammar specification for the production.',
62
+ )
63
+
64
+ # * attribute: action
65
+ action: str = Field(
66
+ ...,
67
+ description='The declared action source for the production.',
68
+ )
@@ -0,0 +1,68 @@
1
+ """Tiferet Ly Token Domain Models"""
2
+
3
+ # *** imports
4
+
5
+ # ** infra
6
+ from pydantic import Field
7
+
8
+ # ** app
9
+ from tiferet.domain.core import DomainObject
10
+
11
+ # *** models
12
+
13
+ # ** model: token_rule
14
+ class TokenRule(DomainObject):
15
+ '''
16
+ A declared word in a language, owned by exactly one grammar.
17
+
18
+ The rule stores its name and grammar membership only. Pattern and
19
+ action source belong to the Simple and Complex variants.
20
+ '''
21
+
22
+ # * attribute: name
23
+ name: str = Field(
24
+ ...,
25
+ description='The bare declared token name.',
26
+ )
27
+
28
+ # * attribute: grammar_id
29
+ grammar_id: str = Field(
30
+ ...,
31
+ description='The identifier of the one grammar that owns the rule.',
32
+ )
33
+
34
+ # ** model: simple_token_rule
35
+ class SimpleTokenRule(TokenRule):
36
+ '''
37
+ A token rule whose declaration is a pattern and nothing else.
38
+
39
+ Simple rules carry no action source. Matching behavior is the
40
+ pattern itself.
41
+ '''
42
+
43
+ # * attribute: pattern
44
+ pattern: str = Field(
45
+ ...,
46
+ description='The declared match pattern for the token.',
47
+ )
48
+
49
+ # ** model: complex_token_rule
50
+ class ComplexTokenRule(TokenRule):
51
+ '''
52
+ A token rule that declares both a pattern and action source.
53
+
54
+ The action is stored as source text. Compiling or executing it is
55
+ outside the domain model.
56
+ '''
57
+
58
+ # * attribute: pattern
59
+ pattern: str = Field(
60
+ ...,
61
+ description='The declared match pattern for the token.',
62
+ )
63
+
64
+ # * attribute: action
65
+ action: str = Field(
66
+ ...,
67
+ description='The declared action source for the token.',
68
+ )
@@ -0,0 +1 @@
1
+ """Tiferet Ly Events"""