├── .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 | travis 7 | travis 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 | --------------------------------------------------------------------------------