implica 0.3.4__py3-none-any.whl → 0.4.0__py3-none-any.whl
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.
Potentially problematic release.
This version of implica might be problematic. Click here for more details.
- implica/__init__.py +2 -1
- implica/core/__init__.py +12 -2
- implica/core/types.py +44 -0
- implica/utils/__init__.py +7 -0
- implica/utils/parsing.py +159 -0
- {implica-0.3.4.dist-info → implica-0.4.0.dist-info}/METADATA +288 -1
- {implica-0.3.4.dist-info → implica-0.4.0.dist-info}/RECORD +9 -7
- {implica-0.3.4.dist-info → implica-0.4.0.dist-info}/LICENSE +0 -0
- {implica-0.3.4.dist-info → implica-0.4.0.dist-info}/WHEEL +0 -0
implica/__init__.py
CHANGED
implica/core/__init__.py
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
from .types import BaseType, Variable, Application, var, app
|
|
1
|
+
from .types import BaseType, Variable, Application, var, app, type_from_string
|
|
2
2
|
from .combinator import Combinator, S, K
|
|
3
3
|
|
|
4
|
-
__all__ = [
|
|
4
|
+
__all__ = [
|
|
5
|
+
"BaseType",
|
|
6
|
+
"Variable",
|
|
7
|
+
"Application",
|
|
8
|
+
"var",
|
|
9
|
+
"app",
|
|
10
|
+
"type_from_string",
|
|
11
|
+
"Combinator",
|
|
12
|
+
"S",
|
|
13
|
+
"K",
|
|
14
|
+
]
|
implica/core/types.py
CHANGED
|
@@ -178,3 +178,47 @@ def app(input_type: BaseType, output_type: BaseType) -> Application:
|
|
|
178
178
|
Application: A new Application instance
|
|
179
179
|
"""
|
|
180
180
|
return Application(input_type=input_type, output_type=output_type)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def type_from_string(type_str: str) -> BaseType:
|
|
184
|
+
"""
|
|
185
|
+
Parse a string representation of a type into a BaseType instance.
|
|
186
|
+
|
|
187
|
+
Supports simple variables (e.g., "A") and implication types (e.g., "A -> B").
|
|
188
|
+
Handles nested types with parentheses (e.g., "(A -> B) -> C").
|
|
189
|
+
The arrow operator (->) is right-associative, so "A -> B -> C" is parsed as "A -> (B -> C)".
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
type_str: String representation of the type
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
BaseType: The parsed type (Variable or Application)
|
|
196
|
+
|
|
197
|
+
Raises:
|
|
198
|
+
ValueError: If the string cannot be parsed as a valid type
|
|
199
|
+
|
|
200
|
+
Examples:
|
|
201
|
+
>>> type_from_string("A")
|
|
202
|
+
Variable(name='A')
|
|
203
|
+
>>> type_from_string("A -> B")
|
|
204
|
+
Application(input_type=Variable(name='A'), output_type=Variable(name='B'))
|
|
205
|
+
>>> type_from_string("(A -> B) -> C")
|
|
206
|
+
Application(input_type=Application(...), output_type=Variable(name='C'))
|
|
207
|
+
"""
|
|
208
|
+
if not type_str or not type_str.strip():
|
|
209
|
+
raise ValueError("Type string cannot be empty")
|
|
210
|
+
|
|
211
|
+
type_str = type_str.strip()
|
|
212
|
+
|
|
213
|
+
# Tokenize the input using the utils module
|
|
214
|
+
from implica.utils import tokenize, parse_type
|
|
215
|
+
|
|
216
|
+
tokens = tokenize(type_str)
|
|
217
|
+
|
|
218
|
+
# Parse the tokens into a type using the utils module
|
|
219
|
+
result, remaining = parse_type(tokens)
|
|
220
|
+
|
|
221
|
+
if remaining:
|
|
222
|
+
raise ValueError(f"Unexpected tokens after parsing: {' '.join(remaining)}")
|
|
223
|
+
|
|
224
|
+
return result
|
implica/utils/parsing.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for parsing type strings.
|
|
3
|
+
|
|
4
|
+
This module contains helper functions for parsing and manipulating types.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List, TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from implica.core.types import BaseType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def tokenize(type_str: str) -> List[str]:
|
|
14
|
+
"""
|
|
15
|
+
Tokenize a type string into a list of tokens.
|
|
16
|
+
|
|
17
|
+
Tokens include: variable names, '(', ')', and '->'.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
type_str: The type string to tokenize
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
List[str]: List of tokens
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
ValueError: If invalid characters are found
|
|
27
|
+
"""
|
|
28
|
+
tokens = []
|
|
29
|
+
i = 0
|
|
30
|
+
while i < len(type_str):
|
|
31
|
+
char = type_str[i]
|
|
32
|
+
|
|
33
|
+
# Skip whitespace
|
|
34
|
+
if char.isspace():
|
|
35
|
+
i += 1
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
# Handle parentheses
|
|
39
|
+
if char in "()":
|
|
40
|
+
tokens.append(char)
|
|
41
|
+
i += 1
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
# Handle arrow operator
|
|
45
|
+
if char == "-":
|
|
46
|
+
if i + 1 < len(type_str) and type_str[i + 1] == ">":
|
|
47
|
+
tokens.append("->")
|
|
48
|
+
i += 2
|
|
49
|
+
continue
|
|
50
|
+
else:
|
|
51
|
+
raise ValueError(f"Invalid character '-' at position {i}")
|
|
52
|
+
|
|
53
|
+
# Handle variable names (alphanumeric and underscores)
|
|
54
|
+
if char.isalnum() or char == "_":
|
|
55
|
+
var_name = ""
|
|
56
|
+
while i < len(type_str) and (type_str[i].isalnum() or type_str[i] == "_"):
|
|
57
|
+
var_name += type_str[i]
|
|
58
|
+
i += 1
|
|
59
|
+
tokens.append(var_name)
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
# Invalid character
|
|
63
|
+
raise ValueError(f"Invalid character '{char}' at position {i}")
|
|
64
|
+
|
|
65
|
+
return tokens
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def parse_type(tokens: List[str]) -> tuple["BaseType", List[str]]:
|
|
69
|
+
"""
|
|
70
|
+
Parse a list of tokens into a type.
|
|
71
|
+
|
|
72
|
+
Uses recursive descent parsing with right-associativity for the arrow operator.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
tokens: List of tokens to parse
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
tuple[BaseType, List[str]]: Parsed type and remaining tokens
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
ValueError: If tokens cannot be parsed
|
|
82
|
+
"""
|
|
83
|
+
from implica.core.types import Application
|
|
84
|
+
|
|
85
|
+
if not tokens:
|
|
86
|
+
raise ValueError("Unexpected end of input")
|
|
87
|
+
|
|
88
|
+
# Parse the left-hand side (either a variable or a parenthesized type)
|
|
89
|
+
left, remaining = parse_primary(tokens)
|
|
90
|
+
|
|
91
|
+
# Check if we have an arrow operator
|
|
92
|
+
if remaining and remaining[0] == "->":
|
|
93
|
+
# Consume the arrow
|
|
94
|
+
remaining = remaining[1:]
|
|
95
|
+
|
|
96
|
+
# Parse the right-hand side (right-associative)
|
|
97
|
+
right, remaining = parse_type(remaining)
|
|
98
|
+
|
|
99
|
+
# Create application
|
|
100
|
+
return Application(input_type=left, output_type=right), remaining
|
|
101
|
+
|
|
102
|
+
# No arrow, just return the primary type
|
|
103
|
+
return left, remaining
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def parse_primary(tokens: List[str]) -> tuple["BaseType", List[str]]:
|
|
107
|
+
"""
|
|
108
|
+
Parse a primary type (variable or parenthesized type).
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
tokens: List of tokens to parse
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
tuple[BaseType, List[str]]: Parsed type and remaining tokens
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
ValueError: If tokens cannot be parsed
|
|
118
|
+
"""
|
|
119
|
+
from implica.core.types import Variable
|
|
120
|
+
|
|
121
|
+
if not tokens:
|
|
122
|
+
raise ValueError("Unexpected end of input")
|
|
123
|
+
|
|
124
|
+
token = tokens[0]
|
|
125
|
+
|
|
126
|
+
# Handle parenthesized type
|
|
127
|
+
if token == "(":
|
|
128
|
+
# Find matching closing parenthesis
|
|
129
|
+
depth = 1
|
|
130
|
+
i = 1
|
|
131
|
+
while i < len(tokens) and depth > 0:
|
|
132
|
+
if tokens[i] == "(":
|
|
133
|
+
depth += 1
|
|
134
|
+
elif tokens[i] == ")":
|
|
135
|
+
depth -= 1
|
|
136
|
+
i += 1
|
|
137
|
+
|
|
138
|
+
if depth != 0:
|
|
139
|
+
raise ValueError("Mismatched parentheses")
|
|
140
|
+
|
|
141
|
+
# Parse the content inside parentheses
|
|
142
|
+
inner_tokens = tokens[1 : i - 1]
|
|
143
|
+
if not inner_tokens:
|
|
144
|
+
raise ValueError("Empty parentheses")
|
|
145
|
+
|
|
146
|
+
result, inner_remaining = parse_type(inner_tokens)
|
|
147
|
+
|
|
148
|
+
if inner_remaining:
|
|
149
|
+
raise ValueError(
|
|
150
|
+
f"Unexpected tokens inside parentheses: {' '.join(inner_remaining)}"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return result, tokens[i:]
|
|
154
|
+
|
|
155
|
+
# Handle variable name
|
|
156
|
+
if token not in ["->", ")"]:
|
|
157
|
+
return Variable(name=token), tokens[1:]
|
|
158
|
+
|
|
159
|
+
raise ValueError(f"Unexpected token: {token}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: implica
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A package for working with graphs representing minimal implicational logic models
|
|
5
5
|
Author: Carlos Fernandez
|
|
6
6
|
Author-email: carlos.ferlo@outlook.com
|
|
@@ -107,6 +107,92 @@ print(complex_function) # Output: (A -> B) -> C
|
|
|
107
107
|
print(nested_function) # Output: A -> B -> C
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
#### Parsing Types from Strings
|
|
111
|
+
|
|
112
|
+
You can also create types by parsing string representations using `type_from_string()`:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
import implica as imp
|
|
116
|
+
|
|
117
|
+
# Parse simple variables
|
|
118
|
+
A = imp.type_from_string("A")
|
|
119
|
+
print(A) # Output: A
|
|
120
|
+
|
|
121
|
+
# Parse function types
|
|
122
|
+
func = imp.type_from_string("A -> B")
|
|
123
|
+
print(func) # Output: A -> B
|
|
124
|
+
|
|
125
|
+
# Parse nested types with right-associativity
|
|
126
|
+
nested = imp.type_from_string("A -> B -> C")
|
|
127
|
+
print(nested) # Output: A -> B -> C
|
|
128
|
+
# This is equivalent to: A -> (B -> C)
|
|
129
|
+
|
|
130
|
+
# Parse with explicit parentheses
|
|
131
|
+
left_assoc = imp.type_from_string("(A -> B) -> C")
|
|
132
|
+
print(left_assoc) # Output: (A -> B) -> C
|
|
133
|
+
|
|
134
|
+
# Parse complex nested types
|
|
135
|
+
complex = imp.type_from_string("((A -> B) -> C) -> (D -> E)")
|
|
136
|
+
print(complex) # Output: ((A -> B) -> C) -> D -> E
|
|
137
|
+
|
|
138
|
+
# Multi-character variable names
|
|
139
|
+
person_func = imp.type_from_string("Person -> String")
|
|
140
|
+
print(person_func) # Output: Person -> String
|
|
141
|
+
|
|
142
|
+
# Variables with numbers and underscores
|
|
143
|
+
data_type = imp.type_from_string("data_1 -> result_2")
|
|
144
|
+
print(data_type) # Output: data_1 -> result_2
|
|
145
|
+
|
|
146
|
+
# Unicode characters are supported
|
|
147
|
+
greek = imp.type_from_string("α -> β -> γ")
|
|
148
|
+
print(greek) # Output: α -> β -> γ
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Parsing rules:**
|
|
152
|
+
|
|
153
|
+
- The arrow operator `->` is **right-associative**: `A -> B -> C` is parsed as `A -> (B -> C)`
|
|
154
|
+
- Use parentheses to override associativity: `(A -> B) -> C`
|
|
155
|
+
- Variable names can contain letters, numbers, and underscores
|
|
156
|
+
- Whitespace is ignored: `A->B` and `A -> B` are equivalent
|
|
157
|
+
- Unicode characters in variable names are supported
|
|
158
|
+
|
|
159
|
+
**Error handling:**
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
import implica as imp
|
|
163
|
+
|
|
164
|
+
# Empty string raises ValueError
|
|
165
|
+
try:
|
|
166
|
+
imp.type_from_string("")
|
|
167
|
+
except ValueError as e:
|
|
168
|
+
print(f"Error: {e}") # Error: Type string cannot be empty
|
|
169
|
+
|
|
170
|
+
# Mismatched parentheses
|
|
171
|
+
try:
|
|
172
|
+
imp.type_from_string("(A -> B")
|
|
173
|
+
except ValueError as e:
|
|
174
|
+
print(f"Error: {e}") # Error: Mismatched parentheses
|
|
175
|
+
|
|
176
|
+
# Invalid characters
|
|
177
|
+
try:
|
|
178
|
+
imp.type_from_string("A @ B")
|
|
179
|
+
except ValueError as e:
|
|
180
|
+
print(f"Error: {e}") # Error: Invalid character '@' at position 2
|
|
181
|
+
|
|
182
|
+
# Missing operand
|
|
183
|
+
try:
|
|
184
|
+
imp.type_from_string("A ->")
|
|
185
|
+
except ValueError as e:
|
|
186
|
+
print(f"Error: {e}") # Error: Unexpected end of input
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Use cases:**
|
|
190
|
+
|
|
191
|
+
- Parse type expressions from configuration files or user input
|
|
192
|
+
- Create types dynamically from strings
|
|
193
|
+
- Simplify type construction with readable syntax
|
|
194
|
+
- Testing and debugging with human-readable type representations
|
|
195
|
+
|
|
110
196
|
#### Extracting Variables from Types
|
|
111
197
|
|
|
112
198
|
Every type has a `variables` property that returns a list of all variables contained in that type. This is computed recursively and may include duplicate variables if they appear multiple times:
|
|
@@ -928,6 +1014,159 @@ for n in graph.nodes():
|
|
|
928
1014
|
# A -> B -> C: 3
|
|
929
1015
|
```
|
|
930
1016
|
|
|
1017
|
+
### Example 11: Using type_from_string for Dynamic Type Creation
|
|
1018
|
+
|
|
1019
|
+
Parse types from strings for flexible, dynamic type construction:
|
|
1020
|
+
|
|
1021
|
+
```python
|
|
1022
|
+
import implica as imp
|
|
1023
|
+
|
|
1024
|
+
# Create a graph
|
|
1025
|
+
graph = imp.Graph()
|
|
1026
|
+
|
|
1027
|
+
# Define type expressions as strings (e.g., from a config file or user input)
|
|
1028
|
+
type_strings = [
|
|
1029
|
+
"Person",
|
|
1030
|
+
"String",
|
|
1031
|
+
"Int",
|
|
1032
|
+
"Person -> String", # Get name
|
|
1033
|
+
"Person -> Int", # Get age
|
|
1034
|
+
"(Person -> String) -> (Person -> Int) -> Person", # Complex transformation
|
|
1035
|
+
]
|
|
1036
|
+
|
|
1037
|
+
# Parse and add all types to the graph
|
|
1038
|
+
nodes = []
|
|
1039
|
+
for type_str in type_strings:
|
|
1040
|
+
parsed_type = imp.type_from_string(type_str)
|
|
1041
|
+
node = imp.node(parsed_type)
|
|
1042
|
+
nodes.append(node)
|
|
1043
|
+
|
|
1044
|
+
with graph.connect() as conn:
|
|
1045
|
+
conn.add_many_nodes(nodes)
|
|
1046
|
+
|
|
1047
|
+
print(f"Created graph with {graph.node_count()} nodes from string definitions")
|
|
1048
|
+
# Output: Created graph with 6 nodes from string definitions
|
|
1049
|
+
|
|
1050
|
+
# You can also build a type library from a configuration
|
|
1051
|
+
type_library = {
|
|
1052
|
+
"identity": "A -> A",
|
|
1053
|
+
"const": "A -> B -> A",
|
|
1054
|
+
"compose": "(B -> C) -> (A -> B) -> A -> C",
|
|
1055
|
+
"apply": "(A -> B) -> A -> B",
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
print("\n=== Type Library ===")
|
|
1059
|
+
for name, type_str in type_library.items():
|
|
1060
|
+
parsed = imp.type_from_string(type_str)
|
|
1061
|
+
print(f"{name:10} : {parsed}")
|
|
1062
|
+
|
|
1063
|
+
# Output:
|
|
1064
|
+
# identity : A -> A
|
|
1065
|
+
# const : A -> B -> A
|
|
1066
|
+
# compose : (B -> C) -> (A -> B) -> A -> C
|
|
1067
|
+
# apply : (A -> B) -> A -> B
|
|
1068
|
+
|
|
1069
|
+
# Validate type strings from user input
|
|
1070
|
+
def validate_and_parse_type(user_input: str) -> tuple[bool, str, object]:
|
|
1071
|
+
"""
|
|
1072
|
+
Validate and parse a type string from user input.
|
|
1073
|
+
Returns (is_valid, message, parsed_type)
|
|
1074
|
+
"""
|
|
1075
|
+
try:
|
|
1076
|
+
parsed = imp.type_from_string(user_input)
|
|
1077
|
+
return True, f"Valid type: {parsed}", parsed
|
|
1078
|
+
except ValueError as e:
|
|
1079
|
+
return False, f"Invalid type: {e}", None
|
|
1080
|
+
|
|
1081
|
+
# Test validation
|
|
1082
|
+
test_inputs = [
|
|
1083
|
+
"A -> B",
|
|
1084
|
+
"(A -> B) -> C",
|
|
1085
|
+
"A ->", # Invalid
|
|
1086
|
+
"Person -> (Address -> String)",
|
|
1087
|
+
"A @ B", # Invalid
|
|
1088
|
+
]
|
|
1089
|
+
|
|
1090
|
+
print("\n=== Type Validation ===")
|
|
1091
|
+
for test in test_inputs:
|
|
1092
|
+
is_valid, message, parsed = validate_and_parse_type(test)
|
|
1093
|
+
status = "✓" if is_valid else "✗"
|
|
1094
|
+
print(f"{status} '{test:30}' : {message}")
|
|
1095
|
+
|
|
1096
|
+
# Output:
|
|
1097
|
+
# ✓ 'A -> B' : Valid type: A -> B
|
|
1098
|
+
# ✓ '(A -> B) -> C' : Valid type: (A -> B) -> C
|
|
1099
|
+
# ✗ 'A ->' : Invalid type: Unexpected end of input
|
|
1100
|
+
# ✓ 'Person -> (Address -> String)' : Valid type: Person -> Address -> String
|
|
1101
|
+
# ✗ 'A @ B' : Invalid type: Invalid character '@' at position 2
|
|
1102
|
+
|
|
1103
|
+
# Build a type inference system
|
|
1104
|
+
def infer_result_type(func_type_str: str, input_type_str: str) -> str:
|
|
1105
|
+
"""
|
|
1106
|
+
Given a function type and an input type, infer the result type.
|
|
1107
|
+
Returns the result type as a string, or an error message.
|
|
1108
|
+
"""
|
|
1109
|
+
try:
|
|
1110
|
+
func_type = imp.type_from_string(func_type_str)
|
|
1111
|
+
input_type = imp.type_from_string(input_type_str)
|
|
1112
|
+
|
|
1113
|
+
# Check if func_type is an application
|
|
1114
|
+
if not isinstance(func_type, imp.Application):
|
|
1115
|
+
return f"Error: {func_type_str} is not a function type"
|
|
1116
|
+
|
|
1117
|
+
# Check if input matches function's input type
|
|
1118
|
+
if func_type.input_type.uid != input_type.uid:
|
|
1119
|
+
return f"Error: Type mismatch. Expected {func_type.input_type}, got {input_type}"
|
|
1120
|
+
|
|
1121
|
+
return str(func_type.output_type)
|
|
1122
|
+
except ValueError as e:
|
|
1123
|
+
return f"Error: {e}"
|
|
1124
|
+
|
|
1125
|
+
# Test type inference
|
|
1126
|
+
print("\n=== Type Inference ===")
|
|
1127
|
+
print(f"Apply 'A -> B' to 'A': {infer_result_type('A -> B', 'A')}")
|
|
1128
|
+
# Output: B
|
|
1129
|
+
|
|
1130
|
+
print(f"Apply 'Person -> String' to 'Person': {infer_result_type('Person -> String', 'Person')}")
|
|
1131
|
+
# Output: String
|
|
1132
|
+
|
|
1133
|
+
print(f"Apply 'A -> B -> C' to 'A': {infer_result_type('A -> B -> C', 'A')}")
|
|
1134
|
+
# Output: B -> C
|
|
1135
|
+
|
|
1136
|
+
print(f"Apply 'A -> B' to 'C': {infer_result_type('A -> B', 'C')}")
|
|
1137
|
+
# Output: Error: Type mismatch. Expected A, got C
|
|
1138
|
+
|
|
1139
|
+
# Parse types from a domain-specific language
|
|
1140
|
+
dsl_program = """
|
|
1141
|
+
define Input
|
|
1142
|
+
define Output
|
|
1143
|
+
define Processor = Input -> Output
|
|
1144
|
+
define Chain = Processor -> Processor -> Processor
|
|
1145
|
+
"""
|
|
1146
|
+
|
|
1147
|
+
print("\n=== Parsing DSL ===")
|
|
1148
|
+
for line in dsl_program.strip().split('\n'):
|
|
1149
|
+
line = line.strip()
|
|
1150
|
+
if line.startswith("define "):
|
|
1151
|
+
parts = line[7:].split(" = ")
|
|
1152
|
+
type_name = parts[0].strip()
|
|
1153
|
+
|
|
1154
|
+
if len(parts) == 1:
|
|
1155
|
+
# Simple type definition
|
|
1156
|
+
print(f"{type_name}: <primitive type>")
|
|
1157
|
+
else:
|
|
1158
|
+
# Complex type definition
|
|
1159
|
+
type_expr = parts[1].strip()
|
|
1160
|
+
parsed = imp.type_from_string(type_expr)
|
|
1161
|
+
print(f"{type_name}: {parsed}")
|
|
1162
|
+
|
|
1163
|
+
# Output:
|
|
1164
|
+
# Input: <primitive type>
|
|
1165
|
+
# Output: <primitive type>
|
|
1166
|
+
# Processor: Input -> Output
|
|
1167
|
+
# Chain: Processor -> Processor -> Processor
|
|
1168
|
+
```
|
|
1169
|
+
|
|
931
1170
|
## API Reference
|
|
932
1171
|
|
|
933
1172
|
### Core Module (`implica.core`)
|
|
@@ -936,6 +1175,7 @@ for n in graph.nodes():
|
|
|
936
1175
|
|
|
937
1176
|
- `var(name: str) -> Variable`: Create a type variable
|
|
938
1177
|
- `app(input_type: BaseType, output_type: BaseType) -> Application`: Create a function type
|
|
1178
|
+
- `type_from_string(type_str: str) -> BaseType`: Parse a type from a string representation
|
|
939
1179
|
- `Variable`: Atomic type variable
|
|
940
1180
|
- `name: str`: The name of the variable
|
|
941
1181
|
- `uid: str`: Unique identifier (SHA256 hash)
|
|
@@ -1010,6 +1250,53 @@ for n in graph.nodes():
|
|
|
1010
1250
|
- `TryRemoveNode(node_uid)`: Remove a node or do nothing if it doesn't exist
|
|
1011
1251
|
- `TryRemoveEdge(edge_uid)`: Remove an edge or do nothing if it doesn't exist
|
|
1012
1252
|
|
|
1253
|
+
### Utilities Module (`implica.utils`)
|
|
1254
|
+
|
|
1255
|
+
**Parsing Functions:**
|
|
1256
|
+
|
|
1257
|
+
- `tokenize(type_str: str) -> list[str]`: Tokenize a type string into tokens
|
|
1258
|
+
- Returns a list of tokens: variable names, `'('`, `')'`, and `'->'`
|
|
1259
|
+
- Raises `ValueError` if invalid characters are found
|
|
1260
|
+
- `parse_type(tokens: list[str]) -> tuple[BaseType, list[str]]`: Parse tokens into a type
|
|
1261
|
+
- Uses recursive descent parsing with right-associativity for arrows
|
|
1262
|
+
- Returns the parsed type and any remaining tokens
|
|
1263
|
+
- Raises `ValueError` if tokens cannot be parsed
|
|
1264
|
+
- `parse_primary(tokens: list[str]) -> tuple[BaseType, list[str]]`: Parse a primary type
|
|
1265
|
+
- Handles variables and parenthesized types
|
|
1266
|
+
- Returns the parsed type and remaining tokens
|
|
1267
|
+
- Raises `ValueError` if tokens cannot be parsed
|
|
1268
|
+
|
|
1269
|
+
**Note:** These functions are primarily used internally by `type_from_string()`, but are exposed for advanced use cases where you need direct control over the parsing process.
|
|
1270
|
+
|
|
1271
|
+
**Example:**
|
|
1272
|
+
|
|
1273
|
+
```python
|
|
1274
|
+
from implica.utils import tokenize, parse_type
|
|
1275
|
+
|
|
1276
|
+
# Tokenize a type string
|
|
1277
|
+
tokens = tokenize("(A -> B) -> C")
|
|
1278
|
+
print(tokens) # Output: ['(', 'A', '->', 'B', ')', '->', 'C']
|
|
1279
|
+
|
|
1280
|
+
# Parse the tokens
|
|
1281
|
+
type_result, remaining = parse_type(tokens)
|
|
1282
|
+
print(type_result) # Output: (A -> B) -> C
|
|
1283
|
+
print(remaining) # Output: []
|
|
1284
|
+
|
|
1285
|
+
# Custom parsing workflow
|
|
1286
|
+
def parse_and_validate(type_str: str) -> bool:
|
|
1287
|
+
"""Check if a string is a valid type expression."""
|
|
1288
|
+
try:
|
|
1289
|
+
tokens = tokenize(type_str)
|
|
1290
|
+
result, remaining = parse_type(tokens)
|
|
1291
|
+
return len(remaining) == 0 # Valid if no tokens remain
|
|
1292
|
+
except ValueError:
|
|
1293
|
+
return False
|
|
1294
|
+
|
|
1295
|
+
print(parse_and_validate("A -> B")) # Output: True
|
|
1296
|
+
print(parse_and_validate("A ->")) # Output: False
|
|
1297
|
+
print(parse_and_validate("(A -> B) -> C")) # Output: True
|
|
1298
|
+
```
|
|
1299
|
+
|
|
1013
1300
|
## Development
|
|
1014
1301
|
|
|
1015
1302
|
### Setup
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
implica/__init__.py,sha256=
|
|
2
|
-
implica/core/__init__.py,sha256=
|
|
1
|
+
implica/__init__.py,sha256=BD-fo6jkUvyETCLqulTeVUHrha0jPyxTPgLIMlvV-zo,910
|
|
2
|
+
implica/core/__init__.py,sha256=gwUoJDHZueECsh_X4KXp_6NX1A_tMNu-wJKXjwstzlU,268
|
|
3
3
|
implica/core/combinator.py,sha256=VOeIj_FRMI7fmuMjjbgRBP-FvExnQ6vOjCD-egt96lI,4324
|
|
4
|
-
implica/core/types.py,sha256=
|
|
4
|
+
implica/core/types.py,sha256=QQZ61JfG9CxaiTzMXfg9jA9NfxuZbG8MnEzhy38w4Pw,6647
|
|
5
5
|
implica/graph/__init__.py,sha256=Lj2bUdPZVgMoIYXF7DeMNuFBl3ftqrkAxhGq-Oj0MoQ,172
|
|
6
6
|
implica/graph/connection.py,sha256=IpyvA_J_BQqhecxN3XoQ00Io7zjwok5PGz2TKia35ZQ,13746
|
|
7
7
|
implica/graph/elements.py,sha256=ag5Gdr-5djGnKOEhoEAm3NpWTmw3gnucq1IEv2xxTB4,6015
|
|
@@ -24,7 +24,9 @@ implica/mutations/try_remove_edge.py,sha256=2aeg6f6nR49MViyZk15-ys7I5Xt0a2wXycBI
|
|
|
24
24
|
implica/mutations/try_remove_many_edges.py,sha256=pgZdqFltMaycNBfdjejYVXQZw3i2K1AJYYcsaKk7n74,1596
|
|
25
25
|
implica/mutations/try_remove_many_nodes.py,sha256=DK_BtKuEh_7zm5bzryOi1BVfmuCiQ-SVA9yIdgN-imE,1817
|
|
26
26
|
implica/mutations/try_remove_node.py,sha256=igTEnGUCd0U2_3Rr5SIOSIs4FZmYi2Y6bLk0Ae64Bsg,1837
|
|
27
|
-
implica
|
|
28
|
-
implica
|
|
29
|
-
implica-0.
|
|
30
|
-
implica-0.
|
|
27
|
+
implica/utils/__init__.py,sha256=zh2OscD5SQ3-YLgsTm3-loTExSuhRRbdZTFFVy3Fxdk,164
|
|
28
|
+
implica/utils/parsing.py,sha256=z7ofQ_tmPaGf3HTOwT1EuANJyIjMpB245mX6URBRHnw,4158
|
|
29
|
+
implica-0.4.0.dist-info/LICENSE,sha256=jDn9mmsCvjx_av9marP7DBqjfmK1gsQmgycgBRC2Eck,1073
|
|
30
|
+
implica-0.4.0.dist-info/METADATA,sha256=ZdKzwDnAJxpbSN_O9r0VMsgHnO6owsqswzghs7alS-o,38241
|
|
31
|
+
implica-0.4.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
32
|
+
implica-0.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|