forked from karaxnim/karax
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pattern.nim
123 lines (107 loc) · 3.47 KB
/
pattern.nim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# Copyright (C) 2012-2018 Dominik Picheta
# MIT License - Look at license.txt for details.
import std/[parseutils, tables]
type
NodeType* = enum
NodeText, NodeField
Node* = object
typ*: NodeType
text*: string
Pattern* = seq[Node]
#/show/{id}
proc parsePattern*(pattern: string): Pattern =
result = @[]
template addNode(result: var Pattern, theT: NodeType, theText: string) =
var newNode: Node
newNode.typ = theT
newNode.text = theText
result.add(newNode)
template `{}`(s: string, i: int): char =
if i >= len(s):
'\0'
else:
s[i]
var i = 0
var text = ""
while i < pattern.len:
case pattern[i]
of '{':
# Add the stored text.
if text != "":
result.addNode(NodeText, text)
text = ""
# Parse named parameter.
inc i # Skip "{"
var nparam = ""
i += pattern.parseUntil(nparam, {'}', '/'}, i)
if pattern[i] != '}':
raise newException(ValueError, "This charactre must be `}`!")
result.addNode(NodeField, nparam)
inc i # skip "}"
of '\\':
inc i # Skip \
if pattern[i] notin {'{', '\\'}:
raise newException(ValueError,
"This character does not require escaping: " & pattern[i])
text.add(pattern{i})
inc i # Skip ``pattern[i]``
else:
text.add(pattern{i})
inc i
if text != "":
result.addNode(NodeText, text)
proc findNextText(pattern: Pattern, i: int, toNode: var Node): bool =
## Finds the next NodeText in the pattern, starts looking from ``i``.
result = false
for n in i..pattern.len()-1:
if pattern[n].typ == NodeText:
toNode = pattern[n]
return true
proc check(n: Node, s: string, i: int): bool =
let cutTo = (n.text.len-1)+i
if cutTo > s.len-1:
return false
return s.substr(i, cutTo) == n.text
proc match*(pattern: Pattern, s: string):
tuple[matched: bool, params: Table[string, string]] =
var i = 0 # Location in ``s``.
result.matched = true
result.params = initTable[string, string]()
for ncount, node in pattern:
case node.typ
of NodeText:
if check(node, s, i):
inc(i, node.text.len) # Skip over this
else:
# No match.
result.matched = false
return
of NodeField:
var nextTxtNode: Node
var stopChar = '/'
if findNextText(pattern, ncount, nextTxtNode):
stopChar = nextTxtNode.text[0]
var matchNamed = ""
i += s.parseUntil(matchNamed, stopChar, i)
result.params[node.text] = matchNamed
if matchNamed == "":
result.matched = false
return
if s.len != i:
result.matched = false
when isMainModule:
let f = parsePattern("/show/{id}/test/{show}")
doAssert match(f, "/show/12/test/hallo").matched
doAssert match(f, "/show/2131726/test/jjjuuwąąss").matched
doAssert(not match(f, "/").matched)
doAssert(not match(f, "/show//test//").matched)
doAssert(not match(f, "/show/asd/asd/test/jjj/").matched)
doAssert(match(f, "/show/@łę¶ŧ←/test/asd/").params["id"] == "@łę¶ŧ←")
# let f2 = parsePattern("/test42/somefile.?@ext?/?")
# doAssert(match(f2, "/test42/somefile/").params["ext"] == "")
# doAssert(match(f2, "/test42/somefile.txt").params["ext"] == "txt")
# doAssert(match(f2, "/test42/somefile.txt/").params["ext"] == "txt")
# let f3 = parsePattern(r"/test32/\@\\\??")
# doAssert(match(f3, r"/test32/@\").matched)
# doAssert(not match(f3, r"/test32/@\\").matched)
# doAssert(match(f3, r"/test32/@\?").matched)