-
Notifications
You must be signed in to change notification settings - Fork 120
/
patterns.nim
135 lines (119 loc) · 4.08 KB
/
patterns.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
124
125
126
127
128
129
130
131
132
133
134
135
import parseutils, strtabs
type
TNodeType = enum
TNodeText, TNodeField
TNode = object
typ: TNodeType
text: string
optional: bool
TPattern* = seq[TNode]
#/show/@id/?
proc parsePattern*(pattern: string): TPattern =
result = @[]
template addNode(result: var TPattern, theT: TNodeType, theText: string,
isOptional: bool): stmt =
block:
var newNode: TNode
newNode.typ = theT
newNode.text = theText
newNode.optional = isOptional
result.add(newNode)
var i = 0
var text = ""
while i < pattern.len():
case pattern[i]
of '@':
# Add the stored text.
if text != "":
result.addNode(TNodeText, text, false)
text = ""
# Parse named parameter.
inc(i) # Skip @
var nparam = ""
i += pattern.parseUntil(nparam, {'/', '?'}, i)
var optional = pattern[i] == '?'
result.addNode(TNodeField, nparam, optional)
if pattern[i] == '?': inc(i) # Only skip ?. / should not be skipped.
of '?':
var optionalChar = text[text.len-1]
setLen(text, text.len-1) # Truncate ``text``.
# Add the stored text.
if text != "":
result.addNode(TNodeText, text, false)
text = ""
# Add optional char.
inc(i) # Skip ?
result.addNode(TNodeText, $optionalChar, true)
of '\\':
inc i # Skip \
if pattern[i] notin {'?', '@', '\\'}:
raise newException(EInvalidValue,
"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(TNodeText, text, false)
proc findNextText(pattern: TPattern, i: int, toNode: var TNode): bool =
## Finds the next TNodeText in the pattern, starts looking from ``i``.
result = false
for n in i..pattern.len()-1:
if pattern[n].typ == TNodeText:
toNode = pattern[n]
return true
proc check(n: TNode, 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: TPattern, s: string): tuple[matched: bool, params: PStringTable] =
var i = 0 # Location in ``s``.
result.matched = true
result.params = {:}.newStringTable()
for ncount, node in pattern:
case node.typ
of TNodeText:
if node.optional:
if check(node, s, i):
inc(i, node.text.len) # Skip over this optional character.
else:
# If it's not there, we have nothing to do. It's optional after all.
else:
if check(node, s, i):
inc(i, node.text.len) # Skip over this
else:
# No match.
result.matched = false
return
of TNodeField:
var nextTxtNode: TNode
var stopChar = '/'
if findNextText(pattern, ncount, nextTxtNode):
stopChar = nextTxtNode.text[0]
var matchNamed = ""
i += s.parseUntil(matchNamed, stopChar, i)
if matchNamed != "":
result.params[node.text] = matchNamed
elif matchNamed == "" and not node.optional:
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(match(f, "/show/asd/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)