├── .gitignore ├── LICENSE ├── README.md ├── compile-test ├── run-tests.js └── util.js ├── demo ├── spago.yaml └── src │ └── ReadmeDemo.purs ├── justfile ├── package.json ├── spago.yaml ├── src └── Type │ ├── Char.purs │ ├── Regex.purs │ └── Regex │ ├── AsciiTable.purs │ ├── CST.purs │ ├── Compile.purs │ ├── Match.purs │ ├── Parse.purs │ ├── Print.purs │ └── RegexRep.purs └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | bower_components/ 3 | node_modules/ 4 | .pulp-cache/ 5 | output/ 6 | output-es/ 7 | generated-docs/ 8 | .psc-package/ 9 | .psc* 10 | .purs* 11 | .psa* 12 | .spago 13 | /tmp 14 | todo.txt -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Michael Bock 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors may be 15 | used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purescript-typelevel-regex 2 | 3 | Regular expressions for type level strings. 4 | 5 | 6 | ## Installation 7 | 8 | ``` 9 | spago install typelevel-regex 10 | ``` 11 | 12 | ## Features 13 | 14 | - Subset of JS regexes 15 | - Friendly error messages 16 | - No runtime overhead 17 | 18 | ## Example 19 | 20 | The `guard` function is similar to `test` which exists for many regex implementations. 21 | The difference is that instead of returning a boolean, 22 | it reflects the type level input string if the regex matches. 23 | 24 | 25 | 26 | ```hs 27 | import Type.Regex as Regex 28 | 29 | type RegexURL = 30 | "^(ftp|https?)://[a-z][a-z0-9]*\\.(app|com|org)(/[a-z]+)*(\\?([a-z]+))?$" 31 | 32 | -- The following only compiles if the type level string matches the above regex: 33 | 34 | sample1 :: String 35 | sample1 = Regex.guard @RegexURL @"http://hello.com/path/to?query" 36 | 37 | sample2 :: String 38 | sample2 = Regex.guard @RegexURL @"http://hello.com/path/to" 39 | 40 | sample3 :: String 41 | sample3 = Regex.guard @RegexURL @"https://hello99.org/path/to" 42 | 43 | sample4 :: String 44 | sample4 = Regex.guard @RegexURL @"ftp://hello.org/path/to/home" 45 | ``` 46 | 47 | 48 | ## Supported regex features 49 | 50 | | Feature | Example | 51 | | --------------------------- | ------------------------ | 52 | | Character literals | `a`, `b`, `c`, ... | 53 | | Wildcards | `.` | 54 | | Match Start/End | `^`, `$` | 55 | | Groups | `(abc)`, `(hello)`, ... | 56 | | Alternatives | `a\|b\|c`, `(foo\|bar)` | 57 | | Match Many | `(foo)*` | 58 | | Match Some | `(foo)+` | 59 | | Match Maybe | `(foo)?` | 60 | | Character Classes | `[abc]`, `[a-z0-9_]` | 61 | | Negative Character Classes | `[^abc]`, `[^a-z0-9_]` | 62 | | Escapes | `\\?`, `\\[` | 63 | 64 | 65 | -------------------------------------------------------------------------------- /compile-test/run-tests.js: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as cp from "child_process"; 3 | import dedent from "dedent"; 4 | import { shouldCompile, shouldNotCompile } from "./util.js"; 5 | import { assert } from "console"; 6 | import { throws } from "assert"; 7 | 8 | const mkModule = (src) => dedent` 9 | module Test.Tmp where 10 | 11 | import Type.Regex as R 12 | 13 | ${src} 14 | `; 15 | 16 | const testRegexOk = (regex, str) => { 17 | shouldCompile( 18 | mkModule(` 19 | str :: String 20 | str = R.guard @"${regex}" @"${str}" 21 | `) 22 | ); 23 | 24 | const jsRegex = new RegExp(regex); 25 | 26 | assert(jsRegex.test(str), "should have matched"); 27 | }; 28 | 29 | const testRegexFail = (regex, str, err) => { 30 | shouldNotCompile( 31 | mkModule(` 32 | str :: String 33 | str = R.guard @"${regex}" @"${str}" 34 | `), 35 | err 36 | ); 37 | 38 | throws(() => { 39 | const jsRegex = new RegExp(regex); 40 | if (!jsRegex.test(str)) { 41 | throw new Error(""); 42 | } 43 | }, `should have thrown: ${regex} matches ${str}`); 44 | }; 45 | 46 | const main = () => { 47 | testRegexOk("^hello$", "hello"); 48 | testRegexFail("^hello$", "hi", "Regex failed to match"); 49 | 50 | testRegexOk("^(foo|bar|baz)$", "foo"); 51 | testRegexOk("^(foo|bar|baz)$", "bar"); 52 | testRegexOk("^(foo|bar|baz)$", "baz"); 53 | testRegexFail("^(foo|bar|baz)$", "qua", "Regex failed to match"); 54 | 55 | testRegexOk("^a*$", ""); 56 | testRegexOk("^a*$", "a"); 57 | testRegexOk("^a*$", "aa"); 58 | testRegexOk("^a*$", "aaa"); 59 | testRegexFail("^a*$", "b", "Regex failed to match"); 60 | 61 | testRegexOk("^(foo)*$", ""); 62 | testRegexOk("^(foo)*$", "foo"); 63 | testRegexOk("^(foo)*$", "foofoo"); 64 | testRegexFail("^(foo)*$", "qua", "Regex failed to match"); 65 | 66 | testRegexOk("^foo", "foobar"); 67 | testRegexFail("^foo", "barfoo", "Regex failed to match"); 68 | 69 | testRegexOk("foo$", "barfoo"); 70 | testRegexFail("foo$", "foobar", "Regex failed to match"); 71 | }; 72 | 73 | main(); 74 | -------------------------------------------------------------------------------- /compile-test/util.js: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as cp from "child_process"; 3 | import dedent from "dedent"; 4 | 5 | const testFile = "test/Test/Tmp.purs"; 6 | 7 | const run = (src) => { 8 | fs.writeFileSync(testFile, dedent(src)); 9 | const result = cp.spawnSync("spago", ["build", "--json-errors"]); 10 | 11 | return result; 12 | }; 13 | 14 | export const shouldNotCompile = (src, expectedError) => { 15 | const result = run(src); 16 | 17 | if (result.status === 0) { 18 | console.log("Expected compilation to fail, but it succeeded.") 19 | process.exit(1); 20 | } 21 | 22 | const resultStr = result.stdout.toString(); 23 | const resultJson = JSON.parse(resultStr); 24 | 25 | const errorMsg = resultJson.errors[0].message; 26 | 27 | if (!errorMsg.includes(expectedError)) { 28 | console.log(`Expected error message to include: ${expectedError}.`); 29 | console.log({src, errorMsg}); 30 | process.exit(1); 31 | } 32 | 33 | fs.rmSync(testFile); 34 | }; 35 | 36 | export const shouldCompile = (src) => { 37 | const result = run(src); 38 | 39 | if (result.status !== 0) { 40 | const resultStr = result.stdout.toString(); 41 | const resultJson = JSON.parse(resultStr); 42 | 43 | console.log(JSON.stringify(resultJson, null, 2)); 44 | 45 | process.exit(1); 46 | } 47 | 48 | fs.rmSync(testFile); 49 | }; 50 | -------------------------------------------------------------------------------- /demo/spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | dependencies: 3 | - typelevel-regex 4 | name: fmt-demo 5 | test: 6 | dependencies: [] 7 | main: Test.Main 8 | -------------------------------------------------------------------------------- /demo/src/ReadmeDemo.purs: -------------------------------------------------------------------------------- 1 | {- 2 | # purescript-typelevel-regex 3 | 4 | Regular expressions for type level strings. 5 | 6 | 7 | ## Installation 8 | 9 | ``` 10 | spago install typelevel-regex 11 | ``` 12 | 13 | ## Features 14 | 15 | - Subset of JS regexes 16 | - Friendly error messages 17 | - No runtime overhead 18 | 19 | ## Example 20 | 21 | The `guard` function is similar to `test` which exists for many regex implementations. 22 | The difference is that instead of returning a boolean, 23 | it reflects the type level input string if the regex matches. 24 | 25 | -} 26 | 27 | module ReadmeDemo where 28 | 29 | import Type.Regex as Regex 30 | 31 | type RegexURL = 32 | "^(ftp|https?)://[a-z][a-z0-9]*\\.(app|com|org)(/[a-z]+)*(\\?([a-z]+))?$" 33 | 34 | -- The following only compiles if the type level string matches the above regex: 35 | 36 | sample1 :: String 37 | sample1 = Regex.guard @RegexURL @"http://hello.com/path/to?query" 38 | 39 | sample2 :: String 40 | sample2 = Regex.guard @RegexURL @"http://hello.com/path/to" 41 | 42 | sample3 :: String 43 | sample3 = Regex.guard @RegexURL @"https://hello99.org/path/to" 44 | 45 | sample4 :: String 46 | sample4 = Regex.guard @RegexURL @"ftp://hello.org/path/to/home" 47 | 48 | {- 49 | 50 | ## Supported regex features 51 | 52 | | Feature | Example | 53 | | --------------------------- | ------------------------ | 54 | | Character literals | `a`, `b`, `c`, ... | 55 | | Wildcards | `.` | 56 | | Match Start/End | `^`, `$` | 57 | | Groups | `(abc)`, `(hello)`, ... | 58 | | Alternatives | `a\|b\|c`, `(foo\|bar)` | 59 | | Match Many | `(foo)*` | 60 | | Match Some | `(foo)+` | 61 | | Match Maybe | `(foo)?` | 62 | | Character Classes | `[abc]`, `[a-z0-9_]` | 63 | | Negative Character Classes | `[^abc]`, `[^a-z0-9_]` | 64 | | Escapes | `\\?`, `\\[` | 65 | 66 | 67 | -} -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | test: 2 | node compile-test/run-tests.js 3 | 4 | gen: 5 | yarn purs-to-md --input-purs demo/src/ReadmeDemo.purs --output-md README.md 6 | sed -i '/module ReadmeDemo where/,+1d' ./README.md -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-tag-syntax", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "git@github.com:thought2/purescript-typelevel-regex.git", 6 | "author": "Michael Bock ", 7 | "license": "MIT", 8 | "type": "module", 9 | "dependencies": { 10 | "dedent": "^1.5.1" 11 | }, 12 | "devDependencies": { 13 | "purs-to-md": "^0.3.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spago.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | dependencies: 3 | - prelude: ">=6.0.1 <7.0.0" 4 | - typelevel-prelude: ">=7.0.0 <8.0.0" 5 | name: typelevel-regex 6 | test: 7 | dependencies: [] 8 | main: Test.Main 9 | publish: 10 | version: 0.0.3 11 | license: BSD-3-Clause 12 | location: 13 | githubOwner: thought2 14 | githubRepo: purescript-typelevel-regex 15 | workspace: 16 | extra_packages: {} 17 | package_set: 18 | registry: 41.2.0 19 | build_opts: 20 | pedantic_packages: true 21 | persist_warnings: true 22 | -------------------------------------------------------------------------------- /src/Type/Char.purs: -------------------------------------------------------------------------------- 1 | module Type.Char 2 | ( Char' 3 | , UnsafeMkChar 4 | , class IsEmptyOrFail 5 | , class SymIsChar 6 | , symIsChar 7 | ) 8 | where 9 | 10 | import Prelude 11 | 12 | import Prim.Symbol as Sym 13 | import Prim.TypeError (class Fail, Beside, Doc, Text) 14 | 15 | foreign import data Char' :: Type 16 | 17 | foreign import data UnsafeMkChar :: Symbol -> Char' 18 | 19 | type CharError s = Text "Expected a single character, got: \"" <> Text s <> Text "\"" 20 | 21 | infixr 6 type Beside as <> 22 | 23 | -------------------------------------------------------------------------------- 24 | 25 | class SymIsChar (s :: Symbol) (char :: Char') | s -> char 26 | 27 | symIsChar :: forall @sym @char. SymIsChar sym char => Unit 28 | symIsChar = unit 29 | 30 | instance symIsCharEmpty :: 31 | ( Fail (CharError "") 32 | ) => 33 | SymIsChar "" char 34 | 35 | else instance symIsCharNonEmpty :: 36 | ( Sym.Cons head tail sym 37 | , IsEmptyOrFail (CharError sym) tail 38 | ) => 39 | SymIsChar sym (UnsafeMkChar head) 40 | 41 | -------------------------------------------------------------------------------- 42 | 43 | class IsEmptyOrFail (err :: Doc) (s :: Symbol) 44 | 45 | instance isEmptyOrFailEmpty :: IsEmptyOrFail err "" 46 | 47 | else instance isEmptyOrFailError :: 48 | ( Fail err 49 | ) => 50 | IsEmptyOrFail err s 51 | -------------------------------------------------------------------------------- /src/Type/Regex.purs: -------------------------------------------------------------------------------- 1 | module Type.Regex where 2 | 3 | import Data.Symbol (class IsSymbol, reflectSymbol) 4 | import Prim.Ordering (EQ, Ordering) 5 | import Prim.Symbol as Sym 6 | import Prim.TypeError (class Fail, Text) 7 | import Type.Proxy (Proxy(..)) 8 | import Type.Regex.CST as CST 9 | import Type.Regex.Compile (class CompileRegex) 10 | import Type.Regex.Match (class MatchRegex) 11 | import Type.Regex.Parse (class ParseRegex) 12 | import Type.Regex.Print (class PrintRegex) 13 | 14 | class 15 | TestRegex (regex :: Symbol) (str :: Symbol) 16 | 17 | instance 18 | ( ParseRegex' regexStr regexCST 19 | , CompileRegex regexCST regex 20 | , MatchRegex regex str 21 | ) => 22 | TestRegex regexStr str 23 | 24 | guard :: forall @regex @str. TestRegex regex str => IsSymbol str => String 25 | guard = reflectSymbol (Proxy :: Proxy str) 26 | 27 | reflectRegex 28 | :: forall @spec cst regex 29 | . ParseRegex spec cst 30 | => CompileRegex cst regex 31 | => IsSymbol spec 32 | => String 33 | reflectRegex = reflectSymbol (Proxy :: Proxy spec) 34 | 35 | --- 36 | 37 | class ParseRegex' (spec :: Symbol) (cst :: CST.Regex) | spec -> cst 38 | 39 | instance 40 | ( ParseRegex spec cst 41 | , PrintRegex cst spec' 42 | , Sym.Compare spec spec' ord 43 | , ParseRegex'Result ord 44 | ) => 45 | ParseRegex' spec cst 46 | 47 | class ParseRegex'Result (result :: Ordering) 48 | 49 | instance ParseRegex'Result EQ 50 | 51 | else instance 52 | ( Fail (Text "Unexpected regex parse error. Please report a bug.") 53 | ) => 54 | ParseRegex'Result ord 55 | -------------------------------------------------------------------------------- /src/Type/Regex/AsciiTable.purs: -------------------------------------------------------------------------------- 1 | module Type.Regex.AsciiTable where 2 | 3 | class AsciiCode (dec :: Int) (sym :: Symbol) | dec -> sym, sym -> dec 4 | 5 | instance AsciiCode 33 "!" 6 | instance AsciiCode 34 "\"" 7 | instance AsciiCode 35 "#" 8 | instance AsciiCode 36 "$" 9 | instance AsciiCode 37 "%" 10 | instance AsciiCode 38 "&" 11 | instance AsciiCode 39 "'" 12 | instance AsciiCode 40 "(" 13 | instance AsciiCode 41 ")" 14 | instance AsciiCode 42 "*" 15 | instance AsciiCode 43 "+" 16 | instance AsciiCode 44 "," 17 | instance AsciiCode 45 "-" 18 | instance AsciiCode 46 "." 19 | instance AsciiCode 47 "/" 20 | instance AsciiCode 48 "0" 21 | instance AsciiCode 49 "1" 22 | instance AsciiCode 50 "2" 23 | instance AsciiCode 51 "3" 24 | instance AsciiCode 52 "4" 25 | instance AsciiCode 53 "5" 26 | instance AsciiCode 54 "6" 27 | instance AsciiCode 55 "7" 28 | instance AsciiCode 56 "8" 29 | instance AsciiCode 57 "9" 30 | instance AsciiCode 58 ":" 31 | instance AsciiCode 59 ";" 32 | instance AsciiCode 60 "<" 33 | instance AsciiCode 61 "=" 34 | instance AsciiCode 62 ">" 35 | instance AsciiCode 63 "?" 36 | instance AsciiCode 64 "@" 37 | instance AsciiCode 65 "A" 38 | instance AsciiCode 66 "B" 39 | instance AsciiCode 67 "C" 40 | instance AsciiCode 68 "D" 41 | instance AsciiCode 69 "E" 42 | instance AsciiCode 70 "F" 43 | instance AsciiCode 71 "G" 44 | instance AsciiCode 72 "H" 45 | instance AsciiCode 73 "I" 46 | instance AsciiCode 74 "J" 47 | instance AsciiCode 75 "K" 48 | instance AsciiCode 76 "L" 49 | instance AsciiCode 77 "M" 50 | instance AsciiCode 78 "N" 51 | instance AsciiCode 79 "O" 52 | instance AsciiCode 80 "P" 53 | instance AsciiCode 81 "Q" 54 | instance AsciiCode 82 "R" 55 | instance AsciiCode 83 "S" 56 | instance AsciiCode 84 "T" 57 | instance AsciiCode 85 "U" 58 | instance AsciiCode 86 "V" 59 | instance AsciiCode 87 "W" 60 | instance AsciiCode 88 "X" 61 | instance AsciiCode 89 "Y" 62 | instance AsciiCode 90 "Z" 63 | instance AsciiCode 91 "[" 64 | instance AsciiCode 92 "\\" 65 | instance AsciiCode 93 "]" 66 | instance AsciiCode 94 "^" 67 | instance AsciiCode 95 "_" 68 | instance AsciiCode 96 "`" 69 | instance AsciiCode 97 "a" 70 | instance AsciiCode 98 "b" 71 | instance AsciiCode 99 "c" 72 | instance AsciiCode 100 "d" 73 | instance AsciiCode 101 "e" 74 | instance AsciiCode 102 "f" 75 | instance AsciiCode 103 "g" 76 | instance AsciiCode 104 "h" 77 | instance AsciiCode 105 "i" 78 | instance AsciiCode 106 "j" 79 | instance AsciiCode 107 "k" 80 | instance AsciiCode 108 "l" 81 | instance AsciiCode 109 "m" 82 | instance AsciiCode 110 "n" 83 | instance AsciiCode 111 "o" 84 | instance AsciiCode 112 "p" 85 | instance AsciiCode 113 "q" 86 | instance AsciiCode 114 "r" 87 | instance AsciiCode 115 "s" 88 | instance AsciiCode 116 "t" 89 | instance AsciiCode 117 "u" 90 | instance AsciiCode 118 "v" 91 | instance AsciiCode 119 "w" 92 | instance AsciiCode 120 "x" 93 | instance AsciiCode 121 "y" 94 | instance AsciiCode 122 "z" 95 | instance AsciiCode 123 "{" 96 | instance AsciiCode 124 "|" 97 | instance AsciiCode 125 "}" 98 | instance AsciiCode 126 "~" 99 | -------------------------------------------------------------------------------- /src/Type/Regex/CST.purs: -------------------------------------------------------------------------------- 1 | module Type.Regex.CST where 2 | 3 | import Type.Char (Char') 4 | 5 | -------------------------------------------------------------------------------- 6 | --- Types 7 | -------------------------------------------------------------------------------- 8 | 9 | --- Regex 10 | 11 | foreign import data Regex :: Type 12 | 13 | -- | Empty Regex 14 | foreign import data Nil :: Regex 15 | 16 | -- | `.` 17 | foreign import data Wildcard :: Regex 18 | 19 | -- | `[a-z123]` 20 | foreign import data RegexCharClass :: CharClass -> Boolean -> Regex 21 | 22 | foreign import data Lit :: Char' -> Regex 23 | 24 | -- | `\\a` 25 | foreign import data Quote :: Char' -> Regex 26 | 27 | -- | `$` 28 | foreign import data EndOfStr :: Regex 29 | 30 | -- | `^` 31 | foreign import data StartOfStr :: Regex 32 | 33 | -- | `?` 34 | foreign import data Optional :: Regex -> Regex 35 | 36 | -- | `+` 37 | foreign import data OneOrMore :: Regex -> Regex 38 | 39 | -- | `*` 40 | foreign import data Many :: Regex -> Regex 41 | 42 | -- | `(` .. `)` 43 | foreign import data Group :: Regex -> Regex 44 | 45 | -- | `ab` 46 | foreign import data Cat :: Regex -> Regex -> Regex 47 | 48 | -- | `|` 49 | foreign import data Alt :: Regex -> Regex -> Regex 50 | 51 | infixr 6 type Cat as ~ 52 | 53 | --- CharClass 54 | 55 | foreign import data CharClass :: Type 56 | 57 | foreign import data CharClassNil :: CharClass 58 | 59 | foreign import data CharClassLit :: Char' -> CharClass -> CharClass 60 | 61 | foreign import data CharClassRange :: Char' -> Char' -> CharClass -> CharClass 62 | -------------------------------------------------------------------------------- /src/Type/Regex/Compile.purs: -------------------------------------------------------------------------------- 1 | module Type.Regex.Compile where 2 | 3 | import Prim.Boolean (False, True) 4 | import Prim.Int as Int 5 | import Prim.Ordering (GT, Ordering) 6 | import Prim.Symbol as Sym 7 | import Prim.TypeError (class Fail, Beside, Doc, Text) 8 | import Type.Char (UnsafeMkChar) 9 | import Type.Regex.AsciiTable (class AsciiCode) 10 | import Type.Regex.CST as CST 11 | import Type.Regex.RegexRep as R 12 | 13 | type MkError (err :: Doc) = Beside (Text "Regex Compile Error: ") err 14 | 15 | type ErrorUnexpected = MkError (Text "Unexpected error. Please report this as a bug.") 16 | 17 | type ErrorRange = MkError (Text "Range error") 18 | 19 | -------------------------------------------------------------------------------- 20 | --- CompileRegex 21 | -------------------------------------------------------------------------------- 22 | 23 | class 24 | CompileRegex (cst :: CST.Regex) (regex :: R.Regex) 25 | | cst -> regex 26 | 27 | instance compileRegexNil :: 28 | CompileRegex CST.Nil R.Nil 29 | 30 | else instance compileRegexWildcard :: 31 | CompileRegex CST.Wildcard R.Wildcard 32 | 33 | else instance compileRegexCharClass :: 34 | ( CompileCharClass charClass positive regex 35 | ) => 36 | CompileRegex (CST.RegexCharClass charClass positive) regex 37 | 38 | else instance compileRegexLit :: 39 | CompileRegex (CST.Lit char) (R.Lit char) 40 | 41 | else instance compileRegexQuote :: 42 | CompileRegex (CST.Quote char) (R.Lit char) 43 | 44 | else instance compileRegexEndOfStr :: 45 | CompileRegex CST.EndOfStr R.EndOfStr 46 | 47 | else instance compileRegexStartOfStr :: 48 | CompileRegex CST.StartOfStr R.StartOfStr 49 | 50 | else instance compileRegexOptional :: 51 | ( CompileRegex cst regex 52 | ) => 53 | CompileRegex (CST.Optional cst) (R.Alt regex R.Nil) 54 | 55 | else instance compileRegexOneOrMore :: 56 | ( CompileRegex cst regex 57 | ) => 58 | CompileRegex (CST.OneOrMore cst) (R.Cat regex (R.Many regex)) 59 | 60 | else instance compileRegexMany :: 61 | ( CompileRegex cst regex 62 | ) => 63 | CompileRegex (CST.Many cst) (R.Many regex) 64 | 65 | else instance compileRegexGroup :: 66 | ( CompileRegex cst regex 67 | ) => 68 | CompileRegex (CST.Group cst) regex 69 | 70 | else instance compileRegexCat' :: 71 | ( CompileRegex cst regex 72 | ) => 73 | CompileRegex (CST.Cat cst CST.Nil) regex 74 | 75 | else instance compileRegexCat'' :: 76 | ( CompileRegex cst regex 77 | ) => 78 | CompileRegex (CST.Cat CST.Nil cst) regex 79 | 80 | else instance compileRegexCat :: 81 | ( CompileRegex cst1 regex1 82 | , CompileRegex cst2 regex2 83 | ) => 84 | CompileRegex (CST.Cat cst1 cst2) (R.Cat regex1 regex2) 85 | 86 | else instance compileRegexAlt :: 87 | ( CompileRegex cst1 regex1 88 | , CompileRegex cst2 regex2 89 | ) => 90 | CompileRegex (CST.Alt cst1 cst2) (R.Alt regex1 regex2) 91 | 92 | else instance compileRegexErrUnexpected :: 93 | ( Fail ErrorUnexpected 94 | ) => 95 | CompileRegex cst regex 96 | 97 | -------------------------------------------------------------------------------- 98 | --- CompileCharClass 99 | -------------------------------------------------------------------------------- 100 | 101 | class 102 | CompileCharClass (charClass :: CST.CharClass) (positive :: Boolean) (regex :: R.Regex) 103 | | charClass positive -> regex 104 | 105 | instance compileCharClassPositive :: 106 | ( CompileCharClassGo charClass "" chars 107 | ) => 108 | CompileCharClass charClass True (R.Lits chars) 109 | 110 | else instance compileCharClassNegative :: 111 | ( CompileCharClassGo charClass "" chars 112 | ) => 113 | CompileCharClass charClass False (R.NotLits chars) 114 | 115 | --- CompileCharClassGo 116 | 117 | class 118 | CompileCharClassGo 119 | (charClass :: CST.CharClass) 120 | (charsFrom :: Symbol) 121 | (charsTo :: Symbol) 122 | | charClass charsFrom -> charsTo 123 | 124 | instance compileCharClassGoNil :: 125 | CompileCharClassGo CST.CharClassNil chars chars 126 | 127 | else instance compileCharClassGoLit :: 128 | ( CompileCharClassGo charClass chars' charsTo 129 | , Sym.Append chars char chars' 130 | ) => 131 | CompileCharClassGo (CST.CharClassLit (UnsafeMkChar char) charClass) chars charsTo 132 | 133 | else instance compileCharClassGoRange :: 134 | ( AsciiCode from charFrom 135 | , AsciiCode to charTo 136 | , GetCharRange from to chars' 137 | , Sym.Append chars chars' chars'' 138 | , CompileCharClassGo charClass chars'' charsTo 139 | ) => 140 | CompileCharClassGo 141 | (CST.CharClassRange (UnsafeMkChar charFrom) (UnsafeMkChar charTo) charClass) 142 | chars 143 | charsTo 144 | 145 | ------------------------------------------------------------------------ 146 | --- GetCharRange 147 | ------------------------------------------------------------------------ 148 | 149 | class 150 | GetCharRange (start :: Int) (end :: Int) (chars :: Symbol) 151 | | start end -> chars 152 | 153 | instance 154 | ( GetCharRangeGuard start end chars 155 | ) => 156 | GetCharRange start end chars 157 | 158 | --- GetCharRangeGuard 159 | 160 | class 161 | GetCharRangeGuard (start :: Int) (end :: Int) (chars :: Symbol) 162 | | start end -> chars 163 | 164 | instance getCharRangeGuard :: 165 | ( Int.Compare start end result 166 | , GetCharRangeGuardResult result start end chars 167 | ) => 168 | GetCharRangeGuard start end chars 169 | 170 | --- GetCharRangeGuardResult 171 | 172 | class 173 | GetCharRangeGuardResult 174 | (result :: Ordering) 175 | (start :: Int) 176 | (end :: Int) 177 | (chars :: Symbol) 178 | | result start end -> chars 179 | 180 | instance getCharRangeGuardResultFail :: 181 | Fail ErrorRange => 182 | GetCharRangeGuardResult GT start end charsIn 183 | 184 | else instance getCharRangeGuardResultOk :: 185 | ( GetCharRangeGo start end "" chars 186 | ) => 187 | GetCharRangeGuardResult result start end chars 188 | 189 | --- GetCharRangeGo 190 | 191 | class 192 | GetCharRangeGo 193 | (start :: Int) 194 | (end :: Int) 195 | (charsIn :: Symbol) 196 | (chars :: Symbol) 197 | | start end charsIn -> chars 198 | 199 | instance getCharRangeGoLast :: 200 | ( AsciiCode start char 201 | , Sym.Append char charsIn chars 202 | ) => 203 | GetCharRangeGo start start charsIn chars 204 | 205 | else instance getCharRangeGoNext :: 206 | ( AsciiCode start char 207 | , GetCharRangeGo start' end charsIn' chars 208 | , Sym.Append char charsIn charsIn' 209 | , Int.Add start 1 start' 210 | ) => 211 | GetCharRangeGo start end charsIn chars 212 | -------------------------------------------------------------------------------- /src/Type/Regex/Match.purs: -------------------------------------------------------------------------------- 1 | module Type.Regex.Match where 2 | 3 | import Prim.Boolean (False, True) 4 | import Prim.Symbol as Sym 5 | import Prim.TypeError (class Fail, Above, Beside, Text) 6 | import Type.Char (UnsafeMkChar) 7 | import Type.Data.Boolean (class And, class Not, class Or) 8 | import Type.Regex.RegexRep (type (~), Regex) 9 | import Type.Regex.RegexRep as R 10 | 11 | ------------------------------------------------------------------------ 12 | --- MatchRegex 13 | ------------------------------------------------------------------------ 14 | 15 | class MatchRegex (regex :: Regex) (str :: Symbol) 16 | 17 | instance matchRegexNoScan :: 18 | ( RegexAttemptGo regex str rest matches 19 | , MatchRegexResult rest matches 20 | ) => 21 | MatchRegex (R.StartOfStr ~ regex) str 22 | 23 | else instance matchRegexScan :: 24 | ( ScanRegexGo regex str rest matches 25 | , MatchRegexResult rest matches 26 | ) => 27 | MatchRegex regex str 28 | 29 | --- MatchRegexResult 30 | 31 | class MatchRegexResult (rest :: Symbol) (matches :: Boolean) 32 | 33 | instance MatchRegexResult rest True 34 | 35 | else instance 36 | ( Fail 37 | ( Above 38 | (Text "Regex failed to match.") 39 | (Beside (Text "Unmatched rest is: ") (Text rest)) 40 | ) 41 | ) => 42 | MatchRegexResult rest matches 43 | 44 | ------------------------------------------------------------------------ 45 | --- ScanRegex 46 | ------------------------------------------------------------------------ 47 | 48 | --- ScanRegexGo 49 | 50 | class 51 | ScanRegexGo (regex :: Regex) (str :: Symbol) (rest :: Symbol) (matches :: Boolean) 52 | | regex str -> rest matches 53 | 54 | instance scanRegexGoNil :: 55 | ScanRegexGo regex "" "" False 56 | 57 | else instance scanRegexGo :: 58 | ( RegexAttemptGo regex str rest matches 59 | , ScanRegexResult matches regex str rest' matches' 60 | ) => 61 | ScanRegexGo regex str rest' matches' 62 | 63 | --- ScanRegexResult 64 | 65 | class 66 | ScanRegexResult 67 | (result :: Boolean) 68 | (regex :: Regex) 69 | (str :: Symbol) 70 | (rest :: Symbol) 71 | (matches :: Boolean) 72 | | result regex str -> rest matches 73 | 74 | instance scanRegexResultSuccess :: 75 | ScanRegexResult True regex str rest True 76 | 77 | else instance scanRegexResultEnd :: 78 | ScanRegexResult False regex "" "" False 79 | 80 | else instance scanRegexResultNext :: 81 | ( Sym.Cons head tail str 82 | , ScanRegexGo regex tail rest matches 83 | ) => 84 | ScanRegexResult False regex str rest matches 85 | 86 | ------------------------------------------------------------------------ 87 | --- RegexAttemptGo 88 | ------------------------------------------------------------------------ 89 | 90 | class 91 | RegexAttemptGo (regex :: Regex) (str :: Symbol) (rest :: Symbol) (matches :: Boolean) 92 | | regex str -> rest matches 93 | 94 | instance regexAttemptGoRegexEnd :: 95 | RegexAttemptGo R.Nil str str True 96 | 97 | else instance regexAttemptGoStringEnd :: 98 | ( IsFinalRegex regex True 99 | ) => 100 | RegexAttemptGo regex "" "" True 101 | 102 | else instance regexAttemptGoStringContinue :: 103 | ( Sym.Cons head tail str 104 | , RegexAttemptMatch regex head tail rest matches 105 | ) => 106 | RegexAttemptGo regex str rest matches 107 | 108 | --- RegexAttemptMatch 109 | 110 | class 111 | RegexAttemptMatch 112 | (regex :: Regex) 113 | (head :: Symbol) 114 | (tail :: Symbol) 115 | (rest :: Symbol) 116 | (matches :: Boolean) 117 | | regex head tail -> rest matches 118 | 119 | instance 120 | RegexAttemptMatch (R.Lit (UnsafeMkChar head)) head tail tail True 121 | 122 | else instance 123 | RegexAttemptMatch R.Wildcard head tail tail True 124 | 125 | else instance 126 | ( Contains chars head matches 127 | , Not matches matches' 128 | ) => 129 | RegexAttemptMatch (R.NotLits chars) head tail tail matches' 130 | 131 | else instance 132 | ( Contains chars head matches 133 | ) => 134 | RegexAttemptMatch (R.Lits chars) head tail tail matches 135 | 136 | else instance 137 | ( Sym.Cons head tail str 138 | ) => 139 | RegexAttemptMatch R.Nil head tail str True 140 | 141 | else instance 142 | ( Sym.Cons head tail str 143 | , RegexAttemptGo regex1 str rest matches 144 | , RegexAttemptMatchCatResult regex2 matches rest rest' matches' 145 | ) => 146 | RegexAttemptMatch (R.Cat regex1 regex2) head tail rest' matches' 147 | 148 | else instance 149 | ( Sym.Cons head tail str 150 | , RegexAttemptGo regex1 str restLeft matches 151 | 152 | , RegexAttemptMatchAltResult regex2 matches restLeft str rest' matches' 153 | ) => 154 | RegexAttemptMatch (R.Alt regex1 regex2) head tail rest' matches' 155 | 156 | else instance 157 | ( Sym.Cons head tail sym 158 | , RegexAttemptGo regex sym restIn matches 159 | , RegexAttemptMatchManyResult matches regex sym restIn rest' matches' 160 | ) => 161 | RegexAttemptMatch (R.Many regex) head tail rest' matches' 162 | 163 | else instance 164 | RegexAttemptMatch regex head tail tail False 165 | 166 | --- RegexAttemptMatchCatResult 167 | 168 | class 169 | RegexAttemptMatchCatResult 170 | (regex :: Regex) 171 | (matched :: Boolean) 172 | (restIn :: Symbol) 173 | (restOut :: Symbol) 174 | (matches :: Boolean) 175 | | regex matched restIn -> restOut matches 176 | 177 | instance 178 | ( RegexAttemptGo regex restIn restOut matches 179 | ) => 180 | RegexAttemptMatchCatResult regex True restIn restOut matches 181 | 182 | else instance 183 | RegexAttemptMatchCatResult regex False rest rest False 184 | 185 | --- RegexAttemptMatchAltResult 186 | 187 | class 188 | RegexAttemptMatchAltResult 189 | (regex :: Regex) 190 | (matched :: Boolean) 191 | (restInLeft :: Symbol) 192 | (restInRight :: Symbol) 193 | (restOut :: Symbol) 194 | (matches :: Boolean) 195 | | regex matched restInLeft restInRight -> restOut matches 196 | 197 | instance 198 | RegexAttemptMatchAltResult regex True rest restInRight rest True 199 | 200 | else instance 201 | ( RegexAttemptGo regex restIn restOut matches 202 | ) => 203 | RegexAttemptMatchAltResult regex False restInLeft restIn restOut matches 204 | 205 | --- RegexAttemptMatchManyResult 206 | 207 | class 208 | RegexAttemptMatchManyResult 209 | (matched :: Boolean) 210 | (regex :: Regex) 211 | (backtrace :: Symbol) 212 | (restIn :: Symbol) 213 | (restOut :: Symbol) 214 | (matches :: Boolean) 215 | | matched regex backtrace restIn -> restOut matches 216 | 217 | instance regexAttemptMatchManyResultContinue :: 218 | ( RegexAttemptGo (R.Many regex) restIn restOut matches 219 | ) => 220 | RegexAttemptMatchManyResult True regex backtrace restIn restOut matches 221 | 222 | else instance regexAttemptMatchManyResultStop :: 223 | RegexAttemptMatchManyResult False regex backtrace restIn backtrace True 224 | 225 | ------------------------------------------------------------------------ 226 | --- IsFinalRegex 227 | ------------------------------------------------------------------------ 228 | 229 | class 230 | IsFinalRegex (regex :: Regex) (bool :: Boolean) 231 | | regex -> bool 232 | 233 | instance IsFinalRegex R.EndOfStr True 234 | 235 | else instance IsFinalRegex R.Nil True 236 | 237 | else instance 238 | ( IsFinalRegex regex1 bool1 239 | , IsFinalRegex regex2 bool2 240 | , Or bool1 bool2 bool 241 | ) => 242 | IsFinalRegex (R.Alt regex1 regex2) bool 243 | 244 | else instance 245 | ( IsFinalRegex regex1 bool1 246 | , IsFinalRegex regex2 bool2 247 | , And bool1 bool2 bool 248 | ) => 249 | IsFinalRegex (R.Cat regex1 regex2) bool 250 | 251 | else instance 252 | IsFinalRegex (R.Many regex) True 253 | 254 | else instance 255 | IsFinalRegex regex False 256 | 257 | ------------------------------------------------------------------------ 258 | -- Contains 259 | ------------------------------------------------------------------------ 260 | 261 | class 262 | Contains (chars :: Symbol) (char :: Symbol) (result :: Boolean) 263 | | chars char -> result 264 | 265 | instance Contains "" char False 266 | 267 | else instance 268 | ( Sym.Cons head tail sym 269 | , ContainsMatch head tail char result 270 | ) => 271 | Contains sym char result 272 | 273 | --- ContainsMatch 274 | 275 | class 276 | ContainsMatch (head :: Symbol) (tail :: Symbol) (char :: Symbol) (result :: Boolean) 277 | | head tail char -> result 278 | 279 | instance ContainsMatch char tail char True 280 | 281 | else instance 282 | ( Contains tail char result 283 | ) => 284 | ContainsMatch head tail char result 285 | 286 | -------------------------------------------------------------------------------- /src/Type/Regex/Parse.purs: -------------------------------------------------------------------------------- 1 | module Type.Regex.Parse where 2 | 3 | import Prelude 4 | 5 | import Prim.Boolean (False, True) 6 | import Prim.Int as Int 7 | import Prim.Symbol as Sym 8 | import Prim.TypeError (class Fail, Beside, Doc, Text) 9 | import Type.Char (class SymIsChar) 10 | import Type.Proxy (Proxy(..)) 11 | import Type.Regex.CST (type (~)) 12 | import Type.Regex.CST as CST 13 | 14 | -------------------------------------------------------------------------------- 15 | --- Errors 16 | -------------------------------------------------------------------------------- 17 | 18 | type MkError (err :: Doc) = Beside (Text "Regex Parse Error: ") err 19 | 20 | type ErrorMissingClose = MkError (Text "Parenthesis mismatch: Missing ')'") 21 | 22 | type ErrorMissingOpen = MkError (Text "Parenthesis mismatch: Missing '('") 23 | 24 | type ErrorIllegalQuantification = MkError (Text "Nothing to repeat") 25 | 26 | type ErrorInvalidRange = MkError (Text "Invalid character range") 27 | 28 | type UnexpectedEndOfCharClass = MkError (Text "Unexpected end of character class") 29 | 30 | type ErrorUnexpectedEnd = (Text "Regex Parse Error: Unexpected end") 31 | 32 | -------------------------------------------------------------------------------- 33 | --- ParseRegex 34 | -------------------------------------------------------------------------------- 35 | 36 | class 37 | ParseRegex (spec :: Symbol) (regex :: CST.Regex) 38 | | spec -> regex 39 | 40 | instance parseRegexInst :: 41 | ( ParseRegexAtDepth spec 0 "" regex 42 | ) => 43 | ParseRegex spec regex 44 | 45 | parseRegex :: forall @spec regex. ParseRegex spec regex => Proxy regex 46 | parseRegex = Proxy 47 | 48 | --- ParseRegexAtDepth 49 | 50 | class 51 | ParseRegexAtDepth (spec :: Symbol) (depth :: Int) (rest :: Symbol) (regex :: CST.Regex) 52 | | spec depth -> rest regex 53 | 54 | instance parseRegexAtDepth :: 55 | ( ParseRegexGo spec CST.Nil depth rest regex 56 | , ReverseRegex regex regex' 57 | ) => 58 | ParseRegexAtDepth spec depth rest regex' 59 | 60 | --- ParseRegexGo 61 | 62 | class 63 | ParseRegexGo 64 | (sym :: Symbol) 65 | (regexFrom :: CST.Regex) 66 | (depth :: Int) 67 | (rest :: Symbol) 68 | (regexTo :: CST.Regex) 69 | | sym regexFrom depth -> rest regexTo 70 | 71 | instance parseRegexGoEnd :: ParseRegexGo "" regex 0 "" regex 72 | 73 | else instance parseRegexGoEndError :: 74 | ( Fail ErrorMissingClose 75 | ) => 76 | ParseRegexGo "" regex depth "" regex 77 | 78 | else instance parseRegexGoCons :: 79 | ( Sym.Cons head tail sym 80 | , ParseRegexMatch head tail regexFrom depth rest regexTo 81 | ) => 82 | ParseRegexGo sym regexFrom depth rest regexTo 83 | 84 | --- ParseRegexMatch 85 | 86 | class 87 | ParseRegexMatch 88 | (head :: Symbol) 89 | (tail :: Symbol) 90 | (regexFrom :: CST.Regex) 91 | (depth :: Int) 92 | (rest :: Symbol) 93 | (regex :: CST.Regex) 94 | | head tail regexFrom depth -> rest regex 95 | 96 | instance parseRegexMatchGroupGroupCloseError :: 97 | ( Fail ErrorMissingOpen 98 | ) => 99 | ParseRegexMatch ")" tail regexFrom 0 rest regexTo 100 | 101 | else instance parseRegexMatchGroupClose :: 102 | ParseRegexMatch ")" tail regex depth tail regex 103 | 104 | else instance parseRegexMatchGroupStart :: 105 | ( Increment depth depthNext 106 | , ParseRegexAtDepth tail depthNext rest' regexTo' 107 | , ParseRegexGo rest' (CST.Group regexTo' ~ regexFrom) depth rest regexTo 108 | ) => 109 | ParseRegexMatch "(" tail regexFrom depth rest regexTo 110 | 111 | else instance parseRegexMatchWildcard :: 112 | ( ParseRegexGo tail (CST.Wildcard ~ regexFrom) depth rest regexTo 113 | ) => 114 | ParseRegexMatch "." tail regexFrom depth rest regexTo 115 | 116 | else instance parseRegexMatchStartOfStr :: 117 | ( ParseRegexGo tail (CST.StartOfStr ~ regexFrom) depth rest regexTo 118 | ) => 119 | ParseRegexMatch "^" tail regexFrom depth rest regexTo 120 | 121 | else instance parseRegexMatchEndOfStr :: 122 | ( ParseRegexGo tail (CST.EndOfStr ~ regexFrom) depth rest regexTo 123 | ) => 124 | ParseRegexMatch "$" tail regexFrom depth rest regexTo 125 | 126 | else instance parseRegexMatchOptional :: 127 | ( ParseRegexGo tail (CST.Optional regexHead ~ regexTail) depth rest regexTo 128 | , IsQuantifiable regexHead 129 | ) => 130 | ParseRegexMatch "?" tail (regexHead ~ regexTail) depth rest regexTo 131 | 132 | else instance 133 | ( Fail ErrorIllegalQuantification 134 | ) => 135 | ParseRegexMatch "?" tail regex depth rest regexTo 136 | 137 | else instance parseRegexMatchOneOrMore :: 138 | ( ParseRegexGo tail (CST.OneOrMore regexHead ~ regexTail) depth rest regexTo 139 | , IsQuantifiable regexHead 140 | ) => 141 | ParseRegexMatch "+" tail (regexHead ~ regexTail) depth rest regexTo 142 | 143 | else instance 144 | ( Fail ErrorIllegalQuantification 145 | ) => 146 | ParseRegexMatch "?" tail regex depth rest regexTo 147 | 148 | else instance parseRegexMatchMany :: 149 | ( ParseRegexGo tail (CST.Many regexHead ~ regexTail) depth rest regexTo 150 | , IsQuantifiable regexHead 151 | ) => 152 | ParseRegexMatch "*" tail (regexHead ~ regexTail) depth rest regexTo 153 | 154 | else instance 155 | ( Fail ErrorIllegalQuantification 156 | ) => 157 | ParseRegexMatch "?" tail regex depth rest regexTo 158 | 159 | else instance parseRegexMatchAlt :: 160 | ( ParseRegexAtDepth tail depth rest regexTo 161 | , Decrement depth depth' 162 | , ReverseRegex regex regex' 163 | ) => 164 | ParseRegexMatch "|" tail regex depth rest (CST.Alt regex' regexTo ~ CST.Nil) 165 | 166 | else instance parseRegexMatchCharClass :: 167 | ( Sym.Cons "[" tail sym 168 | , ParseCharacterClass sym rest charClass positive 169 | , ParseRegexGo rest (CST.RegexCharClass charClass positive ~ regexFrom) depth rest' regexTo 170 | ) => 171 | ParseRegexMatch "[" tail regexFrom depth rest' regexTo 172 | 173 | else instance parseRegexMatchQuote :: 174 | ( ParseRegexGo tail' (CST.Quote char ~ regexFrom) depth rest regexTo 175 | , Sym.Cons head' tail' tail 176 | , SymIsChar head' char 177 | ) => 178 | ParseRegexMatch "\\" tail regexFrom depth rest regexTo 179 | 180 | else instance parseRegexMatchLit :: 181 | ( ParseRegexGo tail (CST.Lit char ~ regexFrom) depth rest regexTo 182 | , SymIsChar head char 183 | ) => 184 | ParseRegexMatch head tail regexFrom depth rest regexTo 185 | 186 | -------------------------------------------------------------------------------- 187 | --- ParseCharClass 188 | -------------------------------------------------------------------------------- 189 | 190 | class 191 | ParseCharacterClass 192 | (sym :: Symbol) 193 | (rest :: Symbol) 194 | (chars :: CST.CharClass) 195 | (positive :: Boolean) 196 | | sym -> rest chars positive 197 | 198 | instance parseCharacterClassInst :: 199 | ( ConsOrFail (Text "Regex Parse Error: Expecting '['") "[" tail sym 200 | , ParseCharacterClassGo tail CST.CharClassNil rest positive charClass 201 | ) => 202 | ParseCharacterClass sym rest charClass positive 203 | 204 | parseCharacterClass 205 | :: forall @sym @rest @chars @positive 206 | . ParseCharacterClass sym rest chars positive 207 | => Unit 208 | parseCharacterClass = unit 209 | 210 | --- ParseCharacterClassGo 211 | 212 | class 213 | ParseCharacterClassGo 214 | (sym :: Symbol) 215 | (charsIn :: CST.CharClass) 216 | (rest :: Symbol) 217 | (positive :: Boolean) 218 | (chars :: CST.CharClass) 219 | | sym charsIn -> rest positive chars 220 | 221 | instance parseCharacterClassGoError :: 222 | ( Fail UnexpectedEndOfCharClass 223 | ) => 224 | ParseCharacterClassGo "" charsClassFrom rest positive charsClassTo 225 | 226 | else instance parseCharacterClassGoCons :: 227 | ( Sym.Cons head tail sym 228 | , ParseCharacterClassMatch head tail charsClassFrom rest positive charsClassTo 229 | ) => 230 | ParseCharacterClassGo sym charsClassFrom rest positive charsClassTo 231 | 232 | --- ParseCharacterClassMatch 233 | 234 | class 235 | ParseCharacterClassMatch 236 | (head :: Symbol) 237 | (tail :: Symbol) 238 | (charClassFrom :: CST.CharClass) 239 | (rest :: Symbol) 240 | (positive :: Boolean) 241 | (charClassTo :: CST.CharClass) 242 | | head tail charClassFrom -> rest positive charClassTo 243 | 244 | instance parseCharacterClassMatchClose :: 245 | ParseCharacterClassMatch "]" tail charClass tail True charClass 246 | 247 | else instance parseCharacterClassMatchNegate :: 248 | ( ParseCharacterClassGo tail CST.CharClassNil tail' positive charClass' 249 | ) => 250 | ParseCharacterClassMatch "^" tail CST.CharClassNil tail' False charClass' 251 | 252 | else instance parseCharacterClassMatchQuote :: 253 | ( Sym.Cons head' tail' tail 254 | , SymIsChar head' char 255 | , ParseCharacterClassGo tail' (CST.CharClassLit char charClassFrom) rest positive charClassTo 256 | ) => 257 | ParseCharacterClassMatch "\\" tail charClassFrom rest positive charClassTo 258 | 259 | else instance parseCharacterClassMatchRange :: 260 | ( ConsOrFail ErrorUnexpectedEnd charEnd' tail' tail 261 | , SymIsChar charEnd' charEnd 262 | , ParseCharacterClassGo tail' (CST.CharClassRange charStart charEnd charClassFrom) rest positive charClassTo 263 | ) => 264 | ParseCharacterClassMatch "-" tail (CST.CharClassLit charStart charClassFrom) rest positive charClassTo 265 | 266 | else instance parseCharacterClassMatchLit :: 267 | ( SymIsChar head char 268 | , ParseCharacterClassGo tail (CST.CharClassLit char charClassFrom) rest positive charClassTo 269 | ) => 270 | ParseCharacterClassMatch head tail charClassFrom rest positive charClassTo 271 | 272 | -------------------------------------------------------------------------------- 273 | --- Increment 274 | -------------------------------------------------------------------------------- 275 | 276 | class Increment (n :: Int) (n' :: Int) | n -> n' 277 | 278 | instance increment :: 279 | ( Int.Add n 1 n' 280 | ) => 281 | Increment n n' 282 | 283 | -------------------------------------------------------------------------------- 284 | --- Decrement 285 | -------------------------------------------------------------------------------- 286 | 287 | class Decrement (n :: Int) (n' :: Int) | n -> n' 288 | 289 | instance decrement :: 290 | ( Int.Add n' 1 n 291 | ) => 292 | Decrement n n' 293 | 294 | -------------------------------------------------------------------------------- 295 | --- ConsOrFail 296 | -------------------------------------------------------------------------------- 297 | 298 | class 299 | ConsOrFail (doc :: Doc) (head :: Symbol) (tail :: Symbol) (sym :: Symbol) 300 | | sym -> head tail 301 | 302 | instance consOrFailEmpty :: 303 | ( Fail doc 304 | ) => 305 | ConsOrFail doc head tail "" 306 | 307 | else instance consOrFailNonEmpty :: 308 | ( Sym.Cons head tail sym 309 | ) => 310 | ConsOrFail doc head tail sym 311 | 312 | -------------------------------------------------------------------------------- 313 | --- IsQuantifiable 314 | -------------------------------------------------------------------------------- 315 | 316 | class IsQuantifiable (regex :: CST.Regex) 317 | 318 | instance isQuantifiableLit :: 319 | IsQuantifiable (CST.Lit s) 320 | 321 | else instance isQuantifiableCharClass :: 322 | IsQuantifiable (CST.RegexCharClass positive s) 323 | 324 | else instance isQuantifiableGroup :: 325 | IsQuantifiable (CST.Group r) 326 | 327 | else instance isQuantifiableFail :: 328 | ( Fail ErrorIllegalQuantification 329 | ) => 330 | IsQuantifiable r 331 | 332 | -------------------------------------------------------------------------------- 333 | --- ReverseRegex 334 | -------------------------------------------------------------------------------- 335 | 336 | class 337 | ReverseRegex (regexFrom :: CST.Regex) (regexOut :: CST.Regex) 338 | | regexFrom -> regexOut 339 | 340 | instance reverseRegex :: 341 | ( ReverseRegexGo regex CST.Nil regexOut 342 | ) => 343 | ReverseRegex regex regexOut 344 | 345 | --- ReverseRegexGo 346 | 347 | class 348 | ReverseRegexGo (regex :: CST.Regex) (regexFrom :: CST.Regex) (regexTo :: CST.Regex) 349 | | regex regexFrom -> regexTo 350 | 351 | instance reverseRegexGoNil :: 352 | ReverseRegexGo CST.Nil a a 353 | 354 | else instance reverseRegexGoCons :: 355 | ( ReverseRegexGo tail (head ~ regexFrom) regexTo 356 | ) => 357 | ReverseRegexGo (head ~ tail) regexFrom regexTo 358 | -------------------------------------------------------------------------------- /src/Type/Regex/Print.purs: -------------------------------------------------------------------------------- 1 | module Type.Regex.Print where 2 | 3 | import Prim.Boolean (False, True) 4 | import Prim.Symbol as Sym 5 | import Prim.TypeError (class Fail, Text) 6 | import Type.Char (UnsafeMkChar) 7 | import Type.Regex.CST as CST 8 | 9 | class 10 | PrintRegex (cst :: CST.Regex) (sym :: Symbol) 11 | | cst -> sym 12 | 13 | instance PrintRegex CST.Nil "" 14 | 15 | else instance PrintRegex CST.Wildcard "*" 16 | 17 | else instance 18 | ( PrintCharClass charClass sym 19 | , Append3 "[" sym "]" sym' 20 | ) => 21 | PrintRegex (CST.RegexCharClass charClass True) sym' 22 | 23 | else instance 24 | ( PrintCharClass charClass sym 25 | , Append3 "[^" sym "]" sym' 26 | ) => 27 | PrintRegex (CST.RegexCharClass charClass False) sym' 28 | 29 | else instance PrintRegex (CST.Lit (UnsafeMkChar char)) char 30 | 31 | else instance 32 | ( Sym.Append "\\" char sym 33 | ) => 34 | PrintRegex (CST.Quote (UnsafeMkChar char)) sym 35 | 36 | else instance PrintRegex CST.EndOfStr "$" 37 | 38 | else instance PrintRegex CST.StartOfStr "^" 39 | 40 | else instance 41 | ( PrintRegex cst1 sym1 42 | , PrintRegex cst2 sym2 43 | , Sym.Append sym1 sym2 sym 44 | ) => 45 | PrintRegex (CST.Cat cst1 cst2) sym 46 | 47 | else instance 48 | ( PrintRegex cst1 sym1 49 | , PrintRegex cst2 sym2 50 | , Append3 sym1 "|" sym2 sym 51 | ) => 52 | PrintRegex (CST.Alt cst1 cst2) sym 53 | 54 | else instance 55 | ( PrintRegex cst sym 56 | , Append3 "(" sym ")" sym' 57 | ) => 58 | PrintRegex (CST.Group cst) sym' 59 | 60 | else instance 61 | ( PrintRegex cst sym 62 | , Sym.Append sym "*" sym' 63 | ) => 64 | PrintRegex (CST.Many cst) sym' 65 | 66 | else instance 67 | ( PrintRegex cst sym 68 | , Sym.Append sym "?" sym' 69 | ) => 70 | PrintRegex (CST.Optional cst) sym' 71 | 72 | else instance 73 | ( PrintRegex cst sym 74 | , Sym.Append sym "+" sym' 75 | ) => 76 | PrintRegex (CST.OneOrMore cst) sym' 77 | 78 | else instance (Fail (Text "Regex Print error")) => PrintRegex cst sym 79 | 80 | -------------------------------------------------------------------------------- 81 | --- PrintCharClass 82 | -------------------------------------------------------------------------------- 83 | 84 | class 85 | PrintCharClass (charClass :: CST.CharClass) (sym :: Symbol) 86 | | charClass -> sym 87 | 88 | instance printCharClassNil :: 89 | PrintCharClass CST.CharClassNil "" 90 | 91 | else instance printCharClassLit :: 92 | ( Sym.Append sym char sym' 93 | , PrintCharClass charClass sym 94 | ) => 95 | PrintCharClass 96 | (CST.CharClassLit (UnsafeMkChar char) charClass) 97 | sym' 98 | 99 | else instance printCharClassRange :: 100 | ( Append3 charFrom "-" charTo sym2 101 | , Sym.Append sym1 sym2 sym 102 | , PrintCharClass charClass sym1 103 | ) => 104 | PrintCharClass 105 | (CST.CharClassRange (UnsafeMkChar charFrom) (UnsafeMkChar charTo) charClass) 106 | sym 107 | 108 | else instance (Fail (Text "Regex PrintCharClass error")) => PrintCharClass charClass sym 109 | 110 | -------------------------------------------------------------------------------- 111 | --- Append3 112 | -------------------------------------------------------------------------------- 113 | 114 | class 115 | Append3 (sym1 :: Symbol) (sym2 :: Symbol) (sym3 :: Symbol) (sym :: Symbol) 116 | | sym1 sym2 sym3 -> sym 117 | 118 | instance 119 | ( Sym.Append sym1 sym2 symMid 120 | , Sym.Append symMid sym3 sym 121 | ) => 122 | Append3 sym1 sym2 sym3 sym -------------------------------------------------------------------------------- /src/Type/Regex/RegexRep.purs: -------------------------------------------------------------------------------- 1 | module Type.Regex.RegexRep where 2 | 3 | import Type.Char (Char') 4 | 5 | foreign import data Regex :: Type 6 | 7 | --- 8 | 9 | foreign import data Nil :: Regex 10 | 11 | foreign import data EndOfStr :: Regex 12 | 13 | foreign import data StartOfStr :: Regex 14 | 15 | foreign import data Wildcard :: Regex 16 | 17 | foreign import data Lit :: Char' -> Regex 18 | 19 | foreign import data NotLits :: Symbol -> Regex 20 | 21 | foreign import data Lits :: Symbol -> Regex 22 | 23 | foreign import data Many :: Regex -> Regex 24 | 25 | foreign import data Cat :: Regex -> Regex -> Regex 26 | 27 | foreign import data Alt :: Regex -> Regex -> Regex 28 | 29 | --- 30 | 31 | infixr 6 type Cat as ~ 32 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | dedent@^1.5.1: 6 | version "1.5.1" 7 | resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" 8 | integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== 9 | 10 | purs-to-md@^0.3.0: 11 | version "0.3.0" 12 | resolved "https://registry.yarnpkg.com/purs-to-md/-/purs-to-md-0.3.0.tgz#8b547d7251869455f7f2bf4f5b3f2769e0c35307" 13 | integrity sha512-+2+qyCGTqbgYNWEG1C4LzuhYjg9ipct+HcYBRqO8i+YdnrbdzDYpZxM4YRRP2OV4Zdt3MSKFd31lwr2jTkAKIA== 14 | --------------------------------------------------------------------------------