mirror of
https://github.com/nim-lang/Nim.git
synced 2026-07-27 19:06:31 +00:00
142 lines
4.2 KiB
Nim
142 lines
4.2 KiB
Nim
# Copyright (C) 2012-2018 Dominik Picheta
|
|
# MIT License - Look at license.txt for details.
|
|
import parseutils, tables
|
|
type
|
|
NodeType* = enum
|
|
NodeText, NodeField
|
|
Node* = object
|
|
typ*: NodeType
|
|
text*: string
|
|
optional*: bool
|
|
|
|
Pattern* = seq[Node]
|
|
|
|
#/show/@id/?
|
|
proc parsePattern*(pattern: string): Pattern =
|
|
result = @[]
|
|
template addNode(result: var Pattern, theT: NodeType, theText: string,
|
|
isOptional: bool): typed =
|
|
block:
|
|
var newNode: Node
|
|
newNode.typ = theT
|
|
newNode.text = theText
|
|
newNode.optional = isOptional
|
|
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, false)
|
|
text = ""
|
|
# Parse named parameter.
|
|
inc(i) # Skip @
|
|
var nparam = ""
|
|
i += pattern.parseUntil(nparam, {'/', '?'}, i)
|
|
var optional = pattern{i} == '?'
|
|
result.addNode(NodeField, nparam, optional)
|
|
if pattern{i} == '?': inc(i) # Only skip ?. / should not be skipped.
|
|
of '?':
|
|
var optionalChar = text[^1]
|
|
setLen(text, text.len-1) # Truncate ``text``.
|
|
# Add the stored text.
|
|
if text != "":
|
|
result.addNode(NodeText, text, false)
|
|
text = ""
|
|
# Add optional char.
|
|
inc(i) # Skip ?
|
|
result.addNode(NodeText, $optionalChar, true)
|
|
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, false)
|
|
|
|
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 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.
|
|
discard
|
|
else:
|
|
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 == "" and not node.optional:
|
|
result.matched = false
|
|
return
|
|
|
|
if s.len != i:
|
|
result.matched = false
|
|
|
|
when true:
|
|
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)
|