├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── doc
└── intro.md
├── examples
├── date.clj
└── parser.clj
├── project.clj
├── src
└── verbal_exprejon
│ └── core.clj
└── test
└── verbal_exprejon
└── core_test.clj
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | pom.xml
5 | pom.xml.asc
6 | *.jar
7 | *.class
8 | /.lein-*
9 | /.nrepl-port
10 | .hgignore
11 | .hg/
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: clojure
2 | jdk:
3 | - oraclejdk8
4 | script:
5 | - lein test
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Guillaume Badi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
Verbal Exprejon
3 |
4 | VerbalExpressions with a lisp
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ---
14 | ## Usage
15 |
16 | ### Create a Regex
17 |
18 | ``` clojure
19 |
20 | (def url
21 | (->> (then "http")
22 | (maybe "s")
23 | (then "://")
24 | (maybe "www.")
25 | (anything-but " ")
26 | (end-of-line)))
27 |
28 | (re-matches url "http://www.google.com")
29 |
30 | ```
31 |
32 | ### Create your own midleware
33 |
34 | A middleware is a function that takes a regex as its last parameter,
35 | and output a regex.
36 |
37 | ``` clojure
38 |
39 | ; Match a `domain` url like https://domain.com
40 |
41 | (defpattern url?
42 | "Match if it is a url"
43 | [domain]
44 | (->> (then "http")
45 | (maybe "s")
46 | (then "://")
47 | (maybe "www.")
48 | (then domain)
49 | (anything-but " ")))
50 |
51 | ```
52 |
53 | ### Reuse your middlewares
54 |
55 | Match stuff like `https://www.facebook.com facebook` or `https://www.google.com google`
56 |
57 | ``` clojure
58 |
59 | (defpattern url-name
60 | "Match if it is a domain url followed by its name"
61 | [domain]
62 | (->> (url? domain)
63 | (then " ")
64 | (then domain)))
65 |
66 | (def match (matcher url-name "google"))
67 | (println (match "https://www.google.com google"))
68 |
69 | ;; -> true
70 |
71 | ```
72 |
73 | ### Examples:
74 |
75 | ``` clojure
76 |
77 | (defpattern hour-pattern
78 | "Matches an hour"
79 | [] (->> (interval [\0 \9])
80 | (times [1 2])
81 | (any-blank)
82 | (maybe (OR ["h" (->> (then "hour") (maybe "s"))]))))
83 |
84 | (defpattern minute-pattern
85 | "Matches a minute"
86 | [] (->> (interval [\0 \9])
87 | (times [1 2])
88 | (any-blank)
89 | (maybe (OR ["m" (->> (then "minute") (maybe "s"))]))))
90 |
91 | (defpattern second-pattern
92 | "Matches a second"
93 | [] (->> (interval [\0 \9])
94 | (times [1 2])
95 | (any-blank)
96 | (maybe (OR ["s" (->> (then "second") (maybe "s"))]))))
97 |
98 | (defpattern time-separator
99 | "Matches the separation between hours, minutes and seconds"
100 | [] (maybe (->> (any-blank)
101 | (maybe (OR ["and" " " ", " ":"]))
102 | (any-blank))))
103 |
104 | (defpattern time-pattern
105 | "Matches a time"
106 | [] (->> (maybe (->> (hour)
107 | (time-separator)))
108 | (maybe (->> (minute)
109 | (time-separator)))
110 | (maybe (second))))
111 |
112 | (def time? (matcher time-pattern))
113 |
114 | (defn testit
115 | [time]
116 | (time? time))
117 |
118 | (testit "12h")
119 | (testit "12h5")
120 | (testit "12h05")
121 | (testit "12h30m")
122 | (testit "12:07")
123 | (testit "12hours")
124 | (testit "12hours and 3 minutes")
125 | (testit "12:5:4")
126 | (testit "12h and 5 minutes")
127 | (testit "5 hours")
128 | (testit "12 minutes")
129 | (testit "12 minutes and 5 seconds")
130 | (testit "5s")
131 |
132 | ```
133 |
134 | ### Reference
135 |
136 | `(then "string")`:
137 |
138 | Matches the string literally
139 |
140 | `(maybe "string")`:
141 |
142 | Matches the string if any
143 |
144 | `(anything)`:
145 |
146 | Matches anything
147 |
148 | `(anything-but "string")`:
149 |
150 | Matches anything except the provided value
151 |
152 | `(one-or-more)`:
153 |
154 | Matches the previous middleware one ore more times
155 |
156 | `(zero-or-more)`:
157 |
158 | Matches the previous middleware zero or more times
159 |
160 | `(any "letters")`:
161 |
162 | Matches any letter from the provided string
163 |
164 | `(any-blank)`:
165 |
166 | Matches any blank characters including line breaks, spaces and tabs
167 |
168 | `(end-of-line)`:
169 |
170 | Matches the end of a line
171 |
172 | `(start-of-line)`:
173 |
174 | Matches the start of a line
175 |
176 | `(line-break)`:
177 |
178 | Matches a \n
179 |
180 | `(interval [characters])`:
181 |
182 | Matches the pairs ranges provided.
183 | Examples:
184 | ``` clojure
185 |
186 | ;; matches any letter between a-z and A-Z
187 | (interval \a \z \A \Z)
188 |
189 | ```
190 |
191 | `(tab)`:
192 |
193 | Matches a tabulation
194 |
195 | `(word)`:
196 |
197 | Matches a word (case insensitive)
198 |
199 | `(OR [vector])`:
200 |
201 | Matches the first matching expression in the vector
202 |
203 | `(times [start end])`:
204 |
205 | Matches the previous middleware from `start` times to `end` times
206 | Example:
207 |
208 | ``` clojure
209 | ; [0-9]{1,3}
210 |
211 | (interval \0 \9)
212 | (times [1 3])
213 |
214 | ```
215 |
216 | `(words-split-by [delimiters])`:
217 |
218 | Matches a sequence of words split by any delimiters in the provided vector
219 |
220 | `(sentence)`:
221 |
222 | Matches a sequence of words split by spaces
223 |
224 | ## License
225 |
226 | This has been done by a Clojure newbie.
227 |
228 | Copyright © 2016 Guillaume Badi
229 |
230 | Distributed under the MIT License
231 |
--------------------------------------------------------------------------------
/doc/intro.md:
--------------------------------------------------------------------------------
1 | # Introduction to verbal-exprejon
2 |
3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/)
4 |
--------------------------------------------------------------------------------
/examples/date.clj:
--------------------------------------------------------------------------------
1 | (ns verbal-exprejon.examples.date
2 | (:require [verbal-exprejon.core :refer :all]))
3 |
4 | (defpattern hour-pattern
5 | "Matches an hour"
6 | [] (->> (interval [\0 \9])
7 | (times [1 2])
8 | (any-blank)
9 | (maybe (OR ["h" (->> (then "hour") (maybe "s"))]))))
10 |
11 | (defpattern minute-pattern
12 | "Matches a minute"
13 | [] (->> (interval [\0 \9])
14 | (times [1 2])
15 | (any-blank)
16 | (maybe (OR ["m" (->> (then "minute") (maybe "s"))]))))
17 |
18 | (defpattern second-pattern
19 | "Matches a second"
20 | [] (->> (interval [\0 \9])
21 | (times [1 2])
22 | (any-blank)
23 | (maybe (OR ["s" (->> (then "second") (maybe "s"))]))))
24 |
25 | (defpattern time-separator
26 | "Matches the separation between hours, minutes and seconds"
27 | [] (maybe (->> (any-blank)
28 | (maybe (OR ["and" " " ", " ":"]))
29 | (any-blank))))
30 |
31 | (defpattern time-pattern
32 | "Matches a time"
33 | [] (->> (maybe (->> (hour)
34 | (time-separator)))
35 | (maybe (->> (minute)
36 | (time-separator)))
37 | (maybe (second))))
38 |
39 | (def time? (matcher time-pattern))
40 |
41 | (defn testit
42 | [time]
43 | (time? time))
44 |
45 | (testit "12h")
46 | (testit "12h5")
47 | (testit "12h05")
48 | (testit "12h30m")
49 | (testit "12:07")
50 | (testit "12hours")
51 | (testit "12hours and 3 minutes")
52 | (testit "12:5:4")
53 | (testit "12h and 5 minutes")
54 | (testit "5 hours")
55 | (testit "12 minutes")
56 | (testit "12 minutes and 5 seconds")
57 | (testit "5s")
58 |
--------------------------------------------------------------------------------
/examples/parser.clj:
--------------------------------------------------------------------------------
1 | ;;
2 | ;;
3 | ;; Does not work yet
4 | ;;
5 | ;;
6 | (ns vebral-exprejon.parser
7 | (:require [verbal-exprejon.core :refer :all]))
8 |
9 | (comment
10 | "
11 | # Cest beau
12 |
13 | add = (a b) => { a + b }
14 | subs = (a b) => { a - b }
15 | subs 3 4, add 1 == 0
16 |
17 | factorial = (n <= 1) => 1
18 | factorial = (n) => factorial n - 1
19 |
20 | nil? = #(% != nil)
21 |
22 | get '/factorial' #( %1 :value, factorial, render %2 )
23 |
24 | get '/factorial'
25 | ({ value <= 1000 } { render }) => { factorial value, render }
26 |
27 | google = request 'http://google.com' | puts | get 'id' | puts
28 |
29 | when google { puts 'done' }
30 |
31 | defn test = ( value ) => { println value + 1 }
32 | ")
33 |
34 | (defpattern block
35 | "Between { and }
36 | TODO:
37 | - every expression is a block"
38 | [] (->> (any-blank)
39 | (then "\\{")
40 | (any-blank)
41 | (maybe (anything))
42 | (any-blank)
43 | (then "\\}")
44 | (any-blank)))
45 |
46 | (print-pattern (block))
47 |
48 | (defpattern statements
49 | "A statement is an instruction
50 | Either an assignment, a function call etc"
51 | [] (->> (assignment)))
52 |
53 | (defpattern parameters-list
54 | "A list of parameters used to declare functions
55 | They are separated by (any-blank)
56 | TODO:
57 | - add typed parameters
58 | - define 'a list of parameters inside parens'"
59 | [] (->> (then "\\(")
60 | (words-split-by ["\\t" "\\s" "\\n" ","])
61 | (then "\\)")))
62 |
63 | (defpattern function-definition
64 | "Matches a function of type (a b) => { a + b }
65 | parameters-list blank => blank block "
66 | [] (->> (parameters-list)
67 | (any-blank)
68 | (then "=>")
69 | (any-blank)
70 | (block)))
71 |
72 | (defpattern expression
73 | "Almost anything"
74 | [] (->> (function-definition)))
75 |
76 | (defn identifier
77 | "Simple word with allowed chars"
78 | [] (word))
79 |
80 | (defpattern assignment
81 | "a = b"
82 | [] (->> (identifier)
83 | (any-blank)
84 | (then "=")
85 | (any-blank)
86 | (expression)))
87 |
88 | (let [block? (matcher block)
89 | param-list? (matcher parameters-list)
90 | function-def? (matcher function-definition)
91 | expression? (matcher expression)
92 | assignment? (matcher assignment)
93 | identifier? (matcher identifier)]
94 | (println (block? "
95 |
96 |
97 |
98 | ")))
99 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject verbal-exprejon "0.1.0-SNAPSHOT"
2 | :description "FIXME: write description"
3 | :url "http://example.com/FIXME"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 | :dependencies [[org.clojure/clojure "1.7.0"]])
7 |
--------------------------------------------------------------------------------
/src/verbal_exprejon/core.clj:
--------------------------------------------------------------------------------
1 | (ns verbal-exprejon.core
2 | (:use [clojure.string :only (join)])
3 | (:use [clojure.pprint]))
4 |
5 | (defn vex [] #"")
6 |
7 | (defn matcher
8 | "Transform a pattern into a test matching function"
9 | [pattern & args]
10 | (fn
11 | [candidate]
12 | (not (nil? (re-matches (apply pattern args) candidate)))))
13 |
14 | (defn print-pattern
15 | "Print the resulting pattern"
16 | [pattern] (println pattern))
17 |
18 | (defn re-add
19 | "Add a new part to the regex"
20 | [regex add]
21 | (re-pattern (str regex add)))
22 |
23 | (defmacro defpattern
24 | [fn-name documentation param-vector body]
25 | `(def ~fn-name
26 | (fn
27 | ([~@param-vector]
28 | (~fn-name ~@param-vector (vex)))
29 | ([~@param-vector regex#]
30 | (re-add regex# ~body)))))
31 |
32 | (defpattern one-or-more
33 | "One or more of the previous middleware"
34 | [] "+")
35 |
36 | (defpattern zero-or-more
37 | "Zero or more of the previous middleware"
38 | [] "*")
39 |
40 | (defpattern any
41 | "Matches any of the letters in value"
42 | [value] (str "([" value "])"))
43 |
44 | (defpattern any-blank
45 | "sequence of blanks characters"
46 | [] (->> (any "\\s\\t\\n")
47 | (zero-or-more)))
48 |
49 | (defpattern anything
50 | "Matches everything"
51 | [] "(.*)")
52 |
53 | (defpattern anything-but
54 | "Matching everything but the specified value"
55 | [value] (str "([^" value "]*)"))
56 |
57 | (defpattern maybe
58 | "Eventually match the provided value"
59 | [value] (str "(" value ")?"))
60 |
61 | (defpattern end-of-line
62 | "Matches the end of the line"
63 | [] "$")
64 |
65 | (defpattern start-of-line
66 | "Matches the start of the line"
67 | [] "^")
68 |
69 | (defpattern then
70 | "Matches the specified value, literally"
71 | [value] (str "(" value ")"))
72 |
73 | (defpattern line-break
74 | "Matches a line break"
75 | [] "(\\n|(\\r\\n))")
76 |
77 | (defpattern interval
78 | "Matches a range of values.
79 | Example (interval \\a \\z \\A \\Z 0 9)
80 | will match every characters between
81 | a and z,
82 | A and Z,
83 | 0 and 9"
84 | [args] (str "(["
85 | (join "" (map (fn [r] (join "-" r)) (partition 2 args)))
86 | "])"))
87 |
88 | (defpattern tab
89 | "Matches a tabulation"
90 | [] " ")
91 |
92 | (defpattern word
93 | "Matches any word (case-insensitive)"
94 | [] "[a-zA-Z]+")
95 |
96 | (defn OR
97 | "OR operator.
98 | Example: (OR ['value 1' 'value 2'])"
99 | [values] (str "(" (join "|" values) ")"))
100 |
101 | (defpattern times
102 | "Specify the interval"
103 | [[start end]] (str "{" start "," end "}"))
104 |
105 | (defpattern url?
106 | "Match if it is a url"
107 | [domain]
108 | (->> (then "http")
109 | (maybe "s")
110 | (then "://")
111 | (maybe "www.")
112 | (then domain)
113 | (then ".")
114 | (anything-but " ")))
115 |
116 | (defpattern words-split-by
117 | "Matches a sequence of words delimited by any of the characters
118 | in ignore-vector"
119 | [ignore-vector]
120 | (let [any-delimiter (join "|" ignore-vector)]
121 | (str "(((" any-delimiter ")+)?([a-zA-Z]+))+")))
122 |
123 | (defpattern sentence
124 | "Matches a sentence of words split by spaces"
125 | [] (->> (words-split-by ["\\s"])))
126 |
--------------------------------------------------------------------------------
/test/verbal_exprejon/core_test.clj:
--------------------------------------------------------------------------------
1 | (ns verbal-exprejon.core-test
2 | (:require [clojure.test :refer :all]
3 | [verbal-exprejon.core :refer :all]))
4 |
5 | (defn is-not-nil
6 | [a] (is (not (nil? a))))
7 |
8 | (defn is-nil
9 | [a] (is (nil? a)))
10 |
11 | (deftest anything-test
12 | (testing "anything"
13 | (let [testfn #(re-matches (->> (vex) (anything)) %)]
14 | (is #"(.*)" (->> (vex) (anything)))
15 | (is-not-nil (testfn "abcdef01234"))
16 | (is-not-nil (testfn "")))))
17 |
18 | (deftest maybe-test
19 | (testing "maybe"
20 | (let [testfn #(re-matches (->> (vex) (maybe "Hello")) %)]
21 | (is #"(Hello)?" (->> (vex) (maybe "Hello")))
22 | (is-not-nil (testfn "Hello"))
23 | (is-not-nil (testfn "")))))
24 |
25 | (deftest eof-test
26 | (testing "end-of-line"
27 | (let [testfn #(re-matches (->> (vex) (end-of-line)) %)]
28 | (is #"$" (->> (vex) (end-of-line)))
29 | (is-not-nil (testfn "")))))
30 |
31 | (deftest start-test
32 | (testing "start-of-line"
33 | (is #"^" (->> (vex) (start-of-line)))))
34 |
35 | (deftest then-test
36 | (testing "then"
37 | (let [testfn #(re-matches (->> (vex) (then "Hello")) %)]
38 | (is #"(Hello)" (->> (vex) (then "Hello")))
39 | (is-not-nil (testfn "Hello"))
40 | (is-nil (testfn "World")))))
41 |
42 | (deftest any-test
43 | (testing "any"
44 | (let [testfn #(re-matches (->> (vex) (any "abc")) %)]
45 | (is #"([abc])" (->> (vex) (any "abc")))
46 | (is-not-nil (testfn "a"))
47 | (is-not-nil (testfn "a"))
48 | (is-not-nil (testfn "c"))
49 | (is-nil (testfn "e"))
50 | (is-nil (testfn "abc")))))
51 |
52 | (deftest br-test
53 | (testing "line-break"
54 | (is #"(\n|(\r\n))" (->> (vex) (line-break)))))
55 |
56 | (deftest interval-test
57 | (testing "interval"
58 | (let [testfn #(re-matches (->> (vex) (interval [\a \z \A \Z])) %)]
59 | (is #"[a-zA-Z]" (->> (vex) (interval [\a \z \A \Z])))
60 | (is-not-nil (testfn "z"))
61 | (is-not-nil (testfn "B"))
62 | (is-nil (testfn "0"))
63 | (is-nil (testfn "9")))))
64 |
65 | (deftest teb-test
66 | (testing "tab"
67 | (is #" " (->> (vex) (tab)))))
68 |
69 |
70 | (deftest word-test
71 | (testing "word"
72 | (let [testfn #(re-matches (->> (vex) (word)) %)]
73 | (is #"([a-zA-Z]+)" (->> (vex) (word)))
74 | (is-not-nil (testfn "a"))
75 | (is-not-nil (testfn "Hello"))
76 | (is-nil (testfn "213")))))
77 |
--------------------------------------------------------------------------------