llparse 0.1.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.
llparse/tire.py ADDED
@@ -0,0 +1,158 @@
1
+ from .pybuilder.main_code import Node, Edge
2
+ from typing import Optional
3
+
4
+ from dataclasses import dataclass
5
+
6
+ @dataclass
7
+ class IEdge:
8
+ # NOTE THIS SHOULD BE STRICTLY BYTES !!!
9
+ key: bytes
10
+ node: Node
11
+ noAdvance:bool
12
+ value:Optional[int] = None
13
+
14
+ def __lt__(self,object):
15
+ return self.key < object.key
16
+
17
+ # TODO RENAME TO TRIE!
18
+
19
+ class TireNode:
20
+ """Mainly Used as an Abstract Object for typing"""
21
+ def __init__(self) -> None:
22
+ return
23
+
24
+ class TireSequence(TireNode):
25
+ def __init__(self,select:bytes,child:TireNode) -> None:
26
+ self.select = select
27
+ self.child = child
28
+
29
+
30
+ class ITireSingleChild:
31
+ def __init__(self,key:int,noAdvance:bool,node:TireNode) -> None:
32
+ self.key = key
33
+ self.noAdvance = noAdvance
34
+ self.node = node
35
+
36
+ class TireEmpty(TireNode):
37
+ def __init__(self,node:Node,value:int) -> None:
38
+ self.node = node
39
+ self.value = value
40
+
41
+ class TireSingle(TireNode):
42
+ def __init__(self,children:list[ITireSingleChild],otherwise:Optional[TireEmpty] = None) -> None:
43
+ self.children = children
44
+ self.otherwise = otherwise
45
+
46
+
47
+
48
+
49
+
50
+ # TODO Retry making Tire....
51
+
52
+ class Tire:
53
+ def __init__(self,name:str) -> None:
54
+ self.name = name
55
+
56
+ def build(self,edges:list[Edge]):
57
+ if len(edges) == 0:
58
+ return None
59
+
60
+ internalEdges: list[IEdge] = []
61
+
62
+ for edge in edges:
63
+ key = str(edge.key) if isinstance(edge.key,int) else edge.key
64
+ internalEdges.append(IEdge(key.encode("utf-8") if isinstance(key,str) else key ,edge.node,edge.noAdvance,edge.value))
65
+ return self.level(internalEdges,[])
66
+
67
+ def level(self,edges:list[IEdge],path:list[bytes]):
68
+ first = edges[0].key
69
+ last = edges[-1].key
70
+
71
+ if len(edges) == 1 and len(edges[0].key) == 0:
72
+ return TireEmpty(edges[0].node,edges[0].value)
73
+
74
+ i = 0
75
+ for i in range(len(first)):
76
+ if first[i] != last[i]:
77
+ break
78
+
79
+ if i > 1:
80
+ # NOTE I think Idutny intended for these sequences
81
+ # to advance otherwise not having this would case a recursion error
82
+ # This is why first[1:i] is used and not first[0:count] like in typescript...
83
+ return self.sequence(edges,first[: i + 1 ],path)
84
+
85
+ return self.single(edges,path)
86
+
87
+ def Slice(self,edges:list[IEdge],off:int):
88
+ slice = [IEdge(edge.key[off:],edge.node,edge.noAdvance,edge.value) for edge in edges]
89
+ return sorted(slice, key = lambda k: k.key)
90
+
91
+
92
+ def sequence(self,edges:list[IEdge],prefix:bytes,path:list[bytes]):
93
+ sliced = self.Slice(edges,len(prefix))
94
+ assert not any([edge.noAdvance for edge in edges])
95
+ child = self.level(sliced,path + [prefix])
96
+ return TireSequence(prefix,child)
97
+
98
+ def single(self,edges:list[IEdge],path:list[bytes]):
99
+
100
+ if len(edges[0].key) == 0:
101
+ if len(path) == 0:
102
+ AssertionError(f'Empty root entry at "{self.name}"')
103
+ if not (len(edges) == 1 or len(edges[1].key) != 0):
104
+ err = f'Duplicate entries in "{self.name}" at: [' + (b", ".join(path).decode("utf-8")) + ']'
105
+
106
+ raise AssertionError(err)
107
+
108
+
109
+ keys : dict[int,list[IEdge]] = {}
110
+
111
+ for edge in edges:
112
+ if len(edge.key) == 0:
113
+ otherwise = TireEmpty(edge.node,edge.value)
114
+ continue
115
+
116
+ key = edge.key[0]
117
+
118
+ if keys.get(key):
119
+ keys[key].append(edge)
120
+ else:
121
+ keys[key] = [edge]
122
+
123
+
124
+ otherwise = None
125
+ children : list[ITireSingleChild] = []
126
+
127
+ for key, subEdges in keys.items():
128
+ # TODO LOG FUNCTION's ARGUMENTS TO DETERMINE WEATHER OR NOT IT'S the Problem...
129
+ # I think this maybe the problem now that I think about it...
130
+ sliced = self.Slice(subEdges, 1)
131
+
132
+
133
+ subPath = path + [chr(key).encode("utf-8")]
134
+
135
+ noAdvance = any([e.noAdvance for e in subEdges])
136
+ allSame = all([e.noAdvance == noAdvance for e in subEdges])
137
+
138
+ if not (allSame or len(subEdges) == 0):
139
+ err = f'Conflicting `.peek` and `.match` entries in "{self.name}" at: [' + (b", ".join(subPath).decode("utf-8")) + ']'
140
+ raise TypeError(err)
141
+ child = ITireSingleChild(key,noAdvance,self.level(sliced,subPath))
142
+ children.append(child)
143
+
144
+
145
+ return TireSingle(children,otherwise)
146
+
147
+
148
+
149
+
150
+ def test():
151
+ t = b"data-to-buffer"
152
+
153
+ for _ in range(2):
154
+ t = t[1:4]
155
+ print(t)
156
+
157
+ if __name__ == "__main__":
158
+ test()
llparse/trie.py ADDED
@@ -0,0 +1,165 @@
1
+ from dataclasses import dataclass
2
+ from functools import total_ordering
3
+ from typing import Optional
4
+
5
+ from .pybuilder.main_code import Edge, Node
6
+
7
+
8
+ @total_ordering
9
+ @dataclass
10
+ class IEdge:
11
+ # NOTE THIS SHOULD BE STRICTLY BYTES !!!
12
+ key: bytes
13
+ node: Node
14
+ noAdvance: bool
15
+ value: Optional[int] = None
16
+
17
+ def __lt__(self, object: "IEdge"):
18
+ return self.key < object.key
19
+
20
+
21
+
22
+ @dataclass
23
+ class TrieNode:
24
+ """Mainly Used as an Abstract Object for typing"""
25
+
26
+
27
+ @dataclass
28
+ class TrieSequence(TrieNode):
29
+ select: bytes
30
+ child: TrieNode
31
+
32
+
33
+ @dataclass
34
+ class ITrieSingleChild:
35
+ key: int
36
+ noAdvance: bool
37
+ node: TrieNode
38
+
39
+
40
+ @dataclass
41
+ class TrieEmpty(TrieNode):
42
+ node: Node
43
+ value: int
44
+
45
+
46
+ @dataclass
47
+ class TrieSingle(TrieNode):
48
+ children: list[ITrieSingleChild]
49
+ otherwise: Optional[TrieEmpty] = None
50
+
51
+
52
+ @dataclass
53
+ class Trie:
54
+ name: str
55
+
56
+ def build(self, edges: list[Edge]):
57
+ if not edges:
58
+ return None
59
+ internalEdges: list[IEdge] = []
60
+
61
+ for edge in edges:
62
+ key = str(edge.key) if isinstance(edge.key, int) else edge.key
63
+ internalEdges.append(
64
+ IEdge(
65
+ key=key.encode("utf-8") if isinstance(key, str) else key,
66
+ noAdvance=edge.noAdvance,
67
+ node=edge.node,
68
+ value=edge.value,
69
+ )
70
+ )
71
+
72
+ return self.level(internalEdges)
73
+
74
+ def level(self, edges: list[IEdge], path: list[bytes] = []):
75
+ first = edges[0].key
76
+ last = edges[-1].key
77
+ # print("level",edges, first)
78
+ if len(edges) == 1 and len(edges[0].key) == 0:
79
+ return TrieEmpty(edges[0].node, edges[0].value)
80
+
81
+ i = 0
82
+ # print(first)
83
+ for i in range(len(first)):
84
+ if first[i] != last[i]:
85
+ break
86
+
87
+ if i > 1:
88
+ # NOTE I think Indutny intended for these sequences
89
+ # to advance otherwise not having this would result in a recursion error
90
+ # This is why first[:i] is used and not first[0:count] like in typescript...
91
+ return self.sequence(edges, first[: i + 1], path)
92
+
93
+ return self.single(edges, path)
94
+
95
+ def Slice(self, edges: list[IEdge], off: int):
96
+ _slice = [
97
+ IEdge(edge.key[off:], edge.node, edge.noAdvance, edge.value)
98
+ for edge in edges
99
+ ]
100
+ return sorted(_slice, key=lambda k: k.key)
101
+
102
+ def sequence(self, edges: list[IEdge], prefix: bytes, path: list[bytes]):
103
+ sliced = self.Slice(edges, len(prefix))
104
+ assert not any([edge.noAdvance for edge in edges])
105
+ child = self.level(sliced, path + [prefix])
106
+ return TrieSequence(prefix, child)
107
+
108
+ def single(self, edges: list[IEdge], path: list[bytes]):
109
+ if not len(edges[0].key):
110
+ assert not path, f'Empty root entry at "{self.name}"'
111
+ assert not (len(edges) == 1 or len(edges[1].key) != 0), (
112
+ f'Duplicate entries in "{self.name}" at: ['
113
+ + (b", ".join(path).decode("utf-8"))
114
+ + "]"
115
+ )
116
+
117
+ keys: dict[int, list[IEdge]] = {}
118
+ otherwise = None
119
+ for edge in edges:
120
+ if not len(edge.key):
121
+ otherwise = TrieEmpty(edge.node, edge.value)
122
+ continue
123
+
124
+ key = edge.key[0]
125
+
126
+ if keys.get(key):
127
+ keys[key].append(edge)
128
+ else:
129
+ keys[key] = [edge]
130
+
131
+ children: list[ITrieSingleChild] = []
132
+
133
+ for key, subEdges in keys.items():
134
+ # TODO LOG FUNCTION's ARGUMENTS TO DETERMINE WEATHER OR NOT IT'S the Problem...
135
+ # I think this maybe the problem now that I think about it...
136
+ sliced = self.Slice(subEdges, 1)
137
+
138
+ subPath = path + [chr(key).encode("utf-8")]
139
+
140
+ noAdvance = any([e.noAdvance for e in subEdges])
141
+ allSame = all([e.noAdvance == noAdvance for e in subEdges])
142
+
143
+ if not (allSame or len(subEdges) == 0):
144
+ err = (
145
+ f'Conflicting `.peek` and `.match` entries in "{self.name}" at: ['
146
+ + (b", ".join(subPath).decode("utf-8"))
147
+ + "]"
148
+ )
149
+ raise TypeError(err)
150
+ child = ITrieSingleChild(key, noAdvance, self.level(sliced, subPath))
151
+ children.append(child)
152
+
153
+ return TrieSingle(children, otherwise)
154
+
155
+
156
+ # def test():
157
+ # t = b"data-to-buffer"
158
+
159
+ # for _ in range(2):
160
+ # t = t[1:4]
161
+ # print(t)
162
+
163
+
164
+ # if __name__ == "__main__":
165
+ # test()
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.4
2
+ Name: llparse
3
+ Version: 0.1.0
4
+ Summary: A Parody of llparse written for writing C Parsers with Python
5
+ Author-email: Vizonex <VizonexBusiness@gmail.com>
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Dynamic: license-file
10
+
11
+ # pyllparse
12
+ A python parody of the typescript library llparse.
13
+
14
+ I take no credit for the orginal work done by indutny and I was originally very nervous about making
15
+ this python library that I made public...
16
+
17
+ Links to the original library
18
+ - https://llparse.org
19
+ - https://github.com/nodejs/llparse
20
+
21
+ Unlike the typescript library all 3 of llparse's libraries were combined in this version of the code for the sake of portability...
22
+ I ended up mentioning how I did this a while back and I also had a concept for a C parser as well but I just didn't like it and I ended up using this instead but I also learned typescript as a bonus and It was alot of fun for me. I don't plan to make this into a real pypi library yet but I also didn't want to take away from the magic of the original source code that I borrowed from...
23
+
24
+ # Looking back on my dead work 2 years later
25
+ I had this idea lay somewhat dormant for 2 years and now seeing that I wanted to write a new socks5 server parser writing it in python
26
+ just made a little bit more sense to me, this way I don't have to stress over typescript or needing to relearn things I learned over 2 years ago.
27
+ I have now made this project public on pypi for anybody who wanted to dig into it and access the code
28
+ or generate your own parsers for C.
29
+
30
+ If your better at typescript. I reccommend sticking to the typescript version, no pressure. This library aims to not compete
31
+ with llparse but to act as an alternative for users who prefer python over typescript/node-js, simillar to puppeteer or pyppeteer.
32
+ Personally maybe we should make a rust crate for a rust version or at least someone will do it.
33
+
34
+
35
+ ## Warnings
36
+ - It should be safe enough to generate code and stress-test it but if something doesn't seem right to you, be sure to throw an issue and share a typescript version of something that works in typescript but not in python, this way something unintended by the typescript developers can be solved.
37
+ - Some code may look unfinished or may need further internal refactoring/polishing and with the other things I want to go out and do
38
+ such as contributions to aiohttp and it's other libraries, cyares, winloop, aiothreading, aiocallback & deprecated-params and I also have an irl Part-time Job, ideas and pull requests are welcomed without hesitation. I maintain other projects listed above to prevent myself from experiencing burnout.
39
+ - Pytest testsuite has not been made yet. If this is a concern to you, please throw an issue on github. (Pytests and new workflows soon!)
40
+ - Some code might be spaghetti-code (because It's been 2 years since I touched much of the code if not at all)
41
+ e.g. tests module should be moved away from the library into the github repo.
42
+ - There's some research portions such as tools to help generate things for cython or tools to make mirrors of llhttp's code
43
+ such as it's native c code (I was looking to see if it could be theoretically autogenerated). Feel free to use them but
44
+ do it at your own risk. They maybe incompleted or not throughly stress-tested.
45
+
46
+ ## New Features
47
+ - Throw me an issue if typescript llparse introduces something new that you want for me or another contributor to try and implement
48
+ just seeing llparse add new features is nothing but exciting to me.
49
+
50
+ - If you want a feature that typescript llparse doesn't have, be sure to try making a pull request over there as well and not just here,
51
+ there's a good chance they will appericate you for helping over there too and your helping make llhttp better by doing so. :)
52
+
53
+
54
+ # Why Did I Translate llparse to python?
55
+ - I wanted to work with a langauge I was more familiar with
56
+ - Better educate myself and others on how these great libraries like llhttp are made
57
+ - Write faster C code that could do more than just a simple split function or a regex...
58
+ - Make it easy for me or someone else to find a problem and solve it in typescript after testing it in python
59
+ - Typescript takes 2 commands to run a script with node-js it while python only takes one cutting the time required tremendously...
60
+ - The orginal project was MIT licensed.
61
+ - I wanted to write my own C Parsering tool with llhttp styled callbacks of my own using a language I was the most comfortable with using.
62
+ - I didn't like __Lemon Parser__ or __Yacc__ all that much and a good ide for handling them in Visual Studio Code with error checking to my knowlegde does not exist.
63
+ - The closest thing I got to what I wanted was a project named __NMFU__ shorthand for no memory for you and even I had problems with writing things using that library...
64
+
65
+ This was the Code that inspired me to try and make a new pyi writer branch for cython and if it wasn't for llhttp
66
+ existing as well as it's magical experience I would've never done what I did.
67
+
68
+ Unlike llparse in typescript, this version has more integrated and experimental features like automatically building the api seen in llhttp and
69
+ I've added a few other things like the dot compiler from llparse_dot and I also made a brand new cython compiler
70
+ for it making easy and simple to make pxd files to port your projects to cython
71
+
72
+
73
+
74
+ ## How to use
75
+ ```python
76
+ # The good old http_parser was borrowed from llparse.org to demonstrate this for you :)
77
+ from llparse import LLParse
78
+
79
+ p = LLParse("http_parser")
80
+ method = p.node("method")
81
+ beforeUrl = p.node("before_url")
82
+ urlSpan = p.span(p.code.span("on_url"))
83
+ url = p.node("url")
84
+ http = p.node("http")
85
+
86
+ # Add custom uint8_t property to the state
87
+ p.property("i8", "method")
88
+
89
+ # Store method inside a custom property
90
+ onMethod = p.invoke(p.code.store("method"), beforeUrl)
91
+
92
+ # Invoke custom C function
93
+ complete = p.invoke(
94
+ p.code.match("on_complete"),
95
+ {
96
+ # Restart
97
+ 0: method
98
+ },
99
+ p.error(4, "`on_complete` error"),
100
+ )
101
+
102
+ method.select(
103
+ {
104
+ "HEAD": 0,
105
+ "GET": 1,
106
+ "POST": 2,
107
+ "PUT": 3,
108
+ "DELETE": 4,
109
+ "OPTIONS": 5,
110
+ "CONNECT": 6,
111
+ "TRACE": 7,
112
+ "PATCH": 8,
113
+ },
114
+ onMethod,
115
+ ).otherwise(p.error(5, "Expected method"))
116
+
117
+ beforeUrl.match(" ", beforeUrl).otherwise(urlSpan.start(url))
118
+
119
+ url.peek(" ", urlSpan.end(http)).skipTo(url)
120
+
121
+ http.match(" HTTP/1.1\r\n\r\n", complete).otherwise(
122
+ p.error(6, "Expected HTTP/1.1 and two newlines")
123
+ )
124
+
125
+ c = p.build(method)
126
+ print(c.c)
127
+ open("http_parser.c", "w").write(c.c)
128
+ open("http_parser.h", "w").write(c.header)
129
+ ```
@@ -0,0 +1,33 @@
1
+ llparse/C_compiler.py,sha256=vwkqgkS8_6waHqxYdDBQCaIseZ1LmMckAWz-7RS4DaA,7535
2
+ llparse/__init__.py,sha256=tOPC4p5WHuWIqzcSidAt67WBEef94QTPXvuqLSWLiKM,50
3
+ llparse/compilator.py,sha256=8ytSUVd8b_r8qx3uptcQoOxMKPWocIGZmFOkZSNLjuY,36387
4
+ llparse/constants.py,sha256=Aq8Q5ASm2IUv-qGwI_DqdF6aEarotinhzEWzb2IaWyA,1182
5
+ llparse/cython_builder.py,sha256=5N2Qz9S1xqTgQJjnThWX78dLWo-MQCntZH_4YkO4G2k,11790
6
+ llparse/debug.py,sha256=DgEsV5XJCEH-7Kn7G0LUbPIMusuUq9cDxWzK3DE6u74,592
7
+ llparse/dot.py,sha256=VnFQCoa1MWtmZ97RYTTN2bMZt72g_Q5XSQoe_O_K5lo,6321
8
+ llparse/enumerator.py,sha256=JqAVamWZ3ohvhnGnanDdUD3N0GriYgoyqYj5xZSQxtk,497
9
+ llparse/frontend.py,sha256=kF1BqV7uCGIQ6rYMHd66t6TbHgmZXYbbIDZZHuNyFSI,18396
10
+ llparse/header.py,sha256=vJgCoJcfl97O6wIUa2w-0-M0yj9V0nqwK1f5ISeGKnU,2575
11
+ llparse/llparse.py,sha256=RA28QQYuy6rWh6i_7TTcX9WgwiOysmpahve0Kv6i8AY,4789
12
+ llparse/settings.py,sha256=XtpeLjKNuOEsmsHqa6ZchZOi5uC0nL_YYY2cKhfLzgA,10838
13
+ llparse/spanalloc.py,sha256=weNy9GKUfU26xswCm-eKiq3HPDWwL9NU4Qbw38HV4E4,5547
14
+ llparse/test.py,sha256=Uz27JLVV89qDYDyPljBp17hLuOGPVvi9kUqpbE4rhiE,6280
15
+ llparse/tire.py,sha256=V-uIKcZvNFnx3hRPeaPbA59wxWMbcCG4OmV8U71Kqp8,4737
16
+ llparse/trie.py,sha256=8FgN13iXBOX2StXBuMQdD7B1U4cz6_JJ2qW6M_-Nepo,4585
17
+ llparse/pybuilder/__init__.py,sha256=fQsl2Z_dRWD_xTNE7_Oox_eNrZAudc_REsdFoJ88UcU,72
18
+ llparse/pybuilder/builder.py,sha256=3rwVurIox8JdlSo_FB-2q-mDTMEA2Mh0XIPoXJPSYAQ,8799
19
+ llparse/pybuilder/loopchecker.py,sha256=E7_jfnNrxsg9xXhmxtg8TsSFntz1OPChMGL5c_Xno9Y,7335
20
+ llparse/pybuilder/main_code.py,sha256=btybTCOAfE8y36Hi-f_ZBm3YMpjXK5_RSGZXt85XWDw,16361
21
+ llparse/pybuilder/parsemap.py,sha256=3A8LQYRzYbcGYHW7QkCXHQ_4G93YMvrNhaLhqlTAflw,817
22
+ llparse/pyfront/containers.py,sha256=_dSFLOx5KRKTdz2aIqKDUQRVRj7YgN2GHQ6z7IcCncY,897
23
+ llparse/pyfront/front.py,sha256=GNTnxAZXcCNb_1aaylkCTlnFTRISNcAgUFE_Wv8PAM0,4545
24
+ llparse/pyfront/implementation.py,sha256=LSDOAVHPMLcRqtPwyIPFceRAWuAA6Egbs8XSWarJsQM,2087
25
+ llparse/pyfront/namespace.py,sha256=06SNWkt5PMS1FSkFZxSCX4ERC8-v7ihcEc6AgD1QoGM,45
26
+ llparse/pyfront/nodes.py,sha256=liyfByTM0dNvN2QjPjAcYESlCHVzKwgbpm8mcZ4OGAg,6099
27
+ llparse/pyfront/peephole.py,sha256=DzcI9UGIk0KT_HW2nbDxia_9UY1Rd8a7cYdPXLUI7So,1314
28
+ llparse/pyfront/transform.py,sha256=t50b3ZaqZhsUdzcaLdAVVWB3TsYatO5UmGEomkjxv1o,371
29
+ llparse-0.1.0.dist-info/licenses/LICENSE,sha256=U6b9Q5b3wm1f1k0LjgUIe-RPxM3MVYPQprLvn8nQ4tw,1064
30
+ llparse-0.1.0.dist-info/METADATA,sha256=3uVzult3Yhqn7jrrlcri_sshkOyB3uYOIIiZoEEBvsw,6621
31
+ llparse-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
+ llparse-0.1.0.dist-info/top_level.txt,sha256=eEVDRI2KTMgmLEBziV-L_2uXHjQhfg3-OI0R2FO48Ko,8
33
+ llparse-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Vizonex
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 @@
1
+ llparse