pattern.lisp 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. (import core/prelude (defun defmacro with case and or if let loop for
  2. symbol->string n nth list? list symbol? type gensym eq? else const-val slice
  3. = /= > <= .> .<! + -))
  4. (import core/string string)
  5. (import compiler/nodes ())
  6. (defun metavar? (x)
  7. "Determine whether X is a metavar."
  8. :hidden
  9. (with (str (symbol->string x))
  10. (and (= (.> x :var) nil) (> (n str) 1) (= (string/char-at str 1) "?"))))
  11. (defun genvar? (x)
  12. "Determine whether X is a gensym var."
  13. :hidden
  14. (with (str (symbol->string x))
  15. (and (= (.> x :var) nil) (> (n str) 1) (= (string/char-at str 1) "%"))))
  16. (defun fullvar? (x)
  17. "Determine whether X is a fully qualified variable."
  18. :hidden
  19. (with (str (symbol->string x))
  20. (and (= (.> x :var) nil) (> (n str) 1) (= (string/char-at str 1) "$"))))
  21. (defun pattern# (ptrn)
  22. "Quote a search/replacement pattern."
  23. :hidden
  24. (case (type ptrn)
  25. ["string" ptrn]
  26. ["number" ptrn]
  27. ["key" ptrn]
  28. ["symbol"
  29. (case ptrn
  30. [metavar? (list `unquote (list `quote ptrn))]
  31. [genvar? (list `unquote (list `quote ptrn))]
  32. [fullvar? (list `unquote (list `quote ptrn))]
  33. [_ ptrn])]
  34. ["list" ptrn
  35. (for i 1 (n ptrn) 1 (.<! ptrn i (pattern# (nth ptrn i))))
  36. ptrn]))
  37. (defmacro pattern (ptrn)
  38. "Quote the provided pattern PTRN, suitable for matching with i
  39. [[matches?]].
  40. This provides several \"magic\" symbol prefixes to aid matching:
  41. - `?` marks a metavar, and will be captured. If the second character
  42. is `&`, then this will capture zero or more values.
  43. - `%` marks a genvar, which will result in a randomly generated
  44. symbol being used in substitutions.
  45. - `$` marks a fullvar, where one can provide the full name to a
  46. variable. Use of this is discouraged and should only be used if you
  47. really need to detect hidden symbols."
  48. (list `syntax-quote (pattern# ptrn)))
  49. (defun match-impl (p e out)
  50. :hidden
  51. (or (= p e)
  52. (let [(ty-p (type p))
  53. (ty-e (type e))]
  54. (cond
  55. ;; Capture metavars
  56. [(and (= ty-p "symbol") (metavar? p))
  57. (.<! out (string/sub (symbol->string p) 2) e)
  58. true]
  59. ;; We're now onto normal matching, so let's make sure we're the
  60. ;; same type.
  61. [(/= ty-p ty-e) false]
  62. ;; If we have a variable, check it's the same. Otherwise check
  63. ;; for contents equality.
  64. [(= ty-p "symbol")
  65. (let [(var-p (symbol->var p))
  66. (var-e (symbol->var e))]
  67. (cond
  68. [(fullvar? p)
  69. (and (/= var-e nil) (= (string/sub (symbol->string p) 2) (.> var-e :full-name)))]
  70. [(= var-p nil) (eq? p e)]
  71. [else (= var-p var-e)]))]
  72. [(= ty-p "string") (= (const-val p) (const-val e))]
  73. [(= ty-p "number") (= (const-val p) (const-val e))]
  74. [(= ty-p "key") (eq? p e)]
  75. [(= ty-p "list")
  76. (let [(np (n p))
  77. (ne (n e))]
  78. (and (<= np ne)
  79. (loop [(ip 1)
  80. (ie 1)]
  81. [(or (> ip np) (> ie ne)) true]
  82. (with (ptrn (nth p ip))
  83. (if (and (symbol? ptrn) (metavar? ptrn) (string/starts-with? (symbol->string ptrn) "?&"))
  84. (with (end (+ ie (- ne np)))
  85. (.<! out (string/sub (symbol->string ptrn) 3) (slice e ie end))
  86. (recur (+ ip 1) (+ end 1)))
  87. (and (match-impl ptrn (nth e ie) out) (recur (+ ip 1) (+ ie 1))))))))]))))
  88. (defun match (ptrn expr)
  89. "Determine whether EXPR matches the provided pattern PTRN, returning
  90. nil or a lookup of capture names to captured expressions."
  91. (with (out {})
  92. (if (match-impl ptrn expr out) out nil)))
  93. (defun matches? (ptrn expr)
  94. "Determine whether EXPR matches the provided pattern PTRN."
  95. (if (match-impl ptrn expr {}) true false))
  96. (defmacro matcher (ptrn)
  97. "Create a matcher for the given pattern literal PTRN.
  98. This is intended for views [[case]] expressions."
  99. (with (expr (gensym))
  100. `(lambda (,expr) (match (pattern ,ptrn) ,expr))))
  101. (defun match-always (ptrn expr)
  102. (with (out {})
  103. (match-impl ptrn expr)
  104. out))