├── .github └── workflows │ └── d.yml ├── .gitignore ├── LICENSE ├── README.md ├── bin └── .gitkeep ├── codecov.yml ├── demo ├── demo.cast ├── demo.gif ├── family.cast ├── family.gif ├── sl.cast └── sl.gif ├── docs ├── GettingStarted.md ├── LoC.md └── Specification.md ├── dub.json ├── dub.selections.json ├── example ├── factorial.pro ├── family.pro ├── if.pro └── list.pro ├── lib └── .gitkeep ├── release.sh ├── src ├── app.d └── dprolog │ ├── converter │ ├── ClauseBuilder.d │ ├── Converter.d │ ├── Lexer.d │ └── Parser.d │ ├── core │ ├── Linenoise.d │ ├── Shell.d │ └── tty.d │ ├── data │ ├── AST.d │ ├── Clause.d │ ├── Command.d │ ├── Predicate.d │ ├── Term.d │ ├── Variant.d │ └── token │ │ ├── Atom.d │ │ ├── Functor.d │ │ ├── LBracket.d │ │ ├── LParen.d │ │ ├── Number.d │ │ ├── Period.d │ │ ├── RBracket.d │ │ ├── RParen.d │ │ ├── Token.d │ │ ├── Variable.d │ │ ├── operator │ │ ├── BinaryOperator.d │ │ ├── ComparisonOperator.d │ │ ├── Operator.d │ │ ├── UnaryOperator.d │ │ └── package.d │ │ └── package.d │ ├── engine │ ├── Consulter.d │ ├── Engine.d │ ├── Evaluator.d │ ├── Executor.d │ ├── Messenger.d │ ├── Reader.d │ ├── UnificationUF.d │ └── builtIn │ │ ├── BuiltIn.d │ │ ├── BuiltInCommand.d │ │ └── BuiltInPredicate.d │ ├── sl │ └── SL.d │ └── util │ ├── Either.d │ ├── GetOpt.d │ ├── Maybe.d │ ├── Message.d │ ├── Singleton.d │ ├── UnionFind.d │ ├── colorize.d │ └── functions.d └── tmp └── .gitkeep /.github/workflows/d.yml: -------------------------------------------------------------------------------- 1 | name: D 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | name: Tests with DMD and LDC 10 | strategy: 11 | matrix: 12 | compiler: 13 | - dmd-latest 14 | - ldc-latest 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | - name: Install linenoise 21 | run: |- 22 | git clone https://github.com/antirez/linenoise.git \ 23 | && cd linenoise \ 24 | && gcc -c -o linenoise.o linenoise.c \ 25 | && ar rcs liblinenoise.a linenoise.o \ 26 | && cd - \ 27 | && mv linenoise/liblinenoise.a lib 28 | - name: Install D compiler 29 | uses: dlang-community/setup-dlang@v1 30 | with: 31 | compiler: ${{ matrix.compiler }} 32 | - name: Run tests 33 | run: dub -q test 34 | 35 | codecov: 36 | name: Code coverage 37 | runs-on: ubuntu-latest 38 | needs: 39 | - test 40 | steps: 41 | - uses: actions/checkout@v2 42 | with: 43 | fetch-depth: 0 44 | - name: Install linenoise 45 | run: |- 46 | git clone https://github.com/antirez/linenoise.git \ 47 | && cd linenoise \ 48 | && gcc -c -o linenoise.o linenoise.c \ 49 | && ar rcs liblinenoise.a linenoise.o \ 50 | && cd - \ 51 | && mv linenoise/liblinenoise.a lib 52 | - name: Install D compiler 53 | uses: dlang-community/setup-dlang@v1 54 | with: 55 | compiler: dmd-latest 56 | - name: Generate code coverage report 57 | run: dub -q test --build=unittest-cov 58 | - name: Upload reports 59 | run: bash <(curl -s https://codecov.io/bash) -s "src-*.lst" 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.o 5 | *.obj 6 | *.exe 7 | __test__*__ 8 | 9 | bin/* 10 | !bin/.gitkeep 11 | lib/* 12 | !lib/.gitkeep 13 | tmp/* 14 | !tmp/.gitkeep 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-present Ark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | D-Prolog 2 | === 3 | 4 | [![](https://github.com/arkark/d-prolog/workflows/D/badge.svg)](https://github.com/arkark/d-prolog/actions) 5 | [![codecov.io](https://codecov.io/gh/arkark/d-prolog/coverage.svg?branch=master)](https://codecov.io/gh/arkark/d-prolog) 6 | [![license: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](https://github.com/arkark/d-prolog/blob/master/LICENSE) 7 | [![Lines of code](https://tokei.rs/b1/github/arkark/d-prolog?category=code)](docs/LoC.md) 8 | [![GitHub version](https://badge.fury.io/gh/arkark%2Fd-prolog.svg)](https://badge.fury.io/gh/arkark%2Fd-prolog) 9 | 10 | A Prolog implementation in D language. 11 | 12 | [![](demo/demo.gif)](https://asciinema.org/a/210436) 13 | 14 | ## Install 15 | 16 | ### Download binary 17 | 18 | Download the [latest](https://github.com/arkark/d-prolog/releases/) `dprolog` binary. 19 | 20 | ### Install from source 21 | 22 | ```sh 23 | $ git clone https://github.com/arkark/d-prolog.git 24 | $ cd d-prolog 25 | ``` 26 | and build (refer to [Development](#development)). 27 | 28 | ## Usage 29 | 30 | See `docs/`. 31 | - [Getting Started](docs/GettingStarted.md) 32 | - [Specification](docs/Specification.md) 33 | 34 | ## Development 35 | 36 | ### Requirements 37 | 38 | - [DMD](https://dlang.org/download.html#dmd): A compiler for D programming language 39 | - [DUB](http://code.dlang.org/): A package manager for D programming language 40 | - [Linenoise](https://github.com/antirez/linenoise) 41 | 42 | #### Install Linenoise 43 | 44 | ```sh 45 | $ git clone https://github.com/antirez/linenoise.git 46 | $ cd linenoise 47 | $ gcc -c -o linenoise.o linenoise.c 48 | $ ar rcs liblinenoise.a linenoise.o 49 | ``` 50 | 51 | and move `liblinenoise.a` to `lib/` or somewhere D can find it (e.g. `/usr/lib/`). 52 | 53 | ### Build 54 | 55 | ```sh 56 | $ dub build 57 | ``` 58 | The destination directory of the output binary is `bin`. 59 | 60 | ### Run 61 | 62 | With no option: 63 | ```sh 64 | $ dub run 65 | ``` 66 | 67 | With some options: 68 | ```sh 69 | $ dub run -- -f example/family.pro --verbose 70 | ``` 71 | 72 | ### Tests 73 | 74 | ```sh 75 | $ dub test 76 | ``` 77 | 78 | ### Release 79 | 80 | ```sh 81 | $ git tag 82 | $ ./release.sh 83 | ``` 84 | 85 | - Building a binary for release -> `bin/$FILE_NAME` 86 | - Calculating lines of code -> `docs/LoC.md` 87 | 88 | ### Future Work 89 | 90 | - Support for Windows 91 | - Adding more tests 92 | 93 | ## License 94 | 95 | [MIT](https://github.com/arkark/d-prolog/blob/master/LICENSE) 96 | -------------------------------------------------------------------------------- /bin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkark/d-prolog/d77ad685aa8331e95b079a036e5d7ad6286c03cf/bin/.gitkeep -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: yes 4 | 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: "70...100" 9 | 10 | status: 11 | project: off 12 | patch: off 13 | changes: no 14 | 15 | parsers: 16 | gcov: 17 | branch_detection: 18 | conditional: yes 19 | loop: yes 20 | method: no 21 | macro: no 22 | 23 | comment: 24 | layout: "header, diff" 25 | behavior: default 26 | require_changes: no 27 | -------------------------------------------------------------------------------- /demo/demo.cast: -------------------------------------------------------------------------------- 1 | {"version": 2, "width": 103, "height": 40, "timestamp": 1541519974, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}} 2 | [0.105956, "o", "\u001b[38;5;250m\u001b[48;5;240m ark \u001b[48;5;237m\u001b[38;5;240m\u001b[38;5;254m\u001b[48;5;237m ~/repos/arkark/d-prolog \u001b[48;5;161m\u001b[38;5;237m\u001b[38;5;15m\u001b[48;5;161m master \u001b[48;5;52m\u001b[38;5;161m\u001b[38;5;15m\u001b[48;5;52m ? \u001b[0m\u001b[38;5;52m\u001b[0m\u001b[0m\r\n\u001b[48;5;236m\u001b[0m\u001b[38;5;15m\u001b[48;5;236m $ \u001b[0m\u001b[38;5;236m\u001b[0m "] 3 | [0.596891, "o", "\u001b[H\u001b[2J\u001b[38;5;250m\u001b[48;5;240m ark \u001b[48;5;237m\u001b[38;5;240m\u001b[38;5;254m\u001b[48;5;237m ~/repos/arkark/d-prolog \u001b[48;5;161m\u001b[38;5;237m\u001b[38;5;15m\u001b[48;5;161m master \u001b[48;5;52m\u001b[38;5;161m\u001b[38;5;15m\u001b[48;5;52m ? \u001b[0m\u001b[38;5;52m\u001b[0m\u001b[0m\r\n\u001b[48;5;236m\u001b[0m\u001b[38;5;15m\u001b[48;5;236m $ \u001b[0m\u001b[38;5;236m\u001b[0m "] 4 | [1.825799, "o", "c"] 5 | [2.049401, "o", "a"] 6 | [2.217839, "o", "t"] 7 | [2.432264, "o", " "] 8 | [3.118102, "o", "e"] 9 | [3.347031, "o", "x"] 10 | [3.616101, "o", "ample/"] 11 | [4.082251, "o", "f"] 12 | [4.306308, "o", "a"] 13 | [4.419337, "o", "m"] 14 | [4.639544, "o", "i"] 15 | [4.771711, "o", "ly.pro "] 16 | [5.006414, "o", "\r\n"] 17 | [5.008697, "o", "male(bob).\r\nmale(tom).\r\nmale(jim).\r\nfemale(pam).\r\nfemale(liz).\r\nfemale(ann).\r\nfemale(pat).\r\n\r\nparent(pam, bob).\r\nparent(tom, bob).\r\nparent(tom, liz).\r\nparent(bob, ann).\r\nparent(bob, pat).\r\nparent(pat, jim).\r\n\r\nfather(X, Y) :- parent(X, Y), male(X).\r\nmother(X, Y) :- parent(X, Y), female(X).\r\n\r\ngrandparent(X, Y) :- parent(X, Z), parent(Z, Y).\r\ngrandfather(X, Y) :- father(X, Z), parent(Z, Y).\r\ngrandmother(X, Y) :- mother(X, Z), parent(Z, Y).\r\n"] 18 | [5.128245, "o", "\u001b[38;5;250m\u001b[48;5;240m ark \u001b[48;5;237m\u001b[38;5;240m\u001b[38;5;254m\u001b[48;5;237m ~/repos/arkark/d-prolog \u001b[48;5;161m\u001b[38;5;237m\u001b[38;5;15m\u001b[48;5;161m master \u001b[48;5;52m\u001b[38;5;161m\u001b[38;5;15m\u001b[48;5;52m ? \u001b[0m\u001b[38;5;52m\u001b[0m\u001b[0m\r\n\u001b[48;5;236m\u001b[0m\u001b[38;5;15m\u001b[48;5;236m $ \u001b[0m\u001b[38;5;236m\u001b[0m "] 19 | [6.26791, "o", "b"] 20 | [6.447996, "o", "i"] 21 | [6.550235, "o", "n"] 22 | [7.175762, "o", "/"] 23 | [7.346663, "o", "dprolog "] 24 | [8.269654, "o", "-"] 25 | [8.56431, "o", "f"] 26 | [8.693669, "o", " "] 27 | [8.865578, "o", "e"] 28 | [9.117049, "o", "x"] 29 | [9.370919, "o", "ample/"] 30 | [9.916754, "o", "f"] 31 | [10.149751, "o", "a"] 32 | [10.286778, "o", "m"] 33 | [10.49259, "o", "i"] 34 | [10.627789, "o", "ly.pro "] 35 | [11.275148, "o", "\r\n"] 36 | [11.28332, "o", "?- "] 37 | [12.772491, "o", "m"] 38 | [12.865024, "o", "a"] 39 | [13.005271, "o", "l"] 40 | [13.120609, "o", "e"] 41 | [13.481756, "o", "("] 42 | [13.788002, "o", "b"] 43 | [13.918859, "o", "o"] 44 | [14.003596, "o", "b"] 45 | [14.310867, "o", ")"] 46 | [14.605045, "o", "."] 47 | [14.775774, "o", "\r\n"] 48 | [14.776388, "o", "true.\r\n\r\n?- "] 49 | [15.497515, "o", "m"] 50 | [15.609309, "o", "a"] 51 | [15.727755, "o", "l"] 52 | [15.858325, "o", "e"] 53 | [16.189886, "o", "("] 54 | [16.377188, "o", "X"] 55 | [16.593374, "o", ")"] 56 | [16.854185, "o", "."] 57 | [17.071449, "o", "\r\n"] 58 | [17.072109, "o", "X = bob; "] 59 | [17.536144, "o", "\r\n"] 60 | [17.536546, "o", "X = tom; "] 61 | [17.917246, "o", "\r\n"] 62 | [17.917745, "o", "X = jim.\r\n\r\n?- "] 63 | [19.914706, "o", "p"] 64 | [20.028344, "o", "a"] 65 | [20.287834, "o", "r"] 66 | [20.484218, "o", "e"] 67 | [20.576298, "o", "n"] 68 | [20.724311, "o", "t"] 69 | [21.150822, "o", "("] 70 | [21.378329, "o", "X"] 71 | [21.654243, "o", ","] 72 | [21.742821, "o", " "] 73 | [22.085944, "o", "Y"] 74 | [22.300091, "o", ")"] 75 | [22.536193, "o", "."] 76 | [22.726616, "o", "\r\n"] 77 | [22.727312, "o", "X = pam, Y = bob; "] 78 | [23.527173, "o", "\r\n"] 79 | [23.527579, "o", "X = tom, Y = bob; "] 80 | [23.707322, "o", "\r\n"] 81 | [23.707706, "o", "X = tom, Y = liz; "] 82 | [24.227561, "o", "\r\n"] 83 | [24.227942, "o", "X = bob, Y = ann; "] 84 | [24.730238, "o", "\r\n"] 85 | [24.730461, "o", "X = bob, Y = pat; "] 86 | [25.246868, "o", "\r\n"] 87 | [25.247362, "o", "X = pat, Y = jim.\r\n\r\n?- "] 88 | [25.81887, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[16C"] 89 | [25.999169, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[15C"] 90 | [26.144211, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[14C"] 91 | [26.552611, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[13C"] 92 | [26.715108, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[12C"] 93 | [26.876871, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[11C"] 94 | [27.196558, "o", "\r?- parent(, Y).\u001b[0K\r\u001b[10C"] 95 | [27.575781, "o", "\r\n"] 96 | [27.576384, "o", "\u001b[31mParseError(1, 1): cannot parse \"?- parent ( , Y )\".\u001b[0m\r\n\r\n?- "] 97 | [29.248107, "o", "f"] 98 | [29.34967, "o", "a"] 99 | [29.53449, "o", "t"] 100 | [29.606153, "o", "h"] 101 | [29.695728, "o", "e"] 102 | [29.908259, "o", "r"] 103 | [30.267633, "o", "("] 104 | [30.797837, "o", "X"] 105 | [31.176349, "o", ","] 106 | [31.256979, "o", " "] 107 | [31.478604, "o", "b"] 108 | [31.628637, "o", "o"] 109 | [31.72125, "o", "b"] 110 | [31.960493, "o", ")"] 111 | [32.219512, "o", "."] 112 | [32.425079, "o", "\r\n"] 113 | [32.428481, "o", "X = tom.\r\n\r\n?- "] 114 | [33.101199, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[18C"] 115 | [33.213097, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[17C"] 116 | [33.414063, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[16C"] 117 | [33.434799, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[15C"] 118 | [33.455244, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[14C"] 119 | [33.475839, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[13C"] 120 | [33.496984, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[12C"] 121 | [33.517922, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[11C"] 122 | [33.537741, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[10C"] 123 | [33.558266, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[9C"] 124 | [33.578404, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[8C"] 125 | [33.598156, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[7C"] 126 | [33.617363, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[6C"] 127 | [33.94977, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[5C"] 128 | [34.284086, "o", "\r?- fther(X, bob).\u001b[0K\r\u001b[4C"] 129 | [34.429253, "o", "\r?- ther(X, bob).\u001b[0K\r\u001b[3C"] 130 | [34.765949, "o", "\r?- mther(X, bob).\u001b[0K\r\u001b[4C"] 131 | [34.837633, "o", "\r?- mother(X, bob).\u001b[0K\r\u001b[5C"] 132 | [35.051467, "o", "\r\n"] 133 | [35.054918, "o", "X = pam.\r\n\r\n?- "] 134 | [35.637666, "o", "\r?- mother(X, bob).\u001b[0K\r\u001b[18C"] 135 | [35.80003, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[18C"] 136 | [36.622005, "o", "\r?- father(X, bob)\u001b[0K\r\u001b[17C"] 137 | [37.11535, "o", ","] 138 | [37.195813, "o", " "] 139 | [37.857693, "o", "m"] 140 | [37.936336, "o", "o"] 141 | [38.281551, "o", "t"] 142 | [38.336848, "o", "h"] 143 | [38.460434, "o", "e"] 144 | [38.649597, "o", "r"] 145 | [39.022997, "o", "("] 146 | [39.313765, "o", "X"] 147 | [39.61535, "o", ","] 148 | [39.702621, "o", " "] 149 | [39.92365, "o", "b"] 150 | [40.100001, "o", "o"] 151 | [40.184351, "o", "b"] 152 | [40.436901, "o", ")"] 153 | [40.676215, "o", "."] 154 | [40.864727, "o", "\r\n"] 155 | [40.871611, "o", "false.\r\n\r\n?- "] 156 | [41.444083, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[34C"] 157 | [41.591448, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[33C"] 158 | [41.792464, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[32C"] 159 | [41.813381, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[31C"] 160 | [41.833103, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[30C"] 161 | [41.853635, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[29C"] 162 | [41.872537, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[28C"] 163 | [41.893452, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[27C"] 164 | [41.914248, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[26C"] 165 | [41.934034, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[25C"] 166 | [41.955042, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[24C"] 167 | [41.975588, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[23C"] 168 | [41.996072, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[22C"] 169 | [42.016568, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[21C"] 170 | [42.294719, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[20C"] 171 | [42.462024, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[19C"] 172 | [42.628873, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[18C"] 173 | [43.051451, "o", "\r?- father(X, bob) mother(X, bob).\u001b[0K\r\u001b[17C"] 174 | [43.291945, "o", "\r?- father(X, bob); mother(X, bob).\u001b[0K\r\u001b[18C"] 175 | [43.44559, "o", "\r\n"] 176 | [43.449966, "o", "X = tom; "] 177 | [44.387268, "o", "\r\n"] 178 | [44.388855, "o", "X = pam.\r\n\r\n?- "] 179 | [46.722314, "o", "h"] 180 | [46.840869, "o", "a"] 181 | [46.944764, "o", "l"] 182 | [47.153362, "o", "t"] 183 | [47.295802, "o", "."] 184 | [47.433935, "o", "\r\n"] 185 | [47.434425, "o", "\r\n"] 186 | [47.656845, "o", "\u001b[38;5;250m\u001b[48;5;240m ark \u001b[48;5;237m\u001b[38;5;240m\u001b[38;5;254m\u001b[48;5;237m ~/repos/arkark/d-prolog \u001b[48;5;161m\u001b[38;5;237m\u001b[38;5;15m\u001b[48;5;161m master \u001b[48;5;52m\u001b[38;5;161m\u001b[38;5;15m\u001b[48;5;52m ? \u001b[0m\u001b[38;5;52m\u001b[0m\u001b[0m\r\n\u001b[48;5;236m\u001b[0m\u001b[38;5;15m\u001b[48;5;236m $ \u001b[0m\u001b[38;5;236m\u001b[0m "] 187 | [48.651099, "o", "b"] 188 | [48.850868, "o", "i"] 189 | [48.952247, "o", "n"] 190 | [49.600797, "o", "/"] 191 | [51.304883, "o", "dprolog "] 192 | [52.371514, "o", "-"] 193 | [52.542759, "o", "f"] 194 | [52.683663, "o", " "] 195 | [52.932978, "o", "e"] 196 | [53.182917, "o", "x"] 197 | [53.422295, "o", "ample/"] 198 | [54.210983, "o", "l"] 199 | [54.428134, "o", "i"] 200 | [54.53285, "o", "st.pro "] 201 | [55.320033, "o", "-"] 202 | [55.473414, "o", "-"] 203 | [55.772667, "o", "v"] 204 | [55.882524, "o", "e"] 205 | [56.092889, "o", "r"] 206 | [56.228396, "o", "b"] 207 | [56.309568, "o", "o"] 208 | [56.48354, "o", "s"] 209 | [56.700026, "o", "e"] 210 | [56.80634, "o", "\r\n"] 211 | [56.81687, "o", "\u001b[36mexecute: Fact(\"member(X, [X|Xs]).\")\u001b[0m\r\n\u001b[36mexecute: Rule(\"member(X, [Y|Ys]) :- member(X, Ys).\")\u001b[0m\r\n"] 212 | [56.817347, "o", "\u001b[36mexecute: Fact(\"append([], Xs, Xs).\")\u001b[0m\r\n\u001b[36mexecute: Rule(\"append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs).\")\u001b[0m\r\n\u001b[36mexecute: Rule(\"reverse(Xs, Ys) :- reverse2(Xs, [], Ys).\")\u001b[0m\r\n\u001b[36mexecute: Fact(\"reverse2([], Ys, Ys).\")\u001b[0m\r\n"] 213 | [56.817732, "o", "\u001b[36mexecute: Rule(\"reverse2([X|Xs], Ys, Zs) :- reverse2(Xs, [X|Ys], Zs).\")\u001b[0m\r\n?- "] 214 | [59.235075, "o", "m"] 215 | [59.320026, "o", "e"] 216 | [59.416728, "o", "m"] 217 | [59.671875, "o", "b"] 218 | [59.734305, "o", "e"] 219 | [59.925748, "o", "r"] 220 | [60.290754, "o", "("] 221 | [60.806269, "o", "1"] 222 | [60.958308, "o", ","] 223 | [61.070704, "o", " "] 224 | [61.401472, "o", "["] 225 | [61.742643, "o", "1"] 226 | [61.941634, "o", ","] 227 | [62.030854, "o", " "] 228 | [62.142635, "o", "2"] 229 | [62.253128, "o", ","] 230 | [62.326139, "o", " "] 231 | [62.480896, "o", "3"] 232 | [62.741708, "o", "]"] 233 | [63.068607, "o", ")"] 234 | [63.316001, "o", "."] 235 | [63.482327, "o", "\r\n"] 236 | [63.483223, "o", "\u001b[36mexecute: Query(\"?- member(1, [1, 2, 3]).\")\u001b[0m\r\n"] 237 | [63.483351, "o", "true.\r\n\r\n"] 238 | [63.483613, "o", "?- "] 239 | [64.575095, "o", "\r?- member(1, [1, 2, 3]).\u001b[0K\r\u001b[24C"] 240 | [64.692413, "o", "\r?- member(1, [1, 2, 3]).\u001b[0K\r\u001b[23C"] 241 | [64.894886, "o", "\r?- member(1, [1, 2, 3]).\u001b[0K\r\u001b[22C"] 242 | [64.915024, "o", "\r?- member(1, [1, 2, 3]).\u001b[0K\r\u001b[21C"] 243 | [64.934916, "o", "\r?- member(1, [1, 2, 3]).\u001b[0K\r\u001b[20C"] 244 | [64.955588, "o", "\r?- member(1, [1, 2, 3]).\u001b[0K\r\u001b[19C"] 245 | [64.976697, "o", "\r?- member(1, [1, 2, 3]).\u001b[0K\r\u001b[18C"] 246 | [64.997274, "o", "\r?- member(1, [1, 2, 3]).\u001b[0K\r\u001b[17C"] 247 | [65.01654, "o", "\r?- member(1, [1, 2, 3]).\u001b[0K\r\u001b[16C"] 248 | [65.035969, "o", "\r?- member(1, [1, 2, 3]).\u001b[0K\r\u001b[15C"] 249 | [65.054998, "o", "\r?- member(1, [1, 2, 3]).\u001b[0K\r\u001b[14C"] 250 | [65.075692, "o", "\r?- member(1, [1, 2, 3]).\u001b[0K\r\u001b[13C"] 251 | [65.095654, "o", "\r?- member(1, [1, 2, 3]).\u001b[0K\r\u001b[12C"] 252 | [65.117011, "o", "\r?- member(1, [1, 2, 3]).\u001b[0K\r\u001b[11C"] 253 | [65.654673, "o", "\r?- member(, [1, 2, 3]).\u001b[0K\r\u001b[10C"] 254 | [65.91653, "o", "\r?- member(X, [1, 2, 3]).\u001b[0K\r\u001b[11C"] 255 | [66.170625, "o", "\r\n"] 256 | [66.17159, "o", "\u001b[36mexecute: Query(\"?- member(X, [1, 2, 3]).\")\u001b[0m\r\n"] 257 | [66.171969, "o", "X = 1; "] 258 | [66.611389, "o", "\r\n"] 259 | [66.61171, "o", "X = 2; "] 260 | [66.810073, "o", "\r\n"] 261 | [66.812321, "o", "X = 3.\r\n\r\n?- "] 262 | [69.496046, "o", "r"] 263 | [69.704995, "o", "e"] 264 | [69.882838, "o", "v"] 265 | [69.905166, "o", "e"] 266 | [70.13014, "o", "r"] 267 | [70.378219, "o", "s"] 268 | [70.603768, "o", "e"] 269 | [71.607674, "o", "("] 270 | [72.039491, "o", "["] 271 | [72.384278, "o", "1"] 272 | [72.599432, "o", ","] 273 | [72.6951, "o", " "] 274 | [72.832151, "o", "2"] 275 | [72.948499, "o", ","] 276 | [73.044873, "o", " "] 277 | [73.15409, "o", "3"] 278 | [73.458646, "o", "]"] 279 | [74.040569, "o", ","] 280 | [74.112668, "o", " "] 281 | [74.41641, "o", "X"] 282 | [74.733426, "o", "s"] 283 | [75.054698, "o", ")"] 284 | [75.320468, "o", "."] 285 | [75.541075, "o", "\r\n"] 286 | [75.542098, "o", "\u001b[36mexecute: Query(\"?- reverse([1, 2, 3], Xs).\")\u001b[0m\r\n"] 287 | [75.545177, "o", "Xs = [3, 2, 1].\r\n\r\n?- "] 288 | [76.936945, "o", "\r?- reverse([1, 2, 3], Xs).\u001b[0K\r\u001b[26C"] 289 | [77.115357, "o", "\r?- reverse([1, 2, 3], Xs).\u001b[0K\r\u001b[25C"] 290 | [77.316674, "o", "\r?- reverse([1, 2, 3], Xs).\u001b[0K\r\u001b[24C"] 291 | [78.445083, "o", "\r?- reverse([1, 2, 3], X).\u001b[0K\r\u001b[23C"] 292 | [78.610332, "o", "\r?- reverse([1, 2, 3], ).\u001b[0K\r\u001b[22C"] 293 | [79.203467, "o", "\r?- reverse([1, 2, 3], [).\u001b[0K\r\u001b[23C"] 294 | [79.591521, "o", "\r?- reverse([1, 2, 3], [3).\u001b[0K\r\u001b[24C"] 295 | [80.048172, "o", "\r?- reverse([1, 2, 3], [3|).\u001b[0K\r\u001b[25C"] 296 | [81.180947, "o", "\r?- reverse([1, 2, 3], [3|X).\u001b[0K\r\u001b[26C"] 297 | [81.594345, "o", "\r?- reverse([1, 2, 3], [3|Xs).\u001b[0K\r\u001b[27C"] 298 | [82.533696, "o", "\r?- reverse([1, 2, 3], [3|Xs]).\u001b[0K\r\u001b[28C"] 299 | [83.03637, "o", "\r\n"] 300 | [83.037665, "o", "\u001b[36mexecute: Query(\"?- reverse([1, 2, 3], [3|Xs]).\")\u001b[0m\r\n"] 301 | [83.042515, "o", "Xs = [2, 1].\r\n\r\n?- "] 302 | [85.613943, "o", "["] 303 | [86.606655, "o", "'"] 304 | [86.908985, "o", "e"] 305 | [87.135659, "o", "x"] 306 | [87.363946, "o", "a"] 307 | [87.503548, "o", "m"] 308 | [87.668895, "o", "p"] 309 | [87.881514, "o", "l"] 310 | [87.993869, "o", "e"] 311 | [88.665459, "o", "/"] 312 | [89.110806, "o", "f"] 313 | [89.322693, "o", "a"] 314 | [89.545781, "o", "c"] 315 | [89.809475, "o", "t"] 316 | [89.918686, "o", "o"] 317 | [90.064567, "o", "r"] 318 | [90.159178, "o", "i"] 319 | [90.330416, "o", "a"] 320 | [90.440427, "o", "l"] 321 | [91.002412, "o", "."] 322 | [91.280545, "o", "p"] 323 | [91.551304, "o", "r"] 324 | [91.632009, "o", "o"] 325 | [93.007362, "o", "'"] 326 | [93.81333, "o", "]"] 327 | [94.20661, "o", "."] 328 | [94.47279, "o", "\r\n"] 329 | [94.473911, "o", "\u001b[36mexecute: Query(\"?- ['example/factorial.pro'].\")\u001b[0m\r\n"] 330 | [94.477917, "o", "\u001b[36mexecute: Rule(\"factorial(N, Result) :- factorial2(N, 1, Result).\")\u001b[0m\r\n\u001b[36mexecute: Fact(\"factorial2(0, Result, Result).\")\u001b[0m\r\n"] 331 | [94.478323, "o", "\u001b[36mexecute: Rule(\"factorial2(N, Acc, Result) :- ( >(N, 0) , ( is(Acc1, *(N, Acc)) , ( is(N1, -(N, 1)) , factorial2(N1, Acc1, Result) ) ) ).\")\u001b[0m\r\n\r\n?- "] 332 | [96.858776, "o", "f"] 333 | [96.93287, "o", "a"] 334 | [97.154438, "o", "c"] 335 | [97.340839, "o", "t"] 336 | [97.404682, "o", "o"] 337 | [97.595542, "o", "r"] 338 | [97.666991, "o", "i"] 339 | [97.861915, "o", "a"] 340 | [97.944278, "o", "l"] 341 | [98.262331, "o", "("] 342 | [98.946193, "o", "3"] 343 | [99.089616, "o", ","] 344 | [99.186281, "o", " "] 345 | [99.495625, "o", "X"] 346 | [99.728698, "o", ")"] 347 | [99.994713, "o", "."] 348 | [100.164514, "o", "\r\n"] 349 | [100.165354, "o", "\u001b[36mexecute: Query(\"?- factorial(3, X).\")\u001b[0m\r\n"] 350 | [100.174135, "o", "X = 6.\r\n\r\n?- "] 351 | [101.040357, "o", "\r?- factorial(3, X).\u001b[0K\r\u001b[19C"] 352 | [101.206224, "o", "\r?- factorial(3, X).\u001b[0K\r\u001b[18C"] 353 | [101.37729, "o", "\r?- factorial(3, X).\u001b[0K\r\u001b[17C"] 354 | [101.568129, "o", "\r?- factorial(3, X).\u001b[0K\r\u001b[16C"] 355 | [101.738415, "o", "\r?- factorial(3, X).\u001b[0K\r\u001b[15C"] 356 | [101.919533, "o", "\r?- factorial(3, X).\u001b[0K\r\u001b[14C"] 357 | [102.947789, "o", "\r?- factorial(, X).\u001b[0K\r\u001b[13C"] 358 | [103.146521, "o", "\r?- factorial(1, X).\u001b[0K\r\u001b[14C"] 359 | [103.282415, "o", "\r?- factorial(10, X).\u001b[0K\r\u001b[15C"] 360 | [103.448534, "o", "\r?- factorial(100, X).\u001b[0K\r\u001b[16C"] 361 | [103.648019, "o", "\r\n"] 362 | [103.648735, "o", "\u001b[36mexecute: Query(\"?- factorial(100, X).\")\u001b[0m\r\n"] 363 | [108.541023, "o", "X = 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000.\r\n\r\n?- "] 364 | [109.727487, "o", "h"] 365 | [109.852596, "o", "a"] 366 | [109.957472, "o", "l"] 367 | [110.185805, "o", "t"] 368 | [110.332949, "o", "."] 369 | [110.434679, "o", "\r\n"] 370 | [110.435181, "o", "\u001b[36mexecute: Query(\"?- halt.\")\u001b[0m\r\n\r\n"] 371 | [110.559598, "o", "\u001b[38;5;250m\u001b[48;5;240m ark \u001b[48;5;237m\u001b[38;5;240m\u001b[38;5;254m\u001b[48;5;237m ~/repos/arkark/d-prolog \u001b[48;5;161m\u001b[38;5;237m\u001b[38;5;15m\u001b[48;5;161m master \u001b[48;5;52m\u001b[38;5;161m\u001b[38;5;15m\u001b[48;5;52m ? \u001b[0m\u001b[38;5;52m\u001b[0m\u001b[0m\r\n\u001b[48;5;236m\u001b[0m\u001b[38;5;15m\u001b[48;5;236m $ \u001b[0m\u001b[38;5;236m\u001b[0m "] 372 | [112.612721, "o", "exit\r\n"] 373 | -------------------------------------------------------------------------------- /demo/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkark/d-prolog/d77ad685aa8331e95b079a036e5d7ad6286c03cf/demo/demo.gif -------------------------------------------------------------------------------- /demo/family.cast: -------------------------------------------------------------------------------- 1 | {"version": 2, "width": 103, "height": 44, "timestamp": 1541406640, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}} 2 | [0.101745, "o", "\u001b[38;5;250m\u001b[48;5;240m ark \u001b[48;5;237m\u001b[38;5;240m\u001b[38;5;254m\u001b[48;5;237m ~/repos/arkark/d-prolog \u001b[48;5;161m\u001b[38;5;237m\u001b[38;5;15m\u001b[48;5;161m master \u001b[48;5;52m\u001b[38;5;161m\u001b[38;5;15m\u001b[48;5;52m ? \u001b[0m\u001b[38;5;52m\u001b[0m\u001b[0m\r\n\u001b[48;5;236m\u001b[0m\u001b[38;5;15m\u001b[48;5;236m $ \u001b[0m\u001b[38;5;236m\u001b[0m "] 3 | [1.726645, "o", "\u001b[H\u001b[2J\u001b[38;5;250m\u001b[48;5;240m ark \u001b[48;5;237m\u001b[38;5;240m\u001b[38;5;254m\u001b[48;5;237m ~/repos/arkark/d-prolog \u001b[48;5;161m\u001b[38;5;237m\u001b[38;5;15m\u001b[48;5;161m master \u001b[48;5;52m\u001b[38;5;161m\u001b[38;5;15m\u001b[48;5;52m ? \u001b[0m\u001b[38;5;52m\u001b[0m\u001b[0m\r\n\u001b[48;5;236m\u001b[0m\u001b[38;5;15m\u001b[48;5;236m $ \u001b[0m\u001b[38;5;236m\u001b[0m "] 4 | [2.475649, "o", "c"] 5 | [2.709371, "o", "a"] 6 | [2.85092, "o", "t"] 7 | [2.939628, "o", " "] 8 | [3.620323, "o", "e"] 9 | [3.815787, "o", "x"] 10 | [4.063583, "o", "ample/"] 11 | [4.698563, "o", "f"] 12 | [4.781631, "o", "amily.pro "] 13 | [4.973031, "o", "\r\n"] 14 | [4.975486, "o", "male(bob).\r\nmale(tom).\r\nmale(jim).\r\nfemale(pam).\r\nfemale(liz).\r\nfemale(ann).\r\nfemale(pat).\r\n\r\nparent(pam, bob).\r\nparent(tom, bob).\r\nparent(tom, liz).\r\nparent(bob, ann).\r\nparent(bob, pat).\r\nparent(pat, jim).\r\n\r\nfather(X, Y) :- parent(X, Y), male(X).\r\nmother(X, Y) :- parent(X, Y), female(X).\r\n\r\ngrandparent(X, Y) :- parent(X, Z), parent(Z, Y).\r\ngrandfather(X, Y) :- father(X, Z), parent(Z, Y).\r\ngrandmother(X, Y) :- mother(X, Z), parent(Z, Y).\r\n"] 15 | [5.082935, "o", "\u001b[38;5;250m\u001b[48;5;240m ark \u001b[48;5;237m\u001b[38;5;240m\u001b[38;5;254m\u001b[48;5;237m ~/repos/arkark/d-prolog \u001b[48;5;161m\u001b[38;5;237m\u001b[38;5;15m\u001b[48;5;161m master \u001b[48;5;52m\u001b[38;5;161m\u001b[38;5;15m\u001b[48;5;52m ? \u001b[0m\u001b[38;5;52m\u001b[0m\u001b[0m\r\n\u001b[48;5;236m\u001b[0m\u001b[38;5;15m\u001b[48;5;236m $ \u001b[0m\u001b[38;5;236m\u001b[0m "] 16 | [6.928046, "o", "b"] 17 | [7.112929, "o", "i"] 18 | [7.198495, "o", "n"] 19 | [8.085945, "o", "/"] 20 | [9.377003, "o", "d"] 21 | [9.577073, "o", "p"] 22 | [9.704852, "o", "rolog "] 23 | [10.787956, "o", "-"] 24 | [10.959846, "o", "f"] 25 | [11.550678, "o", " "] 26 | [12.030042, "o", "e"] 27 | [12.298222, "o", "x"] 28 | [12.57734, "o", "ample/"] 29 | [13.242836, "o", "f"] 30 | [13.32952, "o", "amily.pro "] 31 | [13.636515, "o", "\r\n"] 32 | [13.647408, "o", "?- "] 33 | [15.654525, "o", "m"] 34 | [15.785807, "o", "a"] 35 | [15.933867, "o", "l"] 36 | [16.084489, "o", "e"] 37 | [16.499529, "o", "("] 38 | [16.80712, "o", "b"] 39 | [16.951536, "o", "o"] 40 | [17.060493, "o", "b"] 41 | [17.370617, "o", ")"] 42 | [17.964183, "o", "."] 43 | [18.292266, "o", "\r\n"] 44 | [18.292592, "o", "true.\r\n\r\n"] 45 | [18.2928, "o", "?- "] 46 | [20.290877, "o", "m"] 47 | [20.466678, "o", "a"] 48 | [20.620727, "o", "l"] 49 | [20.777553, "o", "e"] 50 | [21.293494, "o", "("] 51 | [21.54731, "o", "X"] 52 | [21.78693, "o", ")"] 53 | [22.174673, "o", "."] 54 | [22.673664, "o", "\r\n"] 55 | [22.674098, "o", "X = bob; "] 56 | [23.947819, "o", "\r\n"] 57 | [23.947959, "o", "X = tom; "] 58 | [24.563028, "o", "\r\n"] 59 | [24.563578, "o", "X = jim.\r\n\r\n?- "] 60 | [26.98513, "o", "p"] 61 | [27.064097, "o", "a"] 62 | [27.381552, "o", "r"] 63 | [27.585691, "o", "e"] 64 | [27.685853, "o", "n"] 65 | [27.908163, "o", "t"] 66 | [28.970302, "o", "("] 67 | [29.341044, "o", "X"] 68 | [29.642598, "o", ","] 69 | [29.706742, "o", " "] 70 | [30.030661, "o", "Y"] 71 | [30.268788, "o", ")"] 72 | [30.556745, "o", "."] 73 | [30.815578, "o", "\r\n"] 74 | [30.816303, "o", "X = pam, Y = bob; "] 75 | [31.583523, "o", "\r\n"] 76 | [31.583668, "o", "X = tom, Y = bob; "] 77 | [31.963484, "o", "\r\n"] 78 | [31.96363, "o", "X = tom, Y = liz; "] 79 | [32.390916, "o", "\r\n"] 80 | [32.391078, "o", "X = bob, Y = ann; "] 81 | [32.791977, "o", "\r\n"] 82 | [32.792122, "o", "X = bob, Y = pat; "] 83 | [33.207261, "o", "\r\n"] 84 | [33.207416, "o", "X = pat, Y = jim.\r\n\r\n?- "] 85 | [34.327648, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[16C"] 86 | [34.523015, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[15C"] 87 | [34.723926, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[14C"] 88 | [34.744531, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[13C"] 89 | [34.76575, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[12C"] 90 | [34.787333, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[11C"] 91 | [34.806909, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[10C"] 92 | [34.827621, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[9C"] 93 | [34.848161, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[8C"] 94 | [35.106643, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[9C"] 95 | [35.276538, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[10C"] 96 | [35.463959, "o", "\r?- parent(X, Y).\u001b[0K\r\u001b[11C"] 97 | [35.83286, "o", "\r?- parent(, Y).\u001b[0K\r\u001b[10C"] 98 | [36.109446, "o", "\r\n"] 99 | [36.109867, "o", "\u001b[31mParseError(1, 1): cannot parse \"?- parent ( , Y )\".\u001b[0m\r\n\r\n?- "] 100 | [38.402869, "o", "p"] 101 | [38.48493, "o", "a"] 102 | [38.77299, "o", "r"] 103 | [38.965131, "o", "e"] 104 | [39.058149, "o", "n"] 105 | [39.26336, "o", "t"] 106 | [40.231969, "o", "("] 107 | [40.6882, "o", "p"] 108 | [40.877739, "o", "a"] 109 | [40.996444, "o", "m"] 110 | [41.304752, "o", ","] 111 | [41.416786, "o", " "] 112 | [41.711411, "o", "b"] 113 | [41.91454, "o", "o"] 114 | [42.00669, "o", "b"] 115 | [42.362589, "o", ")"] 116 | [43.030994, "o", "."] 117 | [43.258514, "o", "\r\n"] 118 | [43.259132, "o", "true.\r\n\r\n?- "] 119 | [45.972769, "o", "\r?- parent(pam, bob).\u001b[0K\r\u001b[20C"] 120 | [46.127285, "o", "\r?- parent(pam, bob).\u001b[0K\r\u001b[19C"] 121 | [46.32807, "o", "\r?- parent(pam, bob).\u001b[0K\r\u001b[18C"] 122 | [46.348786, "o", "\r?- parent(pam, bob).\u001b[0K\r\u001b[17C"] 123 | [46.369399, "o", "\r?- parent(pam, bob).\u001b[0K\r\u001b[16C"] 124 | [46.389307, "o", "\r?- parent(pam, bob).\u001b[0K\r\u001b[15C"] 125 | [46.41029, "o", "\r?- parent(pam, bob).\u001b[0K\r\u001b[14C"] 126 | [46.430287, "o", "\r?- parent(pam, bob).\u001b[0K\r\u001b[13C"] 127 | [46.449853, "o", "\r?- parent(pam, bob).\u001b[0K\r\u001b[12C"] 128 | [46.718533, "o", "\r?- parent(pam, bob).\u001b[0K\r\u001b[11C"] 129 | [46.891141, "o", "\r?- parent(pam, bob).\u001b[0K\r\u001b[10C"] 130 | [47.057777, "o", "\r?- parent(pam, bob).\u001b[0K\r\u001b[9C"] 131 | [47.390225, "o", "\r?- paren(pam, bob).\u001b[0K\r\u001b[8C"] 132 | [47.590301, "o", "\r?- pare(pam, bob).\u001b[0K\r\u001b[7C"] 133 | [47.610873, "o", "\r?- par(pam, bob).\u001b[0K\r\u001b[6C"] 134 | [47.6311, "o", "\r?- pa(pam, bob).\u001b[0K\r\u001b[5C"] 135 | [47.651164, "o", "\r?- p(pam, bob).\u001b[0K\r\u001b[4C"] 136 | [47.671137, "o", "\r?- (pam, bob).\u001b[0K\r\u001b[3C"] 137 | [48.306409, "o", "\r?- f(pam, bob).\u001b[0K\r\u001b[4C"] 138 | [48.402333, "o", "\r?- fa(pam, bob).\u001b[0K\r\u001b[5C"] 139 | [48.571269, "o", "\r?- fat(pam, bob).\u001b[0K\r\u001b[6C"] 140 | [48.664923, "o", "\r?- fath(pam, bob).\u001b[0K\r\u001b[7C"] 141 | [48.783608, "o", "\r?- fathe(pam, bob).\u001b[0K\r\u001b[8C"] 142 | [48.985393, "o", "\r?- father(pam, bob).\u001b[0K\r\u001b[9C"] 143 | [49.247204, "o", "\r?- father(pam, bob).\u001b[0K\r\u001b[10C"] 144 | [49.552002, "o", "\r?- father(am, bob).\u001b[0K\r\u001b[10C"] 145 | [49.773865, "o", "\r?- father(m, bob).\u001b[0K\r\u001b[10C"] 146 | [49.944465, "o", "\r?- father(, bob).\u001b[0K\r\u001b[10C"] 147 | [50.387103, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[11C"] 148 | [50.635643, "o", "\r\n"] 149 | [50.637954, "o", "X = tom.\r\n\r\n?- "] 150 | [51.702652, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[18C"] 151 | [51.860804, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[17C"] 152 | [52.060608, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[16C"] 153 | [52.081868, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[15C"] 154 | [52.101733, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[14C"] 155 | [52.12318, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[13C"] 156 | [52.143632, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[12C"] 157 | [52.16429, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[11C"] 158 | [52.185092, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[10C"] 159 | [52.205835, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[9C"] 160 | [52.22572, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[8C"] 161 | [52.549981, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[7C"] 162 | [52.742864, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[6C"] 163 | [52.958521, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[5C"] 164 | [53.324954, "o", "\r?- fther(X, bob).\u001b[0K\r\u001b[4C"] 165 | [53.441715, "o", "\r?- ther(X, bob).\u001b[0K\r\u001b[3C"] 166 | [53.813113, "o", "\r?- mther(X, bob).\u001b[0K\r\u001b[4C"] 167 | [53.882721, "o", "\r?- mother(X, bob).\u001b[0K\r\u001b[5C"] 168 | [54.132678, "o", "\r\n"] 169 | [54.134835, "o", "X = pam.\r\n\r\n?- "] 170 | [56.231142, "o", "\r?- mother(X, bob).\u001b[0K\r\u001b[18C"] 171 | [56.4252, "o", "\r?- father(X, bob).\u001b[0K\r\u001b[18C"] 172 | [57.264823, "o", "\r?- father(X, bob)\u001b[0K\r\u001b[17C"] 173 | [57.80454, "o", ","] 174 | [57.882465, "o", " "] 175 | [58.156646, "o", "m"] 176 | [58.245194, "o", "o"] 177 | [58.629368, "o", "t"] 178 | [58.676762, "o", "h"] 179 | [58.809419, "o", "e"] 180 | [58.992562, "o", "r"] 181 | [59.43734, "o", "("] 182 | [59.727187, "o", "X"] 183 | [60.071379, "o", ","] 184 | [60.128467, "o", " "] 185 | [60.388058, "o", "b"] 186 | [60.56723, "o", "o"] 187 | [60.652946, "o", "b"] 188 | [60.941922, "o", ")"] 189 | [61.20562, "o", "."] 190 | [61.429858, "o", "\r\n"] 191 | [61.435322, "o", "false.\r\n\r\n?- "] 192 | [61.953225, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[34C"] 193 | [62.061225, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[33C"] 194 | [62.264124, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[32C"] 195 | [62.280971, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[31C"] 196 | [62.301389, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[30C"] 197 | [62.321386, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[29C"] 198 | [62.34226, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[28C"] 199 | [62.363147, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[27C"] 200 | [62.38334, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[26C"] 201 | [62.403909, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[25C"] 202 | [62.424104, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[24C"] 203 | [62.444127, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[23C"] 204 | [62.463762, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[22C"] 205 | [62.484794, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[21C"] 206 | [62.505711, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[20C"] 207 | [62.525617, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[19C"] 208 | [62.545342, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[18C"] 209 | [62.566442, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[17C"] 210 | [62.895312, "o", "\r?- father(X, bob), mother(X, bob).\u001b[0K\r\u001b[18C"] 211 | [63.42063, "o", "\r?- father(X, bob) mother(X, bob).\u001b[0K\r\u001b[17C"] 212 | [63.621349, "o", "\r?- father(X, bob); mother(X, bob).\u001b[0K\r\u001b[18C"] 213 | [63.838108, "o", "\r\n"] 214 | [63.841316, "o", "X = tom; "] 215 | [64.935717, "o", "\r\n"] 216 | [64.936815, "o", "X = pam.\r\n\r\n?- "] 217 | [70.388598, "o", "h"] 218 | [70.494486, "o", "a"] 219 | [70.655286, "o", "l"] 220 | [70.922817, "o", "t"] 221 | [71.646396, "o", "."] 222 | [71.899462, "o", "\r\n"] 223 | [71.900073, "o", "\r\n"] 224 | [72.036462, "o", "\u001b[38;5;250m\u001b[48;5;240m ark \u001b[48;5;237m\u001b[38;5;240m\u001b[38;5;254m\u001b[48;5;237m ~/repos/arkark/d-prolog \u001b[48;5;161m\u001b[38;5;237m\u001b[38;5;15m\u001b[48;5;161m master \u001b[48;5;52m\u001b[38;5;161m\u001b[38;5;15m\u001b[48;5;52m ? \u001b[0m\u001b[38;5;52m\u001b[0m\u001b[0m\r\n\u001b[48;5;236m\u001b[0m\u001b[38;5;15m\u001b[48;5;236m $ \u001b[0m\u001b[38;5;236m\u001b[0m "] 225 | [76.777359, "o", "exit\r\n"] 226 | -------------------------------------------------------------------------------- /demo/family.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkark/d-prolog/d77ad685aa8331e95b079a036e5d7ad6286c03cf/demo/family.gif -------------------------------------------------------------------------------- /demo/sl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkark/d-prolog/d77ad685aa8331e95b079a036e5d7ad6286c03cf/demo/sl.gif -------------------------------------------------------------------------------- /docs/GettingStarted.md: -------------------------------------------------------------------------------- 1 | Getting Started 2 | === 3 | 4 | ## Start D-Prolog 5 | 6 | ```sh 7 | $ dprolog 8 | ?- 9 | ``` 10 | 11 | With an initialization file: 12 | ```sh 13 | $ dprolog -f example/family.pro 14 | ?- 15 | ``` 16 | ```prolog 17 | ?- male(X). 18 | X = bob; 19 | X = tom; 20 | X = jim. 21 | ``` 22 | 23 | ## Stop D-Prolog 24 | 25 | ```prolog 26 | ?- halt. 27 | ``` 28 | or input `ctrl+c` or `ctrl+d` to stop D-Prolog. 29 | 30 | ## Options 31 | 32 | - `-h`, `--help`: Print a help message 33 | - `-v`, `--version`: Print version of dprolog 34 | - `-f`, `--file=VALUE`: Read `VALUE` as an initialization file 35 | - `--verbose`: Print diagnostic output 36 | 37 | ## Example Prolog files 38 | 39 | - `example/family.pro` 40 | - `example/list.pro` 41 | - `example/factorial.pro` 42 | - `example/if.pro` 43 | 44 | ## Load a file while running 45 | 46 | Input a query `?- [].` while running as follows: 47 | ```prolog 48 | ?- [`file.pro`]. 49 | ``` 50 | ```prolog 51 | ?- [`/path/to/file.pro`]. 52 | ``` 53 | 54 | ## Add rules from the console 55 | 56 | Input a query `?- [user].`, and you will be able to add rules from the console: 57 | ```prolog 58 | ?- [user]. 59 | |: hoge(poyo). 60 | |: 61 | ``` 62 | Input `ctrl+c` or `ctrl+d` to exit from the mode for adding rules. 63 | 64 | ```prolog 65 | ?- hoge(X). 66 | X = poyo. 67 | ``` 68 | 69 | ## Comments 70 | 71 | The %-style line comments are supported. 72 | 73 | ```prolog 74 | ?- X = 1. % This is a comment. 75 | X = 1. 76 | ``` 77 | 78 | ## Lists 79 | 80 | ```prolog 81 | ?- X = [a, b, c]. 82 | X = [a, b, c]. 83 | 84 | ?- X = [a | [b, c]]. 85 | X = [a, b, c]. 86 | ``` 87 | 88 | ## Integers and Arithmetic Operations 89 | 90 | ```prolog 91 | ?- X = 10. % A decimal literal 92 | X = 10. 93 | 94 | ?- X = 0b1010. % A binary literal 95 | X = 10. 96 | 97 | ?- X = 0xff. % A hexadecimal literal 98 | X = 255. 99 | ``` 100 | 101 | ```prolog 102 | ?- X is 1 + 2. 103 | X = 3. 104 | 105 | ?- X = 10, Y is X * X - 1. 106 | X = 10, Y = 99. 107 | 108 | ?- 10 < 100. 109 | true. 110 | ``` 111 | 112 | ## Conjunctions and Disjunctions 113 | 114 | ```sh 115 | $ dprolog -f list.pro 116 | ``` 117 | 118 | Conjunctions: 119 | ```prolog 120 | ?- member(X, [1, 2, 3]), member(X, [3, 4]). 121 | X = 3; 122 | false. 123 | ``` 124 | 125 | Disjunctions: 126 | ```prolog 127 | ?- member(X, [1, 2, 3]); member(X, [3, 4]). 128 | X = 1; 129 | X = 2; 130 | X = 3; 131 | X = 3; 132 | X = 4; 133 | false. 134 | ``` 135 | 136 | Conjuctions and disjunctions: 137 | ```prolog 138 | ?- member(X, [1, 2, 3]); member(X, [3, 4]), X > 3. 139 | X = 1; 140 | X = 2; 141 | X = 3; 142 | X = 4. 143 | 144 | ?- (member(X, [1, 2, 3]); member(X, [3, 4])), X > 3. 145 | X = 4. 146 | ``` 147 | 148 | ## Cut Operator 149 | 150 | ```prolog 151 | ?- X = 1; X = 2. 152 | X = 1; 153 | X = 2. 154 | 155 | ?- X = 1, !; X = 2. 156 | X = 1. 157 | ``` 158 | 159 | Load `example/if.pro`, then 160 | ```prolog 161 | ?- if(true, X = 1, X = 2). 162 | X = 1. 163 | 164 | ?- if(false, X = 1, X = 2). 165 | X = 2. 166 | ``` 167 | 168 | --- 169 | 170 | If you want to know more about D-Prolog, see [Specification](Specification.md). -------------------------------------------------------------------------------- /docs/LoC.md: -------------------------------------------------------------------------------- 1 | ## Lines of Code 2 | 3 | ```sh 4 | $ tokei 5 | ------------------------------------------------------------------------------- 6 | Language Files Lines Code Comments Blanks 7 | ------------------------------------------------------------------------------- 8 | D 49 4409 3671 58 680 9 | JSON 2 27 27 0 0 10 | Markdown 4 365 365 0 0 11 | Prolog 4 51 34 8 9 12 | Shell 1 37 22 6 9 13 | YAML 1 26 22 0 4 14 | ------------------------------------------------------------------------------- 15 | Total 61 4915 4141 72 702 16 | ------------------------------------------------------------------------------- 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/Specification.md: -------------------------------------------------------------------------------- 1 | Specification 2 | === 3 | 4 | ## Tokens 5 | 6 | ``` 7 | ::= | | | | | | | 8 | ::= regex( [a-z][_0-9a-zA-Z]* ) | regex( `[^`]*` ) | ()+ | "!" 9 | ::= regex( 0|[1-9][0-9]* ) | regex( 0[bB][0-1]+ ) | regex( 0[xX][0-9a-fA-F]+ ) 10 | ::= regex( [_A-Z][_0-9a-zA-Z]* ) 11 | ::= "(" 12 | ::= ")" 13 | ::= "[" 14 | ::= "]" 15 | ::= "." 16 | ::= ":" | "?" | "&" | ";" | "," | "|" | "=" | "<" | ">" | "+" | "-" | "*" | "/" | "\" 17 | ``` 18 | 19 | - `` is an arbitrary-precision integer. 20 | 21 | ## Syntax 22 | 23 | ``` 24 | ::= ()* 25 | ::= [ | | ] 26 | ::= 27 | ::= ":-" 28 | ::= "?-" 29 | ::= (("," | ";") )* 30 | ::= | | | | 31 | ::= ("," )* | | | | 32 | ::= 33 | ::= 34 | ::= [ ("," )* ["|" ]] 35 | ``` 36 | 37 | ## Operators 38 | 39 | | Precedence | Type | Name | 40 | | ---------: | :--: | :--- | 41 | | 1200 | xfx | `:-` | 42 | | 1200 | fx | `?-` | 43 | | 1100 | xfy | `;`, `\|` | 44 | | 1000 | xfy | `,` | 45 | | 700 | xfx | `=`, `==`, `<`, `=<`, `>`, `>=`, `=:=`, `=\=`, `is` | 46 | | 500 | yfx | `+`, `-` | 47 | | 400 | yfx | `*`, `div`, `mod` | 48 | | 200 | fy | `+`, `-` | 49 | 50 | - `+`, `-`, `*`, `div`, `mod`: Arithmetic functions 51 | - `<`, `=<`, `>`, `>=`, `=:=`, `=\=`: Arithmetic comparison predicates 52 | 53 | ## Predicates 54 | 55 | - `!/0` 56 | - `true/0` 57 | - `false/0` 58 | - `fail/0` 59 | - `repeat/0` 60 | - `not/1` 61 | 62 | ## System Commands 63 | 64 | ### halt 65 | 66 | ```prolog 67 | ?- halt. % Stop D-Prolog 68 | ``` 69 | 70 | ### clear 71 | 72 | ```prolog 73 | ?- clear. % Clear the screen 74 | ``` 75 | 76 | ### pwd 77 | 78 | ```prolog 79 | ?- pwd. % Print the name of the current working directory 80 | ``` 81 | 82 | ### ls 83 | 84 | ```prolog 85 | ?- ls. % List the file/directory names 86 | ``` 87 | ```prolog 88 | ?- ls('/path/to/directory'). % Use `ls` for a specific path. 89 | ``` 90 | 91 | ### cd 92 | 93 | ```prolog 94 | ?- cd('/path/to/directory'). % Change the current working directory 95 | ``` 96 | 97 | ### sl 98 | 99 | ```prolog 100 | ?- sl. % Display a SL 101 | ``` 102 | Inspired by [SL(1)](https://github.com/mtoyoda/sl), which is loved by many hackers :) 103 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d-prolog", 3 | "authors": ["Ark"], 4 | "description": "A Prolog implementation in Dlang.", 5 | "homepage": "https://github.com/arkark/d-prolog", 6 | "targetName": "dprolog", 7 | "targetPath": "bin", 8 | "dependencies": { 9 | "linenoise": "~>1.1.0" 10 | }, 11 | "dflags": [ 12 | "-J tmp" 13 | ], 14 | "lflags": [ 15 | "-Llib" 16 | ], 17 | "preGenerateCommands": [ 18 | "git describe --tags > tmp/dprolog_version.txt", 19 | "LANG=C date +\"%b %d %Y\" > tmp/compile_date.txt" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "linenoise": "1.1.0+1.0.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/factorial.pro: -------------------------------------------------------------------------------- 1 | 2 | % Result is the factorial of N. 3 | factorial(N, Result) :- factorial2(N, 1, Result). 4 | 5 | factorial2(0, Result, Result). 6 | factorial2(N, Acc, Result) :- 7 | N > 0, 8 | Acc1 is N * Acc, 9 | N1 is N - 1, 10 | factorial2(N1, Acc1, Result). 11 | -------------------------------------------------------------------------------- /example/family.pro: -------------------------------------------------------------------------------- 1 | male(bob). 2 | male(tom). 3 | male(jim). 4 | female(pam). 5 | female(liz). 6 | female(ann). 7 | female(pat). 8 | 9 | parent(pam, bob). 10 | parent(tom, bob). 11 | parent(tom, liz). 12 | parent(bob, ann). 13 | parent(bob, pat). 14 | parent(pat, jim). 15 | 16 | father(X, Y) :- parent(X, Y), male(X). 17 | mother(X, Y) :- parent(X, Y), female(X). 18 | 19 | grandparent(X, Y) :- parent(X, Z), parent(Z, Y). 20 | grandfather(X, Y) :- father(X, Z), parent(Z, Y). 21 | grandmother(X, Y) :- mother(X, Z), parent(Z, Y). 22 | -------------------------------------------------------------------------------- /example/if.pro: -------------------------------------------------------------------------------- 1 | 2 | % conditional expressions: 3 | % 4 | % ?- if (true, X = 1, X = 2). 5 | % X = 1. 6 | % 7 | % ?- if (false, X = 1, X = 2). 8 | % X = 2. 9 | 10 | if(Cond, Then, _) :- Cond, !, Then. 11 | if(_, _, Else) :- Else. 12 | -------------------------------------------------------------------------------- /example/list.pro: -------------------------------------------------------------------------------- 1 | member(X, [X|Xs]). 2 | member(X, [Y|Ys]) :- member(X, Ys). 3 | 4 | append([], Xs, Xs). 5 | append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs). 6 | 7 | reverse(Xs, Ys) :- reverse2(Xs, [], Ys). 8 | reverse2([], Ys, Ys). 9 | reverse2([X|Xs], Ys, Zs) :- reverse2(Xs, [X|Ys], Zs). 10 | -------------------------------------------------------------------------------- /lib/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkark/d-prolog/d77ad685aa8331e95b079a036e5d7ad6286c03cf/lib/.gitkeep -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu -o pipefail 4 | 5 | VERSION=$(git describe --abbrev=0 --tags) 6 | ARCH="x86_64" 7 | 8 | UNAME_OUT="$(uname -s)" 9 | case "$UNAME_OUT" in 10 | Linux*) OS=linux ;; 11 | Darwin*) OS=osx ;; 12 | *) echo "Unknown OS: $UNAME_OUT"; exit 1 13 | esac 14 | 15 | FILE_NAME="dprolog-$VERSION-$OS-$ARCH" 16 | 17 | echo -e "\e[36mBuilding bin/$FILE_NAME\e[0m" 18 | 19 | mkdir "bin/$FILE_NAME" 20 | dub build -b release --arch=x86_64 --force 21 | cp "bin/dprolog" "bin/$FILE_NAME" 22 | cp -r example "bin/$FILE_NAME/example" 23 | 24 | tar cvfz "bin/$FILE_NAME.tar.gz" -C bin "$FILE_NAME" 25 | rm -r "bin/$FILE_NAME" 26 | echo -e "\e[32m-> Succeeded!\e[0m" 27 | 28 | if type docker >/dev/null 2>&1; then 29 | echo -e "\e[36mRunning tokei from mbologna/docker-tokei > docs/LoC.md\e[0m" 30 | 31 | docker image pull mbologna/docker-tokei \ 32 | && docker container run -v $PWD:/data:ro mbologna/docker-tokei tokei \ 33 | | sed -e '1i## Lines of Code\n\n```sh\n$ tokei' -e '$a```' > docs/LoC.md \ 34 | && echo -e "\e[32m-> Succeeded!\e[0m" 35 | fi 36 | -------------------------------------------------------------------------------- /src/app.d: -------------------------------------------------------------------------------- 1 | import dprolog.engine.Engine; 2 | import dprolog.util.GetOpt; 3 | 4 | void main(string[] args) { 5 | GetOpt.run(args); 6 | if (GetOpt.shouldExit()) return; 7 | 8 | while(!Engine.isHalt) { 9 | Engine.next(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/dprolog/converter/ClauseBuilder.d: -------------------------------------------------------------------------------- 1 | module dprolog.converter.ClauseBuilder; 2 | 3 | import dprolog.data.token; 4 | import dprolog.data.AST; 5 | import dprolog.data.Clause; 6 | import dprolog.data.Term; 7 | import dprolog.util.Message; 8 | import dprolog.converter.Converter; 9 | import dprolog.converter.Lexer; 10 | import dprolog.converter.Parser; 11 | import dprolog.util.functions; 12 | import dprolog.util.Maybe; 13 | 14 | import std.stdio; 15 | import std.format; 16 | import std.conv; 17 | import std.range; 18 | import std.array; 19 | import std.algorithm; 20 | import std.functional; 21 | import std.container : DList; 22 | 23 | // ClauseBuilder: ASTRoot -> Clause[] 24 | 25 | class ClauseBuilder : Converter!(ASTRoot, Clause[]) { 26 | 27 | private: 28 | bool _isBuilded; 29 | DList!Clause _resultClauses; 30 | 31 | Maybe!Message _errorMessage; 32 | 33 | public: 34 | 35 | this() { 36 | clear(); 37 | } 38 | 39 | void run(ASTRoot astRoot) { 40 | clear(); 41 | build(astRoot); 42 | } 43 | 44 | Clause[] get() in(_isBuilded) do { 45 | return _resultClauses.array; 46 | } 47 | 48 | void clear() { 49 | _isBuilded = false; 50 | _resultClauses.clear(); 51 | _errorMessage = None!Message; 52 | } 53 | 54 | bool hasError() { 55 | return _errorMessage.isJust; 56 | } 57 | 58 | Message errorMessage() in(hasError) do { 59 | return _errorMessage.get; 60 | } 61 | 62 | private: 63 | 64 | void build(ASTRoot astRoot) { 65 | foreach(ast; astRoot.children) { 66 | assert(ast.token.instanceOf!Period); 67 | assert(ast.children.length <= 1); 68 | if (ast.children.empty) continue; 69 | 70 | Clause clause = ast.children.front.pipe!toClause; 71 | if (hasError) break; 72 | _resultClauses.insertBack(clause); 73 | } 74 | _isBuilded = true; 75 | } 76 | 77 | Clause toClause(AST ast) { 78 | if (ast.pipe!isRule) { 79 | return ast.pipe!toRule; 80 | } else if (ast.pipe!isQuery) { 81 | return ast.pipe!toQuery; 82 | } else { 83 | return ast.pipe!toFact; 84 | } 85 | } 86 | 87 | bool isRule(AST ast) { 88 | return ast.token == Operator.rulifier; 89 | } 90 | 91 | bool isQuery(AST ast) { 92 | return ast.token == Operator.queryfier; 93 | } 94 | 95 | Fact toFact(AST ast) { 96 | Term first = ast.pipe!toTerm; 97 | if (hasError) return null; 98 | if (first.isCompound) setErrorMessage(ast.tokenList); 99 | if (hasError) return null; 100 | return new Fact(first); 101 | } 102 | 103 | Rule toRule(AST ast) { 104 | assert(ast.children.length == 2); 105 | Term first = ast.children.front.pipe!toTerm; 106 | Term second = ast.children.back.pipe!toTerm; 107 | if (hasError) return null; 108 | if (first.isCompound) setErrorMessage(ast.tokenList); 109 | if (hasError) return null; 110 | return new Rule(first, second); 111 | } 112 | 113 | Query toQuery(AST ast) { 114 | assert(ast.children.length == 1); 115 | Term first = ast.children.front.pipe!toTerm; 116 | if (hasError) return null; 117 | return new Query(first); 118 | } 119 | 120 | Term toTerm(AST ast) { 121 | if (ast.token.instanceOf!Functor) { 122 | // Structure 123 | assert(ast.children.length==1); 124 | return ast.children.front.pipe!toArguments.pipe!( 125 | children => hasError ? null : new Term(ast.token, children) 126 | ); 127 | } else if (ast.token.instanceOf!Operator) { 128 | // Operator 129 | if ( 130 | ast.token == Operator.rulifier || 131 | ast.token == Operator.queryfier || 132 | ast.token == Operator.pipe 133 | ) { 134 | setErrorMessage(ast.tokenList); 135 | return null; 136 | } 137 | assert(ast.children.length==1 || ast.children.length==2); 138 | return ast.children.map!(c => c.pipe!toTerm).array.pipe!( 139 | children => hasError ? null : new Term(ast.token, children) 140 | ); 141 | } else if (ast.token.instanceOf!LBracket) { 142 | // List 143 | assert(ast.children.length == 1); 144 | return ast.children.front.pipe!toList; 145 | } else { 146 | // Atom, Number, Variable 147 | assert(ast.children.empty); 148 | return new Term(ast.token, []); 149 | } 150 | } 151 | 152 | Term[] toArguments(AST ast) { 153 | if (ast.token == Operator.comma) { 154 | assert(ast.children.length == 2); 155 | return ast.children.front.pipe!toArguments ~ ast.children.back.pipe!toArguments; 156 | } else { 157 | return [ast.pipe!toTerm]; 158 | } 159 | } 160 | 161 | Term toList(AST ast) { 162 | if (ast.token == Operator.pipe) { 163 | assert(ast.children.length == 2); 164 | 165 | Term back = ast.children.back.pipe!((b) { 166 | if (b.token.instanceOf!LBracket) { 167 | assert(b.children.length == 1); 168 | Term back = b.children.front.pipe!toList; 169 | if (hasError) return null; 170 | if (back.token != Operator.pipe && back.token != Atom.emptyList) { 171 | Term empty = new Term(cast(Token) Atom.emptyList, []); 172 | back = new Term(cast(Token) Operator.pipe, [back, empty]); 173 | } 174 | return back; 175 | } else if (ast.children.back.token.instanceOf!Variable) { 176 | return new Term(ast.children.back.token, []); 177 | } else { 178 | setErrorMessage(ast.tokenList); 179 | return null; 180 | } 181 | }); 182 | if (hasError) return null; 183 | 184 | Term front = ast.children.front.pipe!toList; 185 | if (hasError) return null; 186 | if (front.token == Operator.pipe) { 187 | Term buildList(Term parent) { 188 | assert(parent.token == Operator.pipe); 189 | if (parent.children.back.token != Operator.pipe) { 190 | assert(parent.children.back.token == Atom.emptyList); 191 | return new Term(parent.token, [parent.children.front, back]); 192 | } else { 193 | return new Term(parent.token, [parent.children.front, buildList(parent.children.back)]); 194 | } 195 | } 196 | return buildList(front); 197 | } else { 198 | return new Term(ast.token, [front, back]); 199 | } 200 | } else if (ast.token == Operator.comma) { 201 | assert(ast.children.length == 2); 202 | Term front = ast.children.front.pipe!toTerm; 203 | Term back = ast.children.back.pipe!toList; 204 | if (back.token != Operator.pipe) { 205 | Term empty = new Term(cast(Token) Atom.emptyList, []); 206 | back = new Term(cast(Token) Operator.pipe, [back, empty]); 207 | } 208 | return new Term(cast(Token) Operator.pipe, [front, back]); 209 | } else if (ast.token == Atom.emptyList) { 210 | return ast.pipe!toTerm; 211 | } else { 212 | Term front = ast.pipe!toTerm; 213 | Term back = new Term(cast(Token) Atom.emptyList, []); 214 | return new Term(cast(Token) Operator.pipe, [front, back]); 215 | } 216 | } 217 | 218 | void setErrorMessage(Token[] tokens) in(!tokens.empty) do { 219 | _errorMessage = ErrorMessage( 220 | format!"SyntaxError(%d, %d): \"%s\""( 221 | tokens.front.line, 222 | tokens.front.column, 223 | tokens.map!(t => t.lexeme).join(" ") 224 | ) 225 | ); 226 | } 227 | 228 | 229 | /* ---------- Unit Tests ---------- */ 230 | 231 | unittest { 232 | writeln(__FILE__, ": test fact/rule/query"); 233 | 234 | auto lexer = new Lexer; 235 | auto parser = new Parser; 236 | auto clauseBuilder = new ClauseBuilder; 237 | 238 | dstring src = "hoge(aaa). po(X) :- hoge(X). ?- po(aaa)."; 239 | lexer.run(src); 240 | parser.run(lexer.get); 241 | ASTRoot root = parser.get; 242 | 243 | AST fact = root.children[0].children.front; 244 | AST rule = root.children[1].children.front; 245 | AST query = root.children[2].children.front; 246 | 247 | assert(!clauseBuilder.isRule(fact) && !clauseBuilder.isQuery(fact) ); 248 | assert( clauseBuilder.isRule(rule) && !clauseBuilder.isQuery(rule) ); 249 | assert(!clauseBuilder.isRule(query) && clauseBuilder.isQuery(query)); 250 | } 251 | 252 | unittest { 253 | writeln(__FILE__, ": test build 1"); 254 | 255 | dstring src = "hoge(a, b, c, d)."; 256 | 257 | auto lexer = new Lexer; 258 | auto parser = new Parser; 259 | auto clauseBuilder = new ClauseBuilder; 260 | 261 | lexer.run(src); 262 | parser.run(lexer.get); 263 | clauseBuilder.run(parser.get); 264 | assert(!clauseBuilder.hasError); 265 | 266 | Clause[] clauseList = clauseBuilder.get; 267 | assert(clauseList.length == 1); 268 | assert(clauseList.front.instanceOf!Fact); 269 | 270 | Term term = (cast(Fact) clauseList.front).first; 271 | assert(term.isStructure); 272 | assert(term.children.length == 4); 273 | assert(term.children.all!(t => t.children.empty && t.isAtom)); 274 | } 275 | 276 | unittest { 277 | writeln(__FILE__, ": test build 2"); 278 | 279 | dstring src = "hoge(X) :- po1(X, Y), po2(X, Y); po3(X), po4(X)."; 280 | 281 | auto lexer = new Lexer; 282 | auto parser = new Parser; 283 | auto clauseBuilder = new ClauseBuilder; 284 | 285 | lexer.run(src); 286 | parser.run(lexer.get); 287 | clauseBuilder.run(parser.get); 288 | assert(!clauseBuilder.hasError); 289 | 290 | Clause[] clauseList = clauseBuilder.get; 291 | assert(clauseList.length == 1); 292 | assert(clauseList.front.instanceOf!Rule); 293 | 294 | Term term = (cast(Rule) clauseList.front).second; 295 | assert(term.isCompound && term.token==Operator.semicolon); 296 | assert(term.children.length==2); 297 | assert(term.children.all!(t => t.isCompound && t.token==Operator.comma)); 298 | assert(term.children.all!( 299 | a => a.children.length==2 && a.children.all!( 300 | b => !b.isCompound && b.isStructure && b.children.all!( 301 | c => !c.isCompound && c.isVariable 302 | ) 303 | ) 304 | )); 305 | } 306 | 307 | unittest { 308 | writeln(__FILE__, ": test build 3"); 309 | 310 | dstring src = "?- X is 1 * 2 + 3 mod 4."; 311 | 312 | auto lexer = new Lexer; 313 | auto parser = new Parser; 314 | auto clauseBuilder = new ClauseBuilder; 315 | 316 | lexer.run(src); 317 | parser.run(lexer.get); 318 | clauseBuilder.run(parser.get); 319 | assert(!clauseBuilder.hasError); 320 | 321 | Clause[] clauseList = clauseBuilder.get; 322 | assert(clauseList.length == 1); 323 | assert(clauseList.front.instanceOf!Query); 324 | 325 | Term term = (cast(Query) clauseList.front).first; 326 | assert(!term.isCompound && term.isStructure && term.token.lexeme=="is"); 327 | assert(term.children.length == 2); 328 | assert(term.children.front.isVariable); 329 | assert(term.children.back.pipe!( 330 | t => t.isStructure && t.token.lexeme=="+" 331 | )); 332 | assert(term.children.back.children.length == 2); 333 | assert(term.children.back.children.front.pipe!( 334 | t => t.isStructure && t.token.lexeme=="*" 335 | )); 336 | assert(term.children.back.children.back.pipe!( 337 | t => t.isStructure && t.token.lexeme=="mod" 338 | )); 339 | } 340 | 341 | unittest { 342 | writeln(__FILE__, ": test build 4"); 343 | 344 | dstring src = "[1, 2|[3|[4, 5|[6]]]]."; 345 | // => "[1|[2|[3|[4|[5|[6|[]]]]]]]." 346 | 347 | auto lexer = new Lexer; 348 | auto parser = new Parser; 349 | auto clauseBuilder = new ClauseBuilder; 350 | 351 | lexer.run(src); 352 | parser.run(lexer.get); 353 | clauseBuilder.run(parser.get); 354 | assert(!clauseBuilder.hasError); 355 | 356 | Clause[] clauseList = clauseBuilder.get; 357 | assert(clauseList.length == 1); 358 | assert(clauseList.front.instanceOf!Fact); 359 | 360 | Term first = (cast(Fact) clauseList.front).first; 361 | 362 | bool validate(Term term, long n) { 363 | return n==0 ? (() => 364 | term.isAtom && term.token == Atom.emptyList 365 | )() : (() => 366 | term.isStructure && 367 | term.token == Operator.pipe && 368 | term.children.front.isNumber && 369 | validate(term.children.back, n-1) 370 | )(); 371 | } 372 | 373 | assert(validate(first, 6)); 374 | } 375 | 376 | unittest { 377 | writeln(__FILE__, ": test error"); 378 | 379 | auto lexer = new Lexer; 380 | auto parser = new Parser; 381 | auto clauseBuilder = new ClauseBuilder; 382 | 383 | void testError(dstring src, bool isError) { 384 | lexer.run(src); 385 | assert(!lexer.hasError); 386 | parser.run(lexer.get); 387 | assert(!parser.hasError); 388 | clauseBuilder.run(parser.get); 389 | assert(clauseBuilder.hasError == isError); 390 | } 391 | 392 | testError("", false); 393 | testError(".", false); 394 | testError("hoge(a).", false); 395 | testError("hoge(X).", false); 396 | testError("?- hoge(X).", false); 397 | testError("aaa(a), bbb(b).", true); // => Error: Factが複合節 398 | testError("aaa(a); bbb(b).", true); // => Error: Factが複合節 399 | testError("aaa(X), bbb(X) :- ccc(X).", true); // => Error: Ruleのheadが複合節 400 | testError("aa :- (bb :- cc).", true); // => Error: RuleのSyntaxが不適切 401 | testError("?- (aa :- cc).", true); // => Error: QueryのSyntaxが不適切 402 | testError("[].", false); 403 | testError("[a | X].", false); 404 | testError("[a | a].", true); // => Error: ListのSyntaxが不適切 405 | testError("[a | [a | X]].", false); 406 | testError("[a | a | X].", true); // => Error: ListのSyntaxが不適切 407 | testError("[1, 2, 3, 4].", false); 408 | testError("[1, 2, 3, 4 | []].", false); 409 | testError("[1, 2, 3 | [4]].", false); 410 | testError("[1, 2, 3 | 4].", true); // => Error: ListのSyntaxが不適切 411 | testError("[1 | [2 | [3 | [4]]]].", false); 412 | testError("[1 | [2, 3 | [4]]].", false); 413 | testError("[[], a, [1, 2]].", false); 414 | } 415 | 416 | } 417 | -------------------------------------------------------------------------------- /src/dprolog/converter/Converter.d: -------------------------------------------------------------------------------- 1 | module dprolog.converter.Converter; 2 | 3 | import dprolog.util.Message; 4 | 5 | // Converter: S => T 6 | 7 | interface Converter(S, T) { 8 | 9 | void run(S src); 10 | T get(); 11 | void clear(); 12 | @property bool hasError(); 13 | @property Message errorMessage(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/dprolog/converter/Lexer.d: -------------------------------------------------------------------------------- 1 | module dprolog.converter.Lexer; 2 | 3 | import dprolog.data.token; 4 | import dprolog.util.Message; 5 | import dprolog.converter.Converter; 6 | import dprolog.util.functions; 7 | import dprolog.util.Maybe; 8 | 9 | import std.stdio; 10 | import std.format; 11 | import std.conv; 12 | import std.string; 13 | import std.format; 14 | import std.ascii; 15 | import std.range; 16 | import std.array; 17 | import std.algorithm; 18 | import std.regex; 19 | import std.functional; 20 | import std.concurrency; 21 | import std.container : DList; 22 | 23 | // Lexer (lexical analyzer): dstring -> Token[] 24 | 25 | class Lexer : Converter!(dstring, Token[]) { 26 | 27 | private: 28 | bool _isTokenized; 29 | DList!Token _resultTokens; 30 | 31 | Maybe!Message _errorMessage; 32 | 33 | public: 34 | this() { 35 | clear(); 36 | } 37 | 38 | void run(dstring src) { 39 | clear(); 40 | tokenize(src); 41 | } 42 | 43 | Token[] get() in(_isTokenized) do { 44 | return _resultTokens.array; 45 | } 46 | 47 | void clear() { 48 | _isTokenized = false; 49 | _resultTokens.clear; 50 | _errorMessage = None!Message; 51 | } 52 | 53 | @property bool hasError() { 54 | return _errorMessage.isJust; 55 | } 56 | 57 | @property Message errorMessage() in(hasError) do { 58 | return _errorMessage.get; 59 | } 60 | 61 | private: 62 | 63 | void tokenize(dstring src) { 64 | auto lookaheader = getLookaheader(src); 65 | while(!lookaheader.empty) { 66 | TokenGen tokenGen = getTokenGen(lookaheader); 67 | getToken(lookaheader, tokenGen).apply!( 68 | token => _resultTokens.insertBack(token) 69 | ); 70 | } 71 | _isTokenized = true; 72 | } 73 | 74 | TokenGen getTokenGen(Generator!Node lookaheader) { 75 | Node node = lookaheader.front; 76 | dstring head = node.value; 77 | auto genR = [ 78 | AtomGen, 79 | NumberGen, 80 | VariableGen, 81 | LParenGen, 82 | RParenGen, 83 | LBracketGen, 84 | RBracketGen, 85 | PeriodGen, 86 | EmptyGen 87 | ].find!(gen => gen.validatePrefix(head)); 88 | return genR.empty ? ErrorGen : genR.front; 89 | } 90 | 91 | Maybe!Token getToken(Generator!Node lookaheader, TokenGen tokenGen) { 92 | if (lookaheader.empty) return None!Token; 93 | return getTokenNode(lookaheader, tokenGen).bind!( 94 | node => tokenGen.getToken(node) 95 | ); 96 | } 97 | 98 | Maybe!Node getTokenNode(Generator!Node lookaheader, TokenGen tokenGen) in(!lookaheader.empty) do { 99 | Node nowNode = lookaheader.front; 100 | lookaheader.popFront; 101 | while(!lookaheader.empty) { 102 | Node nextNode = nowNode ~ lookaheader.front; 103 | if (tokenGen.validatePrefix(nextNode.value)) { 104 | nowNode = nextNode; 105 | lookaheader.popFront; 106 | } else { 107 | break; 108 | } 109 | } 110 | bool isValid = tokenGen.validateAll(nowNode.value); 111 | if (!isValid) { 112 | setErrorMessage(nowNode); 113 | clearLookaheader(lookaheader); 114 | } 115 | return isValid.fmap(nowNode); 116 | } 117 | 118 | void setErrorMessage(Node node) { 119 | long num = 20; 120 | dstring str = node.value.pipe!( 121 | lexeme => lexeme.length>num ? lexeme.take(num).to!dstring ~ " ... " : lexeme 122 | ); 123 | _errorMessage = ErrorMessage( 124 | format!"TokenError(%d, %d): cannot tokenize \"%s\"."( 125 | node.line, 126 | node.column, 127 | str 128 | ) 129 | ); 130 | } 131 | 132 | Generator!Node getLookaheader(immutable dstring src) { 133 | return new Generator!Node({ 134 | foreach(line, str; src.splitLines) { 135 | foreach(column, ch; str) { 136 | if (ch == '%') break; // a comment 137 | Node(ch.to!dstring, line+1, column+1).yield; 138 | } 139 | } 140 | }); 141 | } 142 | 143 | void clearLookaheader(Generator!Node lookaheader) { 144 | while(!lookaheader.empty) { 145 | lookaheader.popFront; 146 | } 147 | } 148 | 149 | struct TokenGen { 150 | immutable bool function(dstring) validatePrefix; 151 | immutable bool function(dstring) validateAll; 152 | immutable Maybe!Token function(Node) getToken; 153 | } 154 | 155 | static TokenGen AtomGen = TokenGen( 156 | (dstring prefix) { 157 | enum re = ctRegex!(r"(([a-z][_0-9a-zA-Z]*)|('[^']*'?)|(["d ~Token.specialCharacters.escaper.to!dstring~ r"]+)|!)$"d); 158 | auto res = prefix.matchFirst(re); 159 | return !res.empty && res.front==prefix; 160 | }, 161 | (dstring lexeme) { 162 | enum re = ctRegex!(r"(([a-z][_0-9a-zA-Z]*)|('[^']*')|(["d ~Token.specialCharacters.escaper.to!dstring~ r"]+)|!)$"d); 163 | auto res = lexeme.matchFirst(re); 164 | return !res.empty && res.front==lexeme; 165 | }, 166 | (Node node) => Just!Token(new Atom(node.value, node.line, node.column)) 167 | ); 168 | 169 | static TokenGen NumberGen = TokenGen( 170 | (dstring prefix) { 171 | enum dstring decimal = r"0|[1-9][0-9]*"d; 172 | enum dstring binary = r"0[bB][0-1]*"d; 173 | enum dstring hexadecimal = r"0[xX][0-9a-fA-F]*"d; 174 | enum re = ctRegex!(format!r"((%s)|(%s)|(%s))$"d(decimal, binary, hexadecimal)); 175 | auto res = prefix.matchFirst(re); 176 | return !res.empty && res.front==prefix; 177 | }, 178 | (dstring lexeme) { 179 | enum dstring decimal = r"0|[1-9][0-9]*"d; 180 | enum dstring binary = r"0[bB][0-1]+"d; 181 | enum dstring hexadecimal = r"0[xX][0-9a-fA-F]+"d; 182 | enum re = ctRegex!(format!r"((%s)|(%s)|(%s))$"d(decimal, binary, hexadecimal)); 183 | auto res = lexeme.matchFirst(re); 184 | return !res.empty && res.front==lexeme; 185 | }, 186 | (Node node) => Just!Token(new Number(node.value, node.line, node.column)) 187 | ); 188 | 189 | static TokenGen VariableGen = TokenGen( 190 | (dstring prefix) { 191 | enum re = ctRegex!(r"[_A-Z][_0-9a-zA-Z]*$"d); 192 | auto res = prefix.matchFirst(re); 193 | return !res.empty && res.front==prefix; 194 | }, 195 | (dstring lexeme) { 196 | enum re = ctRegex!(r"[_A-Z][_0-9a-zA-Z]*$"d); 197 | auto res = lexeme.matchFirst(re); 198 | return !res.empty && res.front==lexeme; 199 | }, 200 | (Node node) => Just!Token(new Variable(node.value, node.line, node.column)) 201 | ); 202 | 203 | static TokenGen LParenGen = TokenGen( 204 | (dstring prefix) => prefix == "(", 205 | (dstring lexeme) => lexeme == "(", 206 | (Node node) => Just!Token(new LParen(node.value, node.line, node.column)) 207 | ); 208 | 209 | static TokenGen RParenGen = TokenGen( 210 | (dstring prefix) => prefix == ")", 211 | (dstring lexeme) => lexeme == ")", 212 | (Node node) => Just!Token(new RParen(node.value, node.line, node.column)) 213 | ); 214 | 215 | static TokenGen LBracketGen = TokenGen( 216 | (dstring prefix) => prefix == "[", 217 | (dstring lexeme) => lexeme == "[", 218 | (Node node) => Just!Token(new LBracket(node.value, node.line, node.column)) 219 | ); 220 | 221 | static TokenGen RBracketGen = TokenGen( 222 | (dstring prefix) => prefix == "]", 223 | (dstring lexeme) => lexeme == "]", 224 | (Node node) => Just!Token(new RBracket(node.value, node.line, node.column)) 225 | ); 226 | 227 | static TokenGen PeriodGen = TokenGen( 228 | (dstring prefix) => prefix == ".", 229 | (dstring lexeme) => lexeme == ".", 230 | (Node node) => Just!Token(new Period(node.value, node.line, node.column)) 231 | ); 232 | 233 | static TokenGen EmptyGen = TokenGen( 234 | (dstring prefix) => prefix.length==1 && prefix.front.isWhite, 235 | (dstring lexeme) => lexeme.length==1 && lexeme.front.isWhite, 236 | (Node node) => None!Token 237 | ); 238 | 239 | static TokenGen ErrorGen = TokenGen( 240 | (dstring prefix) => false, 241 | (dstring lexeme) => false, 242 | (Node node) => None!Token 243 | ); 244 | 245 | struct Node { 246 | dstring value; 247 | long line; 248 | long column; 249 | 250 | Node opBinary(string op)(Node that) if (op == "~") { 251 | return Node( 252 | this.value~that.value, 253 | min(this.line, that.line), 254 | this.linethat.line ? that.column : 256 | min(this.column, that.column) 257 | ); 258 | } 259 | 260 | string toString() const { 261 | return format!"Node(value: \"%s\", line: %s, column: %s)"(value, line, column); 262 | } 263 | 264 | } 265 | 266 | 267 | 268 | /* ---------- Unit Tests ---------- */ 269 | 270 | // test Node 271 | unittest { 272 | writeln(__FILE__, ": test Node"); 273 | 274 | Node n1 = Node("abc", 1, 10); 275 | Node n2 = Node("de", 2, 5); 276 | Node n3 = Node("fg", 2, 1); 277 | assert(n1~n2 == Node("abcde", 1, 10)); 278 | assert(n2~n3 == Node("defg", 2, 1)); 279 | } 280 | 281 | // test TokenGen 282 | unittest { 283 | writeln(__FILE__, ": test TokenGen"); 284 | 285 | // AtomGen 286 | assert(AtomGen.validatePrefix("a")); 287 | assert(AtomGen.validatePrefix("\'")); 288 | assert(AtomGen.validatePrefix("\'aaa")); 289 | assert(!AtomGen.validatePrefix("aaa\'")); 290 | assert(AtomGen.validatePrefix(",")); 291 | assert(!AtomGen.validatePrefix("A")); 292 | assert(AtomGen.validatePrefix("!")); 293 | assert(AtomGen.validateAll("abc")); 294 | assert(AtomGen.validateAll("' po _'")); 295 | assert(AtomGen.validateAll("''")); 296 | assert(AtomGen.validateAll("|+|")); 297 | assert(!AtomGen.validateAll("'")); 298 | assert(!AtomGen.validateAll("' po _")); 299 | assert(AtomGen.validateAll("!")); 300 | 301 | // NumberGen 302 | assert(NumberGen.validatePrefix("0")); 303 | assert(NumberGen.validatePrefix("0b")); 304 | assert(NumberGen.validatePrefix("0B")); 305 | assert(NumberGen.validatePrefix("0x")); 306 | assert(NumberGen.validatePrefix("0X")); 307 | assert(!NumberGen.validatePrefix("_")); 308 | assert(NumberGen.validateAll("123")); 309 | assert(NumberGen.validateAll("0")); 310 | assert(NumberGen.validateAll("0b10")); 311 | assert(NumberGen.validateAll("0xff")); 312 | assert(NumberGen.validateAll("0XFF")); 313 | assert(!NumberGen.validateAll("0b123")); 314 | assert(!NumberGen.validateAll("0xxxx")); 315 | assert(!NumberGen.validateAll("0123")); 316 | 317 | // VariableGen 318 | assert(VariableGen.validatePrefix("A")); 319 | assert(VariableGen.validatePrefix("_")); 320 | assert(VariableGen.validatePrefix("_a")); 321 | assert(!VariableGen.validatePrefix("a")); 322 | assert(VariableGen.validateAll("Po")); 323 | assert(VariableGen.validateAll("_yeah")); 324 | 325 | // LParenGen 326 | assert(LParenGen.validatePrefix("(")); 327 | assert(LParenGen.validateAll("(")); 328 | 329 | // RParenGen 330 | assert(RParenGen.validatePrefix(")")); 331 | assert(RParenGen.validateAll(")")); 332 | 333 | // LBracketGen 334 | assert(LBracketGen.validatePrefix("[")); 335 | assert(LBracketGen.validateAll("[")); 336 | 337 | // RBracketGen 338 | assert(RBracketGen.validatePrefix("]")); 339 | assert(RBracketGen.validateAll("]")); 340 | 341 | // PeriodGen 342 | assert(PeriodGen.validatePrefix(".")); 343 | assert(PeriodGen.validateAll(".")); 344 | 345 | // EmptyGen 346 | assert(EmptyGen.validatePrefix(" ")); 347 | assert(EmptyGen.validateAll(" ")); 348 | } 349 | 350 | // test lookaheader 351 | unittest { 352 | writeln(__FILE__, ": test Lookaheader"); 353 | 354 | auto lexer = new Lexer; 355 | auto lookaheader = lexer.getLookaheader("a\nbc\nd"); 356 | 357 | assert(!lookaheader.empty); 358 | assert(lookaheader.front == Node("a", 1, 1)); 359 | lookaheader.popFront; 360 | assert(lookaheader.front == Node("b", 2, 1)); 361 | lookaheader.popFront; 362 | assert(lookaheader.front == Node("c", 2, 2)); 363 | lookaheader.popFront; 364 | assert(lookaheader.front == Node("d", 3, 1)); 365 | lookaheader.popFront; 366 | assert(lookaheader.empty); 367 | } 368 | 369 | // test getTokenGen 370 | unittest { 371 | writeln(__FILE__, ": test getTokenGen"); 372 | 373 | auto lexer = new Lexer; 374 | auto lookaheader = lexer.getLookaheader("hoge(10, X)."); 375 | assert(lexer.getTokenGen(lookaheader) == AtomGen); 376 | lookaheader.drop(4); 377 | assert(lexer.getTokenGen(lookaheader) == LParenGen); 378 | lookaheader.drop(1); 379 | assert(lexer.getTokenGen(lookaheader) == NumberGen); 380 | lookaheader.drop(2); 381 | assert(lexer.getTokenGen(lookaheader) == AtomGen); 382 | lookaheader.drop(1); 383 | assert(lexer.getTokenGen(lookaheader) == EmptyGen); 384 | lookaheader.drop(1); 385 | assert(lexer.getTokenGen(lookaheader) == VariableGen); 386 | lookaheader.drop(1); 387 | assert(lexer.getTokenGen(lookaheader) == RParenGen); 388 | lookaheader.drop(1); 389 | assert(lexer.getTokenGen(lookaheader) == PeriodGen); 390 | lookaheader.drop(1); 391 | assert(lookaheader.empty); 392 | 393 | assert(!lexer.hasError); 394 | assert(lexer._errorMessage.isNone); 395 | } 396 | 397 | // test getTokenNode 398 | unittest { 399 | writeln(__FILE__, ": test getTokenNode"); 400 | 401 | auto lexer = new Lexer; 402 | auto lookaheader = lexer.getLookaheader("hoge(10, X)."); 403 | assert(lexer.getTokenNode(lookaheader, AtomGen) == Node("hoge", 1, 1)); 404 | assert(lexer.getTokenNode(lookaheader, LParenGen) == Node("(", 1, 5)); 405 | assert(lexer.getTokenNode(lookaheader, NumberGen) == Node("10", 1, 6)); 406 | assert(lexer.getTokenNode(lookaheader, AtomGen) == Node(",", 1, 8)); 407 | assert(lexer.getTokenNode(lookaheader, EmptyGen) == Node(" ", 1, 9)); 408 | assert(lexer.getTokenNode(lookaheader, VariableGen) == Node("X", 1, 10)); 409 | assert(lexer.getTokenNode(lookaheader, RParenGen) == Node(")", 1, 11)); 410 | assert(lexer.getTokenNode(lookaheader, PeriodGen) == Node(".", 1, 12)); 411 | assert(lookaheader.empty); 412 | 413 | assert(!lexer.hasError); 414 | assert(lexer._errorMessage.isNone); 415 | } 416 | 417 | // test getToken 418 | unittest { 419 | writeln(__FILE__, ": test getToken"); 420 | 421 | auto lexer = new Lexer; 422 | auto lookaheader = lexer.getLookaheader("hoge(10, X)."); 423 | assert(lexer.getToken(lookaheader, AtomGen).fmap!(t => t.instanceOf!Atom) == true); 424 | assert(lexer.getToken(lookaheader, LParenGen).fmap!(t => t.instanceOf!LParen) == true); 425 | assert(lexer.getToken(lookaheader, NumberGen).fmap!(t => t.instanceOf!Number) == true); 426 | assert(lexer.getToken(lookaheader, AtomGen).fmap!(t => t.instanceOf!Atom) == true); 427 | assert(lexer.getToken(lookaheader, EmptyGen).isNone); 428 | assert(lexer.getToken(lookaheader, VariableGen).fmap!(t => t.instanceOf!Variable) == true); 429 | assert(lexer.getToken(lookaheader, RParenGen).fmap!(t => t.instanceOf!RParen) == true); 430 | assert(lexer.getToken(lookaheader, PeriodGen).fmap!(t => t.instanceOf!Period) == true); 431 | assert(lookaheader.empty); 432 | 433 | assert(!lexer.hasError); 434 | assert(lexer._errorMessage.isNone); 435 | } 436 | 437 | // test tokenize 438 | unittest { 439 | writeln(__FILE__, ": test tokenize"); 440 | 441 | auto lexer = new Lexer; 442 | Token[] tokens; 443 | 444 | lexer.run("hoge(10, X)."); 445 | assert(!lexer.hasError); 446 | tokens = lexer.get(); 447 | assert(tokens.length == 7); 448 | assert(tokens[0].instanceOf!Atom); 449 | assert(tokens[1].instanceOf!LParen); 450 | assert(tokens[2].instanceOf!Number); 451 | assert(tokens[3].instanceOf!Atom); 452 | assert(tokens[4].instanceOf!Variable); 453 | assert(tokens[5].instanceOf!RParen); 454 | assert(tokens[6].instanceOf!Period); 455 | 456 | lexer.run("('poあ')."); 457 | assert(!lexer.hasError); 458 | tokens = lexer.get(); 459 | assert(tokens.length == 4); 460 | assert(tokens[0].instanceOf!LParen); 461 | assert(tokens[1].instanceOf!Atom); 462 | assert(tokens[2].instanceOf!RParen); 463 | assert(tokens[3].instanceOf!Period); 464 | 465 | } 466 | 467 | // test errorMessage 468 | unittest { 469 | writeln(__FILE__, ": test errorMessage"); 470 | 471 | auto lexer = new Lexer; 472 | assert(!lexer.hasError); 473 | lexer.run("{po}"); 474 | assert(lexer.hasError); 475 | lexer.run("hoge(X)."); 476 | assert(!lexer.hasError); 477 | lexer.run("hoge(hogeあ)"); 478 | assert(lexer.hasError); 479 | lexer.run("'aaaaaaaaaaaaaaaaabbbbbbbbbbbbbbb"); 480 | assert(lexer.hasError); 481 | // lexer.errorMessage.writeln; 482 | } 483 | } 484 | -------------------------------------------------------------------------------- /src/dprolog/converter/Parser.d: -------------------------------------------------------------------------------- 1 | module dprolog.converter.Parser; 2 | 3 | import dprolog.data.token; 4 | import dprolog.data.AST; 5 | import dprolog.util.Message; 6 | import dprolog.converter.Converter; 7 | import dprolog.converter.Lexer; 8 | import dprolog.util.functions; 9 | import dprolog.util.Maybe; 10 | 11 | import std.stdio; 12 | import std.format; 13 | import std.conv; 14 | import std.algorithm; 15 | import std.array; 16 | import std.range; 17 | import std.concurrency; 18 | import std.traits; 19 | import std.container : DList; 20 | 21 | // Parser: Token[] -> ASTRoot 22 | 23 | class Parser : Converter!(Token[], ASTRoot) { 24 | 25 | private: 26 | bool _isParsed; 27 | ASTRoot _resultAST; 28 | 29 | Maybe!Message _errorMessage; 30 | 31 | public: 32 | 33 | this() { 34 | clear(); 35 | } 36 | 37 | void run(Token[] tokens) { 38 | clear(); 39 | parse(tokens); 40 | } 41 | 42 | ASTRoot get() in(_isParsed) do { 43 | return _resultAST; 44 | } 45 | 46 | void clear() { 47 | _isParsed = false; 48 | _resultAST = null; 49 | _errorMessage = None!Message; 50 | } 51 | 52 | @property bool hasError() { 53 | return _errorMessage.isJust; 54 | } 55 | 56 | @property Message errorMessage() in(hasError) do { 57 | return _errorMessage.get; 58 | } 59 | 60 | private: 61 | 62 | void parse(Token[] tokens) { 63 | _resultAST = new ASTRoot(tokens); 64 | parseProgram(tokens, _resultAST); 65 | _isParsed = true; 66 | } 67 | 68 | void parseProgram(Token[] tokens, AST parent) { 69 | while(!tokens.empty) { 70 | long cnt = tokens.countUntil!(t => t.instanceOf!Period); 71 | if (cnt == -1) { 72 | setErrorMessage(tokens); 73 | return; 74 | } else { 75 | AST ast = new AST(tokens[cnt], tokens[0..cnt+1]); 76 | parseClause(tokens[0..cnt], ast); 77 | parent.children ~= ast; 78 | tokens = tokens[cnt+1..$]; 79 | } 80 | } 81 | } 82 | 83 | void parseClause(Token[] tokens, AST parent) { 84 | parseTermList(tokens, parent); 85 | } 86 | 87 | void parseTermList(Token[] tokens, AST parent) { 88 | Maybe!Operator opM = findHighestOperator(tokens); 89 | if (hasError) return; 90 | if (opM.isNone) { 91 | parseTerm(tokens, parent); 92 | } else { 93 | opM.apply!((op) { 94 | AST ast = new AST(op, tokens); 95 | long cnt = tokens.countUntil!(t => t is op); 96 | if (op.notation != Operator.Notation.Prefix) parseTermList(tokens[0..cnt], ast); 97 | if (op.notation != Operator.Notation.Postfix) parseTermList(tokens[cnt+1..$], ast); 98 | parent.children ~= ast; 99 | }, true); 100 | } 101 | } 102 | 103 | void parseTerm(Token[] tokens, AST parent) { 104 | if (tokens.empty) return; 105 | 106 | Token head = tokens.front; 107 | Token last = tokens.back; 108 | if (head.instanceOf!LParen && last.instanceOf!RParen && tokens.length>2) { 109 | parseTermList(tokens[1..$-1], parent); 110 | } else if (head.instanceOf!LBracket) { 111 | parseList(tokens, parent); 112 | } else if (head.instanceOf!Atom && tokens.length>1) { 113 | parseStructure(tokens, parent); 114 | } else if ((head.instanceOf!Atom || head.instanceOf!Number || head.instanceOf!Variable) && tokens.length==1) { 115 | parent.children ~= new AST(head, tokens[0..1]); 116 | } else { 117 | setErrorMessage(tokens); 118 | } 119 | } 120 | 121 | void parseStructure(Token[] tokens, AST parent) { 122 | if (tokens.length < 4) { 123 | setErrorMessage(tokens); 124 | return; 125 | } 126 | if (!tokens.front.instanceOf!Atom || !tokens[1].instanceOf!LParen || !tokens.back.instanceOf!RParen) { 127 | setErrorMessage(tokens); 128 | return; 129 | } 130 | AST ast = new AST(new Functor(cast(Atom) tokens.front), tokens); 131 | parseTermList(tokens[2..$-1], ast); 132 | parent.children ~= ast; 133 | } 134 | 135 | void parseList(Token[] tokens, AST parent) { 136 | if (tokens.length < 2) { 137 | setErrorMessage(tokens); 138 | return; 139 | } 140 | if (!tokens.front.instanceOf!LBracket || !tokens.back.instanceOf!RBracket) { 141 | setErrorMessage(tokens); 142 | return; 143 | } 144 | AST ast = new AST(tokens.front, tokens[0..1]); 145 | if (tokens.length == 2) { 146 | // 空リスト 147 | ast.children ~= new AST(cast(Token) Atom.emptyList, []); 148 | } else { 149 | parseTermList(tokens[1..$-1], ast); 150 | } 151 | parent.children ~= ast; 152 | } 153 | 154 | Maybe!Operator findHighestOperator(Token[] tokens) { 155 | specifyOperators(tokens); 156 | auto gen = new Generator!Operator({ 157 | auto parenStack = DList!Token(); 158 | foreach(token; tokens) { 159 | if (token.instanceOf!LParen || token.instanceOf!LBracket) { 160 | parenStack.insertBack(token); 161 | } else if (token.instanceOf!RParen) { 162 | if (parenStack.empty) { 163 | setErrorMessage(tokens); 164 | return; 165 | } 166 | if (!parenStack.back.instanceOf!LParen) { 167 | setErrorMessage(tokens); 168 | return; 169 | } 170 | parenStack.removeBack; 171 | } else if (token.instanceOf!RBracket) { 172 | if (parenStack.empty) { 173 | setErrorMessage(tokens); 174 | return; 175 | } 176 | if (!parenStack.back.instanceOf!LBracket) { 177 | setErrorMessage(tokens); 178 | return; 179 | } 180 | parenStack.removeBack; 181 | } else if (token.instanceOf!Operator && parenStack.empty) { 182 | yield(cast(Operator) token); 183 | } 184 | } 185 | }); 186 | return (!gen.empty).fmap(gen.fold!((a, b) { 187 | if (a.precedence>b.precedence) return a; 188 | if (a.precedence t.lexeme).join(" ") 235 | ) 236 | ); 237 | } 238 | 239 | 240 | /* ---------- Unit Tests ---------- */ 241 | 242 | static void testAST(TAry...)(ASTRoot root, long[] inds...) { 243 | AST ast = root; 244 | foreach(i; inds) { 245 | assert(ast.children.length > i); 246 | ast = ast.children[i]; 247 | } 248 | assert(ast.children.length == TAry.length); 249 | foreach(i, T; TAry) { 250 | assert(ast.children[i].token.instanceOf!T); 251 | } 252 | } 253 | 254 | unittest { 255 | writeln(__FILE__, ": test parse 1"); 256 | 257 | auto lexer = new Lexer; 258 | auto parser = new Parser; 259 | lexer.run("hoge(X)."); 260 | parser.run(lexer.get()); 261 | assert(!parser.hasError); 262 | ASTRoot root = parser.get(); 263 | Parser.testAST!(Period)(root); 264 | Parser.testAST!(Functor)(root, 0); 265 | Parser.testAST!(Variable)(root, 0, 0); 266 | 267 | // root.writeln; 268 | } 269 | 270 | unittest { 271 | writeln(__FILE__, ": test parse 2"); 272 | 273 | auto lexer = new Lexer; 274 | auto parser = new Parser; 275 | lexer.run("aa(X) :- po(X, _)."); 276 | parser.run(lexer.get()); 277 | assert(!parser.hasError); 278 | ASTRoot root = parser.get(); 279 | Parser.testAST!(Period)(root); 280 | Parser.testAST!(Operator)(root, 0); 281 | Parser.testAST!(Functor, Functor)(root, 0, 0); 282 | Parser.testAST!(Variable)(root, 0, 0, 0); 283 | Parser.testAST!(Operator)(root, 0, 0, 1); 284 | Parser.testAST!(Variable, Variable)(root, 0, 0, 1, 0); 285 | 286 | // root.writeln; 287 | } 288 | 289 | unittest { 290 | writeln(__FILE__, ": test parse 3"); 291 | 292 | auto lexer = new Lexer; 293 | auto parser = new Parser; 294 | lexer.run("aa; _po. ('あ')."); 295 | parser.run(lexer.get()); 296 | assert(!parser.hasError); 297 | ASTRoot root = parser.get(); 298 | Parser.testAST!(Period, Period)(root); 299 | Parser.testAST!(Operator)(root, 0); 300 | Parser.testAST!(Atom, Variable)(root, 0, 0); 301 | Parser.testAST!(Atom)(root, 1); 302 | 303 | // root.writeln; 304 | } 305 | 306 | unittest { 307 | writeln(__FILE__, ": test parse 4"); 308 | 309 | auto lexer = new Lexer; 310 | auto parser = new Parser; 311 | lexer.run("X is 1 + 2 + Y * 10."); 312 | parser.run(lexer.get()); 313 | assert(!parser.hasError); 314 | ASTRoot root = parser.get(); 315 | Parser.testAST!(Period)(root); 316 | Parser.testAST!(Operator)(root, 0); 317 | Parser.testAST!(Variable, BinaryOperator)(root, 0, 0); 318 | Parser.testAST!(BinaryOperator, BinaryOperator)(root, 0, 0, 1); 319 | Parser.testAST!(Number, Number)(root, 0, 0, 1, 0); 320 | Parser.testAST!(Variable, Number)(root, 0, 0, 1, 1); 321 | 322 | // root.writeln; 323 | } 324 | 325 | unittest { 326 | writeln(__FILE__, ": test parse 5"); 327 | 328 | auto lexer = new Lexer; 329 | auto parser = new Parser; 330 | lexer.run("X is + 1."); 331 | parser.run(lexer.get()); 332 | assert(!parser.hasError); 333 | ASTRoot root = parser.get(); 334 | Parser.testAST!(Period)(root); 335 | Parser.testAST!(Operator)(root, 0); 336 | Parser.testAST!(Variable, UnaryOperator)(root, 0, 0); 337 | Parser.testAST!(Number)(root, 0, 0, 1); 338 | 339 | // root.writeln; 340 | } 341 | 342 | unittest { 343 | writeln(__FILE__, ": test parse 6"); 344 | 345 | auto lexer = new Lexer; 346 | auto parser = new Parser; 347 | lexer.run("conc([X|L1], L2, [X|List]) :- conc(L1, L2, List)."); 348 | parser.run(lexer.get()); 349 | assert(!parser.hasError); 350 | ASTRoot root = parser.get(); 351 | Parser.testAST!(Period)(root); 352 | Parser.testAST!(Operator)(root, 0); 353 | Parser.testAST!(Functor, Functor)(root, 0, 0); 354 | Parser.testAST!(Operator)(root, 0, 0, 0); 355 | Parser.testAST!(LBracket, Operator)(root, 0, 0, 0, 0); 356 | Parser.testAST!(Operator)(root, 0, 0, 0, 0, 0); 357 | Parser.testAST!(Variable, Variable)(root, 0, 0, 0, 0, 0, 0); 358 | Parser.testAST!(Variable, LBracket)(root, 0, 0, 0, 0, 1); 359 | Parser.testAST!(Operator)(root, 0, 0, 0, 0, 1, 1); 360 | Parser.testAST!(Variable, Variable)(root, 0, 0, 0, 0, 1, 1, 0); 361 | Parser.testAST!(Operator)(root, 0, 0, 1); 362 | Parser.testAST!(Variable, Operator)(root, 0, 0, 1, 0); 363 | Parser.testAST!(Variable, Variable)(root, 0, 0, 1, 0, 1); 364 | 365 | // root.writeln; 366 | } 367 | 368 | unittest { 369 | writeln(__FILE__, ": test error"); 370 | 371 | auto lexer = new Lexer; 372 | auto parser = new Parser; 373 | 374 | void testError(dstring src, bool isError) { 375 | lexer.run(src); 376 | assert(!lexer.hasError); 377 | parser.run(lexer.get); 378 | if (parser.hasError != isError) { 379 | lexer.get.writeln; 380 | parser.hasError.writeln; 381 | parser.errorMessage.writeln; 382 | parser.get.writeln; 383 | } 384 | assert(parser.hasError == isError); 385 | } 386 | 387 | testError("", false); 388 | testError(".....", false); 389 | testError("().", true); 390 | testError("+.", true); 391 | testError("*.", true); 392 | testError("hoge(X). po", true); 393 | testError("hoge(X).", false); 394 | testError("?- a :- b.", true); 395 | testError("?- .", true); 396 | testError("aa :- bb :- cc.", true); 397 | testError("aa :- (bb :- cc).", false); 398 | testError("hoge(aa aa).", true); 399 | testError("hoge(()).", true); 400 | testError("hoge((X).", true); 401 | testError("[].", false); 402 | testError("[|].", true); 403 | testError("[a|].", true); 404 | testError("[|a].", true); 405 | testError("+1+2+3.", false); 406 | testError("*1*2*3.", true); 407 | } 408 | 409 | } 410 | -------------------------------------------------------------------------------- /src/dprolog/core/Linenoise.d: -------------------------------------------------------------------------------- 1 | module dprolog.core.Linenoise; 2 | 3 | import dprolog.util.Maybe; 4 | import dprolog.util.Singleton; 5 | 6 | import std.conv; 7 | import std.string; 8 | 9 | import raw = deimos.linenoise; 10 | 11 | alias Linenoise = Singleton!Linenoise_; 12 | 13 | private class Linenoise_ { 14 | private enum defaultMaxHistoryLength = 100; 15 | 16 | this() { 17 | setMaxHistoryLength(defaultMaxHistoryLength); 18 | } 19 | 20 | Maybe!string nextLine(string prompt) { 21 | char* line = raw.linenoise(prompt.toStringz); 22 | return (line !is null).fmap!({ 23 | string result = line.fromStringz.to!string; 24 | destroy(line); 25 | return result; 26 | }); 27 | } 28 | 29 | bool addHistory(string line) { 30 | return raw.linenoiseHistoryAdd(line.toStringz) != 0; 31 | } 32 | 33 | bool setMaxHistoryLength(size_t length) { 34 | return raw.linenoiseHistorySetMaxLen(length.to!int) != 0; 35 | } 36 | 37 | bool saveHistory(string fileName) { 38 | return raw.linenoiseHistorySave(fileName.toStringz) != 0; 39 | } 40 | 41 | bool loadHistory(string fileName) { 42 | return raw.linenoiseHistoryLoad(fileName.toStringz) != 0; 43 | } 44 | 45 | void clearScreen() { 46 | raw.linenoiseClearScreen(); 47 | } 48 | 49 | void enableMultiLineMode() { 50 | raw.linenoiseSetMultiLine(1); 51 | } 52 | 53 | void disableMultiLineMode() { 54 | raw.linenoiseSetMultiLine(0); 55 | } 56 | 57 | /* 58 | The followings aren't wrapped. 59 | - struct linenoiseCompletions 60 | - alias linenoiseCompletionCallback 61 | - void linenoiseSetCompletionCallback(linenoiseCompletionCallback) 62 | - void linenoiseAddCompletion(linenoiseCompletions *, const char *) 63 | */ 64 | } 65 | -------------------------------------------------------------------------------- /src/dprolog/core/Shell.d: -------------------------------------------------------------------------------- 1 | module dprolog.core.Shell; 2 | 3 | import dprolog.util.Either; 4 | import dprolog.util.Message; 5 | import dprolog.util.Singleton; 6 | 7 | import std.stdio : StdioException; 8 | import std.conv; 9 | import std.string; 10 | import std.process; 11 | import std.file : chdir, FileException; 12 | import std.traits; 13 | 14 | alias Shell = Singleton!Shell_; 15 | 16 | private class Shell_ { 17 | 18 | private: 19 | alias Result = ReturnType!executeShell; 20 | 21 | public: 22 | Either!(Message, string[]) executeLs() { 23 | return execute("ls").bind!((result) { 24 | if (result.status == 0) { 25 | return result.output.chomp.splitLines.Right!(Message, string[]); 26 | } else { 27 | return ErrorMessage(result.output.chomp).Left!(Message, string[]); 28 | } 29 | }); 30 | } 31 | 32 | Either!(Message, string[]) executeLsWithPath(string path) { 33 | return execute("ls " ~ path).bind!((result) { 34 | if (result.status == 0) { 35 | return result.output.chomp.splitLines.Right!(Message, string[]); 36 | } else { 37 | return ErrorMessage(result.output.chomp).Left!(Message, string[]); 38 | } 39 | }); 40 | } 41 | 42 | Either!(Message, string[]) executeCdWithPath(string path) { 43 | return execute("echo " ~ path).bind!((result) { 44 | try { 45 | if (result.status == 0) { 46 | result.output.chomp.chdir; 47 | } else { 48 | path.chdir; 49 | } 50 | return [].Right!(Message, string[]); 51 | } catch (FileException e) { 52 | return ErrorMessage(e.msg).Left!(Message, string[]); 53 | } 54 | }); 55 | } 56 | 57 | private: 58 | Either!(Message, Result) execute(string command) { 59 | try { 60 | return executeShell(command).Right!(Message, Result); 61 | } catch (ProcessException e) { 62 | return ErrorMessage(e.msg).Left!(Message, Result); 63 | } catch (StdioException e) { 64 | return ErrorMessage(e.msg).Left!(Message, Result); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/dprolog/core/tty.d: -------------------------------------------------------------------------------- 1 | module dprolog.core.tty; 2 | 3 | import dprolog.util.Either; 4 | import dprolog.util.Message; 5 | 6 | import core.sys.posix.sys.ioctl; 7 | import std.stdio; 8 | 9 | Either!(Message, int) getColumns() { 10 | auto size = winsize(); 11 | if (ioctl(stdout.fileno(), TIOCGWINSZ, &size) == -1) { 12 | return ErrorMessage("Cannot get column size").Left!(Message, int); 13 | } 14 | return Right!(Message, int)(size.ws_col); 15 | } 16 | 17 | Either!(Message, int) getRows() { 18 | auto size = winsize(); 19 | if (ioctl(stdout.fileno(), TIOCGWINSZ, &size) == -1) { 20 | return ErrorMessage("Cannot get row size").Left!(Message, int); 21 | } 22 | return Right!(Message, int)(size.ws_row); 23 | } 24 | -------------------------------------------------------------------------------- /src/dprolog/data/AST.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.AST; 2 | 3 | import dprolog.data.token; 4 | 5 | import std.range; 6 | import std.format; 7 | import std.algorithm; 8 | 9 | // Abstract Syntax Tree 10 | 11 | class AST { 12 | Token token; 13 | Token[] tokenList; 14 | AST[] children; 15 | 16 | this(Token token, Token[] tokenList) { 17 | this.token = token; 18 | this.tokenList = tokenList; 19 | this.children = []; 20 | } 21 | 22 | override string toString() const { 23 | return toString(0); 24 | } 25 | 26 | protected string toString(long tabCount) const { 27 | string tab = "\t".repeat(tabCount).join; 28 | return format!"%sAST(token: %s, chidlen: [%-(\n%s,%)\n%s])"( 29 | tab, 30 | token, 31 | children.map!( 32 | c => c.toString(tabCount + 1) 33 | ), 34 | tab 35 | ); 36 | } 37 | } 38 | 39 | class ASTRoot : AST { 40 | 41 | this(Token[] tokenList) { 42 | super(null, tokenList); 43 | } 44 | 45 | override string toString() const { 46 | return toString(0); 47 | } 48 | 49 | protected override string toString(long tabCount) const { 50 | string tab = "\t".repeat(tabCount).join; 51 | return format!"%sASTRoot(children: [%-(\n%s,%)\n%s])"( 52 | tab, 53 | children.map!( 54 | c => c.toString(tabCount + 1) 55 | ), 56 | tab 57 | ); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/dprolog/data/Clause.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.Clause; 2 | 3 | import dprolog.data.token; 4 | import dprolog.data.Term; 5 | import dprolog.util.UnionFind; 6 | 7 | import std.format; 8 | import std.algorithm; 9 | 10 | abstract class Clause {} 11 | 12 | class Fact : Clause { 13 | Term first; 14 | 15 | this(Term first) { 16 | this.first = first; 17 | } 18 | 19 | override string toString() const { 20 | return format!"Fact(\"%s.\")"(first); 21 | } 22 | 23 | invariant { 24 | assert(!first.isCompound); 25 | } 26 | } 27 | 28 | class Rule : Clause { 29 | Term first; 30 | Term second; 31 | 32 | this(Term first, Term second) { 33 | this.first = first; 34 | this.second = second; 35 | } 36 | 37 | override string toString() const { 38 | return format!"Rule(\"%s :- %s.\")"(first, second); 39 | } 40 | 41 | invariant { 42 | assert(!first.isCompound); 43 | } 44 | } 45 | 46 | class Query : Clause { 47 | Term first; 48 | 49 | this(Term first) { 50 | this.first = first; 51 | } 52 | 53 | override string toString() const { 54 | return format!"Query(\"?- %s.\")"(first); 55 | } 56 | 57 | bool hasOnlyUnderscore() { 58 | bool rec(Term term) { 59 | if (term.isVariable && !term.token.isUnderscore) return false; 60 | return term.children.all!rec; 61 | } 62 | return rec(first); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/dprolog/data/Command.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.Command; 2 | 3 | import dprolog.data.Term; 4 | 5 | interface Command { 6 | bool isMatch(Term term); 7 | void execute(Term term); 8 | } 9 | -------------------------------------------------------------------------------- /src/dprolog/data/Predicate.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.Predicate; 2 | 3 | import dprolog.data.Term; 4 | import dprolog.data.Variant; 5 | import dprolog.engine.Executor; 6 | import dprolog.engine.UnificationUF; 7 | 8 | interface Predicate { 9 | alias UnificateRecFun = UnificateResult delegate(Variant, UnificationUF); 10 | 11 | bool isMatch(const Term term); 12 | UnificateResult unificate(Variant variant, UnificationUF unionFind, UnificateRecFun unificateRecFun); 13 | } 14 | -------------------------------------------------------------------------------- /src/dprolog/data/Term.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.Term; 2 | 3 | import dprolog.data.token; 4 | import dprolog.util.functions; 5 | 6 | import std.stdio; 7 | import std.format; 8 | import std.algorithm; 9 | import std.range; 10 | 11 | class Term { 12 | Token token; 13 | Term[] children; 14 | immutable bool isDetermined; 15 | immutable bool isCompound; 16 | 17 | this(Token token, Term[] children) { 18 | this.token = token; 19 | this.children = children; 20 | this.isDetermined = (token.instanceOf!Atom && children.all!(c => c.isDetermined)) || token.instanceOf!Number; 21 | this.isCompound = token==Operator.comma || token==Operator.semicolon; 22 | } 23 | 24 | @property bool isAtom() const { 25 | return token.instanceOf!Atom; 26 | } 27 | 28 | @property bool isNumber() const { 29 | return token.instanceOf!Number; 30 | } 31 | 32 | @property bool isVariable() const { 33 | return token.instanceOf!Variable; 34 | } 35 | 36 | @property bool isStructure() const { 37 | return token.instanceOf!Functor || token.instanceOf!Operator; 38 | } 39 | 40 | override string toString() const { 41 | if (isCompound) { 42 | return format!"( %s %s %s )"(children.front, token.lexeme, children.back); 43 | } else if (isStructure) { 44 | if (token == Operator.pipe) { 45 | return format!"[%s]"(toListString()); 46 | } else { 47 | return format!"%s(%-(%s, %))"(token.lexeme, children); 48 | } 49 | } else { 50 | if (token == Atom.emptyList) { 51 | return "[]"; 52 | } else { 53 | return format!"%s"(token.lexeme); 54 | } 55 | } 56 | } 57 | private string toListString() const in { 58 | assert(token == Operator.pipe); 59 | assert(children.length == 2); 60 | } do { 61 | if (children.back.token == Atom.emptyList) { 62 | return format!"%s"(children.front); 63 | } else if (children.back.token == Operator.pipe) { 64 | return format!"%s, %s"(children.front, children.back.toListString()); 65 | } else { 66 | return format!"%s%s%s"(children.front, token.lexeme, children.back); 67 | } 68 | } 69 | 70 | invariant { 71 | assert( 72 | (token.instanceOf!Atom ) || 73 | (token.instanceOf!Number && children.empty) || 74 | (token.instanceOf!Variable && children.empty) 75 | ); 76 | assert(token != Operator.rulifier); 77 | assert(token != Operator.queryfier); 78 | assert(isDetermined == ((token.instanceOf!Atom && children.all!(c => c.isDetermined)) || token.instanceOf!Number)); 79 | } 80 | 81 | 82 | /* ---------- Unit Tests ---------- */ 83 | 84 | unittest { 85 | writeln(__FILE__, ": test"); 86 | 87 | Atom atom = new Atom("a", -1, -1); 88 | Number num = new Number("1", -1, -1); 89 | Variable var = new Variable("X", -1, -1); 90 | Functor fun = new Functor(atom); 91 | Operator pipe = cast(Operator) Operator.pipe; 92 | Operator comma = cast(Operator) Operator.comma; 93 | 94 | Term atomT = new Term(atom, []); 95 | Term numT = new Term(num, []); 96 | Term varT = new Term(var, []); 97 | Term funT = new Term(fun, [atomT, varT, numT]); 98 | Term listT = new Term(pipe, [funT, new Term(pipe, [numT, varT])]); 99 | Term comT = new Term(comma, [listT, funT]); 100 | 101 | assert(atomT.isDetermined); 102 | assert(numT.isDetermined); 103 | assert(!varT.isDetermined); 104 | assert(!funT.isDetermined); 105 | assert(!listT.isDetermined); 106 | assert(!listT.children.back.isDetermined); 107 | assert(!listT.children.back.children.back.isDetermined); 108 | assert(!comT.isDetermined); 109 | 110 | assert(!atomT.isCompound); 111 | assert(!numT.isCompound); 112 | assert(!varT.isCompound); 113 | assert(!funT.isCompound); 114 | assert(!listT.isCompound); 115 | assert(!listT.children.back.isCompound); 116 | assert(!listT.children.back.children.back.isCompound); 117 | assert(comT.isCompound); 118 | 119 | assert(atomT.isAtom); 120 | assert(numT.isNumber); 121 | assert(varT.isVariable); 122 | assert(funT.isStructure); 123 | assert(listT.isStructure); 124 | assert(listT.children.back.isStructure); 125 | assert(listT.children.back.children.back.isVariable); 126 | assert(comT.isStructure); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/dprolog/data/Variant.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.Variant; 2 | 3 | import dprolog.data.Term; 4 | 5 | import std.format; 6 | import std.range; 7 | import std.algorithm; 8 | 9 | class Variant { 10 | private const long id; 11 | Term term; 12 | Variant[] children; 13 | 14 | this(long id, Term term, Variant[] children) { 15 | this.id = id; 16 | this.term = term; 17 | this.children = children; 18 | } 19 | 20 | bool isVariable() { 21 | return term.isVariable; 22 | } 23 | 24 | override hash_t toHash() { 25 | hash_t hash = term.token.toHash; 26 | return id.hashOf(hash); 27 | } 28 | 29 | override bool opEquals(Object o) { 30 | auto that = cast(Variant) o; 31 | return that && this.id==that.id && this.term.token==that.term.token && this.children.length==that.children.length && zip(this.children, that.children).all!(a => a[0] == a[1]); 32 | } 33 | 34 | override string toString() const { 35 | return format!"Variant(id: %s, term: %s, children: %s)"(id, term, children); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/dprolog/data/token/Atom.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.Atom; 2 | 3 | import dprolog.data.token; 4 | 5 | import std.format; 6 | 7 | class Atom : Token { 8 | 9 | this(dstring lexeme, long line, long column) { 10 | super(lexeme, line, column); 11 | } 12 | 13 | override bool opEquals(Object o) { 14 | auto that = cast(Atom) o; 15 | return that && this.lexeme==that.lexeme; 16 | } 17 | 18 | override string toString() const { 19 | return format!"Atom(lexeme: \"%s\")"(lexeme); 20 | } 21 | 22 | static immutable Atom emptyList = cast(immutable) new Atom("", -1, -1); // for using an empty list 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/dprolog/data/token/Functor.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.Functor; 2 | 3 | import dprolog.data.token; 4 | 5 | import std.format; 6 | 7 | class Functor : Atom { 8 | 9 | this(Atom atom) { 10 | super(atom.lexeme, atom.line, atom.column); 11 | } 12 | 13 | override bool opEquals(Object o) { 14 | auto that = cast(Functor) o; 15 | return that && this.lexeme==that.lexeme; 16 | } 17 | 18 | override string toString() const { 19 | return format!"Functor(lexeme: \"%s\")"(lexeme); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/dprolog/data/token/LBracket.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.LBracket; 2 | 3 | import dprolog.data.token; 4 | 5 | import std.format; 6 | 7 | class LBracket : Token { 8 | 9 | this(dstring lexeme, long line, long column) { 10 | super(lexeme, line, column); 11 | } 12 | 13 | override bool opEquals(Object o) { 14 | return true; 15 | } 16 | 17 | override string toString() const { 18 | return format!"LBracket(lexeme: \"%s\")"(lexeme); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/dprolog/data/token/LParen.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.LParen; 2 | 3 | import dprolog.data.token; 4 | 5 | import std.format; 6 | 7 | class LParen : Token { 8 | 9 | this(dstring lexeme, long line, long column) { 10 | super(lexeme, line, column); 11 | } 12 | 13 | override bool opEquals(Object o) { 14 | return true; 15 | } 16 | 17 | override string toString() const { 18 | return format!"LParen(lexeme: \"%s\")"(lexeme); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/dprolog/data/token/Number.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.Number; 2 | 3 | import dprolog.data.token; 4 | 5 | import std.stdio; 6 | import std.conv; 7 | import std.algorithm; 8 | import std.range; 9 | import std.format; 10 | import std.bigint; 11 | 12 | class Number : Token { 13 | 14 | private immutable BigInt value; 15 | this(dstring lexeme, long line, long column) { 16 | string literal = lexeme.to!string; 17 | if (isBinaryLiteral(literal)) { 18 | literal = convertBinaryToHexadecimal(literal); 19 | } 20 | this(BigInt(literal), line, column); 21 | } 22 | this(BigInt value, long line = -1, long column = -1) { 23 | super(value.to!dstring, line, column); 24 | this.value = value; 25 | } 26 | 27 | Number opUnary(string op)() 28 | if (op=="+" || op=="-") { 29 | return new Number(mixin(op ~ "this.value")); 30 | } 31 | 32 | Number opBinary(string op)(Number that) 33 | if (op=="+" || op=="-" || op=="*" || op=="/" || op=="%") { 34 | return new Number(mixin("this.value" ~ op ~ "that.value")); 35 | } 36 | 37 | override bool opEquals(Object o) { 38 | auto that = cast(Number) o; 39 | return that && this.lexeme==that.lexeme; 40 | } 41 | 42 | BigInt opCmp(Number that) const { 43 | return this.value - that.value; 44 | } 45 | 46 | override string toString() const { 47 | return format!"Number(value: %s)"(value); 48 | } 49 | 50 | private: 51 | static bool isBinaryLiteral(string binary) { 52 | return binary.startsWith("0b") && binary.length > 2 && binary.drop(2).all!"a=='0' || a=='1'"; 53 | } 54 | static string convertBinaryToHexadecimal(string binary) in(isBinaryLiteral(binary)) do { 55 | int[] nums = binary.drop(2).retro.map!(c => c.to!string.to!int).array; 56 | int[] hexadecimals = []; 57 | foreach(i; 0..int.max) { 58 | if (4*i >= nums.length) break; 59 | int h = 0; 60 | foreach(j; 0..4) { 61 | if (4*i+j >= nums.length) break; 62 | h += nums[4*i+j]< h.to!string(16)).retro.join.to!string; 67 | } 68 | 69 | unittest { 70 | writeln(__FILE__, ": test convertBinaryToHexadecimal"); 71 | 72 | assert(convertBinaryToHexadecimal("0b0") == "0x0"); 73 | assert(convertBinaryToHexadecimal("0b1010") == "0xA"); 74 | assert(convertBinaryToHexadecimal("0b111111") == "0x3F"); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/dprolog/data/token/Period.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.Period; 2 | 3 | import dprolog.data.token; 4 | 5 | import std.format; 6 | 7 | class Period : Token { 8 | 9 | this(dstring lexeme, long line, long column) { 10 | super(lexeme, line, column); 11 | } 12 | 13 | override bool opEquals(Object o) { 14 | return true; 15 | } 16 | 17 | override string toString() const { 18 | return format!"Period(lexeme: \"%s\")"(lexeme); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/dprolog/data/token/RBracket.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.RBracket; 2 | 3 | import dprolog.data.token; 4 | 5 | import std.format; 6 | 7 | class RBracket : Token { 8 | 9 | this(dstring lexeme, long line, long column) { 10 | super(lexeme, line, column); 11 | } 12 | 13 | override bool opEquals(Object o) { 14 | return true; 15 | } 16 | 17 | override string toString() const { 18 | return format!"RBracket(lexeme: \"%s\")"(lexeme); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/dprolog/data/token/RParen.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.RParen; 2 | 3 | import dprolog.data.token; 4 | 5 | import std.format; 6 | 7 | class RParen : Token { 8 | 9 | this(dstring lexeme, long line, long column) { 10 | super(lexeme, line, column); 11 | } 12 | 13 | override bool opEquals(Object o) { 14 | return true; 15 | } 16 | 17 | override string toString() const { 18 | return format!"RParen(lexeme: \"%s\")"(lexeme); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/dprolog/data/token/Token.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.Token; 2 | 3 | import std.format; 4 | 5 | abstract class Token { 6 | 7 | static immutable dstring specialCharacters = ":?&;,|=<>+-*/\\"; 8 | 9 | immutable long line; 10 | immutable long column; 11 | immutable dstring lexeme; 12 | this(dstring lexeme, long line, long column) { 13 | this.lexeme = lexeme; 14 | this.line = line; 15 | this.column = column; 16 | } 17 | 18 | bool isUnderscore() { 19 | return lexeme == "_"; 20 | } 21 | 22 | override hash_t toHash() { 23 | hash_t hash = 0u; 24 | foreach(c; lexeme) { 25 | hash = c.hashOf(hash); 26 | } 27 | return hash; 28 | } 29 | 30 | override string toString() const { 31 | return format!"Token(lexeme: \"%s\")"(lexeme); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/dprolog/data/token/Variable.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.Variable; 2 | 3 | import dprolog.data.token; 4 | 5 | import std.format; 6 | 7 | class Variable : Token { 8 | 9 | this(dstring lexeme, long line, long column) { 10 | super(lexeme, line, column); 11 | } 12 | 13 | override bool opEquals(Object o) { 14 | auto that = cast(Variable) o; 15 | return that && this.lexeme==that.lexeme; 16 | } 17 | 18 | override string toString() const { 19 | return format!"Variable(lexeme: \"%s\")"(lexeme); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/dprolog/data/token/operator/BinaryOperator.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.operator.BinaryOperator; 2 | 3 | import dprolog.data.token; 4 | 5 | import std.stdio; 6 | import std.format; 7 | import std.functional; 8 | 9 | abstract class BinaryOperator : Operator { 10 | private this(dstring lexeme, long precedence, string type, long line, long column) { 11 | super(lexeme, precedence, type, line, column); 12 | } 13 | Number calc(Number x, Number y); 14 | override string toString() const { 15 | return format!"BinaryOperator(lexeme: \"%s\", precedence: %s, type: %s)"(lexeme, precedence, type); 16 | } 17 | } 18 | 19 | BinaryOperator makeBinaryOperator(alias fun)(dstring lexeme, long precedence, string type, long line = -1, long column = -1) 20 | if (is(typeof(binaryFun!fun(Number.init, Number.init)) == Number)) { 21 | return new class(lexeme, precedence, type, line, column) BinaryOperator { 22 | this(dstring lexeme, long precedence, string type, long line, long column) { 23 | super(lexeme, precedence, type, line, column); 24 | } 25 | override Number calc(Number x, Number y) { 26 | return binaryFun!fun(x, y); 27 | } 28 | override protected Operator make(long line, long column) const { 29 | return makeBinaryOperator!fun(this.lexeme, this.precedence, this.type, line, column); 30 | } 31 | }; 32 | } 33 | 34 | unittest { 35 | writeln(__FILE__, ": test BinaryOperator"); 36 | 37 | BinaryOperator plusOp = makeBinaryOperator!"a + b"("+", 500, "yfx"); 38 | BinaryOperator multOp = makeBinaryOperator!"a * b"("*", 500, "yfx"); 39 | 40 | import std.bigint; 41 | Number num(long value) { 42 | return new Number(BigInt(value)); 43 | } 44 | 45 | assert(plusOp.calc(num(121), num(12)) == num(121 + 12)); 46 | assert(multOp.calc(num(921), num(19)) == num(921 * 19)); 47 | } 48 | -------------------------------------------------------------------------------- /src/dprolog/data/token/operator/ComparisonOperator.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.operator.ComparisonOperator; 2 | 3 | import dprolog.data.token; 4 | 5 | import std.stdio; 6 | import std.format; 7 | import std.functional; 8 | 9 | abstract class ComparisonOperator : Operator { 10 | private this(dstring lexeme, long precedence, string type, long line, long column) { 11 | super(lexeme, precedence, type, line, column); 12 | } 13 | bool calc(Number x, Number y); 14 | override string toString() const { 15 | return format!"ComparisonOperator(lexeme: \"%s\", precedence: %s, type: %s)"(lexeme, precedence, type); 16 | } 17 | } 18 | 19 | ComparisonOperator makeComparisonOperator(alias fun)(dstring lexeme, long precedence, string type, long line = -1, long column = -1) 20 | if (is(typeof(binaryFun!fun(Number.init, Number.init)) == bool)) { 21 | return new class(lexeme, precedence, type, line, column) ComparisonOperator { 22 | this(dstring lexeme, long precedence, string type, long line, long column) { 23 | super(lexeme, precedence, type, line, column); 24 | } 25 | override bool calc(Number x, Number y) { 26 | return binaryFun!fun(x, y); 27 | } 28 | override protected Operator make(long line, long column) const { 29 | return makeComparisonOperator!fun(this.lexeme, this.precedence, this.type, line, column); 30 | } 31 | }; 32 | } 33 | 34 | unittest { 35 | writeln(__FILE__, ": test ComparisonOperator"); 36 | 37 | ComparisonOperator lessOp = makeComparisonOperator!"a < b"("<", 700, "xfx"); 38 | ComparisonOperator eqOp = makeComparisonOperator!"a == b"("=:=", 700, "xfx"); 39 | ComparisonOperator neqOp = makeComparisonOperator!"a != b"("=\\=", 700, "xfx"); 40 | 41 | import std.bigint; 42 | Number num(long value) { 43 | return new Number(BigInt(value)); 44 | } 45 | 46 | assert(lessOp.calc(num(1), num(10)) == (1 < 10)); 47 | assert(lessOp.calc(num(10), num(1)) == (10 < 1)); 48 | assert(lessOp.calc(num(1), num(1)) == (1 < 1)); 49 | assert(eqOp.calc(num(1), num(1)) == (1 == 1)); 50 | assert(eqOp.calc(num(1), num(-1)) == (1 == -1)); 51 | assert(neqOp.calc(num(1), num(1)) == (1 != 1)); 52 | assert(neqOp.calc(num(1), num(-1)) == (1 != -1)); 53 | } 54 | -------------------------------------------------------------------------------- /src/dprolog/data/token/operator/Operator.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.operator.Operator; 2 | 3 | import dprolog.data.token; 4 | import dprolog.util.Maybe; 5 | 6 | import std.format; 7 | import std.algorithm; 8 | import std.range; 9 | import std.functional; 10 | 11 | class Operator : Atom { 12 | 13 | immutable long precedence; 14 | immutable string type; 15 | @property Notation notation() { 16 | switch(type) { 17 | case "fx" : case "fy" : return Notation.Prefix; 18 | case "xfx": case "xfy": case "yfx": return Notation.Infix; 19 | case "xf" : case "yf" : return Notation.Postfix; 20 | default: assert(false); 21 | } 22 | } 23 | 24 | override bool opEquals(Object o) { 25 | auto that = cast(Operator) o; 26 | return that && this.lexeme==that.lexeme && this.precedence==that.precedence && this.type==that.type; 27 | } 28 | 29 | override string toString() const { 30 | return format!"Operator(lexeme: \"%s\", precedence: %s, type: %s)"(lexeme, precedence, type); 31 | } 32 | 33 | static Maybe!Operator getOperator(Atom atom, Notation notation) { 34 | string[] types = getTypes(notation); 35 | auto ary = systemOperatorList.find!( 36 | op => op.lexeme==atom.lexeme && types.canFind(op.type) 37 | ); 38 | return ary.empty ? None!Operator : Just(ary.front.make(atom.line, atom.column)); 39 | } 40 | 41 | static private string[] getTypes(Notation notation) { 42 | final switch(notation) { 43 | case Notation.Prefix : return ["fx", "fy"]; 44 | case Notation.Infix : return ["xfx", "xfy", "yfx"]; 45 | case Notation.Postfix : return ["xf", "yf"]; 46 | } 47 | } 48 | 49 | protected this(dstring lexeme, long precedence, string type, long line = -1, long column = -1) { 50 | super(lexeme, line, column); 51 | this.precedence = precedence; 52 | this.type = type; 53 | } 54 | 55 | protected Operator make(long line, long column) const { 56 | return new Operator(this.lexeme, this.precedence, this.type, line, column); 57 | } 58 | 59 | enum Notation { 60 | Prefix, Infix, Postfix 61 | } 62 | 63 | static immutable { 64 | Operator rulifier = cast(immutable) new Operator(":-", 1200, "xfx"); 65 | Operator queryfier = cast(immutable) new Operator("?-", 1200, "fx"); 66 | Operator semicolon = cast(immutable) new Operator(";", 1100, "xfy"); 67 | Operator comma = cast(immutable) new Operator(",", 1000, "xfy"); 68 | Operator pipe = cast(immutable) new Operator("|", 1100, "xfy"); 69 | Operator equal = cast(immutable) new Operator("=", 700, "xfx"); 70 | Operator equalEqual = cast(immutable) new Operator("==", 700, "xfx"); 71 | Operator eval = cast(immutable) new Operator("is", 700, "xfx"); 72 | } 73 | 74 | static private immutable immutable(Operator)[] systemOperatorList = [ 75 | rulifier, 76 | queryfier, 77 | semicolon, 78 | pipe, 79 | comma, 80 | equal, 81 | equalEqual, 82 | eval, 83 | cast(immutable) makeComparisonOperator!"a < b"("<", 700, "xfx"), 84 | cast(immutable) makeComparisonOperator!"a <= b"("=<", 700, "xfx"), 85 | cast(immutable) makeComparisonOperator!"a > b"(">", 700, "xfx"), 86 | cast(immutable) makeComparisonOperator!"a >= b"(">=", 700, "xfx"), 87 | cast(immutable) makeComparisonOperator!"a == b"("=:=", 700, "xfx"), 88 | cast(immutable) makeComparisonOperator!"a != b"("=\\=", 700, "xfx"), 89 | cast(immutable) makeBinaryOperator!"a + b"("+", 500, "yfx"), 90 | cast(immutable) makeBinaryOperator!"a - b"("-", 500, "yfx"), 91 | cast(immutable) makeBinaryOperator!"a * b"("*", 400, "yfx"), 92 | cast(immutable) makeBinaryOperator!"a / b"("div", 400, "yfx"), 93 | cast(immutable) makeBinaryOperator!"a % b"("mod", 400, "yfx"), 94 | cast(immutable) makeUnaryOperator!"+a"("+", 200, "fy"), 95 | cast(immutable) makeUnaryOperator!"-a"("-", 200, "fy") 96 | ]; 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/dprolog/data/token/operator/UnaryOperator.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.operator.UnaryOperator; 2 | 3 | import dprolog.data.token; 4 | 5 | import std.stdio; 6 | import std.format; 7 | import std.functional; 8 | 9 | abstract class UnaryOperator : Operator { 10 | private this(dstring lexeme, long precedence, string type, long line, long column) { 11 | super(lexeme, precedence, type, line, column); 12 | } 13 | Number calc(Number x); 14 | override string toString() const { 15 | return format!"UnaryOperator(lexeme: \"%s\", precedence: %s, type: %s)"(lexeme, precedence, type); 16 | } 17 | } 18 | 19 | UnaryOperator makeUnaryOperator(alias fun)(dstring lexeme, long precedence, string type, long line = -1, long column = -1) 20 | if (is(typeof(unaryFun!fun(Number.init)) == Number)) { 21 | return new class(lexeme, precedence, type, line, column) UnaryOperator { 22 | this(dstring lexeme, long precedence, string type, long line, long column) { 23 | super(lexeme, precedence, type, line, column); 24 | } 25 | override Number calc(Number x) { 26 | return unaryFun!fun(x); 27 | } 28 | override protected Operator make(long line, long column) const { 29 | return makeUnaryOperator!fun(this.lexeme, this.precedence, this.type, line, column); 30 | } 31 | }; 32 | } 33 | 34 | unittest { 35 | writeln(__FILE__, ": test UnaryOperator"); 36 | 37 | UnaryOperator plusOp = makeUnaryOperator!"+a"("+", 200, "fy"); 38 | UnaryOperator subOp = makeUnaryOperator!"-a"("-", 200, "fy"); 39 | 40 | import std.bigint; 41 | Number num(long value) { 42 | return new Number(BigInt(value)); 43 | } 44 | 45 | assert(plusOp.calc(num(10)) == num(+10)); 46 | assert(subOp.calc(num(10)) == num(-10)); 47 | assert(subOp.calc(subOp.calc(num(10))) == num(-(-10))); 48 | } 49 | -------------------------------------------------------------------------------- /src/dprolog/data/token/operator/package.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token.operator; 2 | 3 | public { 4 | import dprolog.data.token.operator.Operator; 5 | import dprolog.data.token.operator.UnaryOperator; 6 | import dprolog.data.token.operator.BinaryOperator; 7 | import dprolog.data.token.operator.ComparisonOperator; 8 | } 9 | -------------------------------------------------------------------------------- /src/dprolog/data/token/package.d: -------------------------------------------------------------------------------- 1 | module dprolog.data.token; 2 | 3 | public { 4 | import dprolog.data.token.Token; 5 | import dprolog.data.token.Atom; 6 | import dprolog.data.token.Number; 7 | import dprolog.data.token.Variable; 8 | import dprolog.data.token.Functor; 9 | import dprolog.data.token.operator; 10 | import dprolog.data.token.LParen; 11 | import dprolog.data.token.RParen; 12 | import dprolog.data.token.LBracket; 13 | import dprolog.data.token.RBracket; 14 | import dprolog.data.token.Period; 15 | } 16 | -------------------------------------------------------------------------------- /src/dprolog/engine/Consulter.d: -------------------------------------------------------------------------------- 1 | module dprolog.engine.Consulter; 2 | 3 | import dprolog.engine.Executor; 4 | import dprolog.util.Singleton; 5 | import dprolog.core.Linenoise; 6 | 7 | import std.conv; 8 | import std.array; 9 | 10 | alias Consulter = Singleton!Consulter_; 11 | 12 | private class Consulter_ { 13 | void consult() { 14 | dstring[] texts = []; 15 | string prompt = "|: "; 16 | while(true) { 17 | auto line = Linenoise.nextLine(prompt); 18 | if (line.isJust) { 19 | texts ~= line.get.to!dstring; 20 | } else { 21 | break; 22 | } 23 | } 24 | Executor.execute(texts.join("\n")); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/dprolog/engine/Engine.d: -------------------------------------------------------------------------------- 1 | module dprolog.engine.Engine; 2 | 3 | import dprolog.data.token; 4 | import dprolog.engine.Executor; 5 | import dprolog.engine.Messenger; 6 | import dprolog.util.Message; 7 | import dprolog.util.Singleton; 8 | import dprolog.core.Linenoise; 9 | 10 | import std.conv; 11 | 12 | alias Engine = Singleton!Engine_; 13 | 14 | private class Engine_ { 15 | 16 | private: 17 | bool _isHalt = false; 18 | bool _verboseMode = false; 19 | 20 | public: 21 | void next() in(!isHalt) do { 22 | dstring queryfier = Operator.queryfier.lexeme ~ " "; 23 | auto line = Linenoise.nextLine(queryfier.to!string); 24 | if (line.isJust) { 25 | Linenoise.addHistory(line.get); 26 | dstring clause = line.get.to!dstring; 27 | Executor.execute(queryfier ~ clause); 28 | Messenger.showAll(); 29 | Messenger.writeln(DefaultMessage("")); 30 | } else { 31 | halt(); 32 | } 33 | } 34 | 35 | void halt() { 36 | _isHalt = true; 37 | } 38 | 39 | @property bool isHalt() { 40 | return _isHalt; 41 | } 42 | 43 | @property bool verboseMode() { 44 | return _verboseMode; 45 | } 46 | @property bool verboseMode(bool value) { 47 | return _verboseMode = value; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/dprolog/engine/Evaluator.d: -------------------------------------------------------------------------------- 1 | module dprolog.engine.Evaluator; 2 | 3 | import dprolog.data.token; 4 | import dprolog.data.Variant; 5 | import dprolog.util.Message; 6 | import dprolog.util.Either; 7 | import dprolog.util.Singleton; 8 | import dprolog.engine.UnificationUF; 9 | 10 | import std.range; 11 | import std.algorithm; 12 | 13 | alias Evaluator = Singleton!Evaluator_; 14 | 15 | private class Evaluator_ { 16 | 17 | Either!(Message, Number) calc(Variant variant, UnificationUF unionFind) { 18 | Variant root = unionFind.root(variant); 19 | return root.term.token.castSwitch!( 20 | (BinaryOperator op) => calc(root.children.front, unionFind).bind!( 21 | x => calc(root.children.back, unionFind).fmap!( 22 | y => op.calc(x, y) 23 | ) 24 | ), 25 | (UnaryOperator op) => calc(root.children.front, unionFind).fmap!( 26 | x => op.calc(x) 27 | ), 28 | (Number num) => Right!(Message, Number)(num), 29 | (Object _) => Left!(Message, Number)(WarningMessage("Warning: Evaluation Fault")) 30 | ); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/dprolog/engine/Executor.d: -------------------------------------------------------------------------------- 1 | module dprolog.engine.Executor; 2 | 3 | import dprolog.data.token; 4 | import dprolog.data.Term; 5 | import dprolog.data.Clause; 6 | import dprolog.data.Variant; 7 | import dprolog.util.Message; 8 | import dprolog.converter.Converter; 9 | import dprolog.converter.Lexer; 10 | import dprolog.converter.Parser; 11 | import dprolog.converter.ClauseBuilder; 12 | import dprolog.util.functions; 13 | import dprolog.util.Maybe; 14 | import dprolog.util.Either; 15 | import dprolog.util.Singleton; 16 | import dprolog.engine.Engine; 17 | import dprolog.engine.Messenger; 18 | import dprolog.engine.builtIn.BuiltInCommand; 19 | import dprolog.engine.builtIn.BuiltInPredicate; 20 | import dprolog.engine.Evaluator; 21 | import dprolog.engine.UnificationUF; 22 | import dprolog.core.Linenoise; 23 | 24 | import std.format; 25 | import std.conv; 26 | import std.range; 27 | import std.array; 28 | import std.algorithm; 29 | import std.functional; 30 | import std.typecons; 31 | import std.concurrency : Generator, yield; 32 | 33 | alias UnificateResult = Tuple!(bool, "found", bool, "isCutted"); 34 | 35 | class CallUnificateNotifier {} 36 | alias UnificateYield = Either!(CallUnificateNotifier, UnificationUF); 37 | 38 | alias Executor = Singleton!Executor_; 39 | 40 | private class Executor_ { 41 | 42 | private: 43 | Lexer _lexer; 44 | Parser _parser; 45 | ClauseBuilder _clauseBuilder; 46 | 47 | Clause[] _storage; 48 | 49 | public: 50 | this() { 51 | _lexer = new Lexer; 52 | _parser = new Parser; 53 | _clauseBuilder = new ClauseBuilder; 54 | clear(); 55 | } 56 | 57 | void execute(dstring src) in(!Engine.isHalt) do { 58 | toClauseList(src).apply!((clauseList) { 59 | foreach(clause; clauseList) { 60 | if (Engine.isHalt) break; 61 | executeClause(clause); 62 | } 63 | }); 64 | } 65 | 66 | void clear() { 67 | _lexer.clear; 68 | _parser.clear; 69 | _clauseBuilder.clear; 70 | _storage = []; 71 | } 72 | 73 | private: 74 | Maybe!(Clause[]) toClauseList(dstring src) { 75 | auto convert(S, T)(Converter!(S, T) converter) { 76 | return (S src) { 77 | converter.run(src); 78 | if (converter.hasError) { 79 | Messenger.add(converter.errorMessage); 80 | return None!T; 81 | } 82 | return converter.get.Just; 83 | }; 84 | } 85 | return Just(src).bind!( 86 | a => convert(_lexer)(a) 87 | ).bind!( 88 | a => convert(_parser)(a) 89 | ).bind!( 90 | a => convert(_clauseBuilder)(a) 91 | ); 92 | } 93 | 94 | void executeClause(Clause clause) { 95 | if (Engine.verboseMode) { 96 | Messenger.writeln(VerboseMessage(format!"execute: %s"(clause))); 97 | } 98 | clause.castSwitch!( 99 | (Fact fact) => executeFact(fact), 100 | (Rule rule) => executeRule(rule), 101 | (Query query) => executeQuery(query) 102 | ); 103 | } 104 | 105 | void executeFact(Fact fact) { 106 | _storage ~= fact; 107 | } 108 | 109 | void executeRule(Rule rule) { 110 | _storage ~= rule; 111 | } 112 | 113 | void executeQuery(Query query) { 114 | if (BuiltInCommand.traverse(query.first)) { 115 | // when matching a built-in pattern 116 | return; 117 | } 118 | 119 | Variant first, second; 120 | UnificationUF unionFind = buildUnionFind(query, first, second); 121 | 122 | auto generator = new Generator!UnificateYield({ 123 | unificate(first, unionFind); 124 | }, 1<<20); 125 | 126 | string[] rec(Variant v, UnificationUF uf, ref bool[string] exists) { 127 | if (v.isVariable && !v.term.token.isUnderscore) { 128 | Variant root = uf.root(v); 129 | string lexeme = v.term.to!string; 130 | if (lexeme in exists) { 131 | return []; 132 | } else { 133 | exists[lexeme] = true; 134 | } 135 | return [ 136 | lexeme, 137 | "=", 138 | { 139 | Term f(Variant var) { 140 | if (var.isVariable) { 141 | auto x = uf.root(var); 142 | return x == var ? x.term : x.pipe!f; 143 | } else { 144 | return new Term( 145 | var.term.token, 146 | var.children.map!f.array 147 | ); 148 | } 149 | } 150 | 151 | return root.pipe!f.to!string; 152 | }() 153 | ].join(" ").only.array; 154 | } else { 155 | return v.children.map!(u => rec(u, uf, exists)).join.array; 156 | } 157 | } 158 | 159 | void eatYields() { 160 | while(!generator.empty) { 161 | auto result = generator.front; 162 | if (result.isLeft) { 163 | generator.popFront; 164 | } else { 165 | break; 166 | } 167 | } 168 | } 169 | 170 | eatYields(); 171 | 172 | if (generator.empty) { 173 | Messenger.showAll(); 174 | Messenger.writeln(DefaultMessage("false.")); 175 | } else { 176 | while(true) { 177 | assert(!generator.empty); 178 | auto result = generator.front; 179 | generator.popFront; 180 | assert(result.isRight); 181 | 182 | auto uf = result.right; 183 | 184 | Messenger.showAll(); 185 | bool[string] exists; 186 | string answer = rec(first, uf, exists).join(", "); 187 | if (answer.empty) answer = "true"; 188 | 189 | if (generator.empty) { 190 | Messenger.writeln(DefaultMessage(answer ~ ".")); 191 | break; 192 | } 193 | 194 | auto line = Linenoise.nextLine(answer ~ "; "); 195 | if (line.isJust) { 196 | } else { 197 | Messenger.writeln(InfoMessage("% Execution Aborted")); 198 | break; 199 | } 200 | 201 | eatYields(); 202 | if (generator.empty) { 203 | Messenger.writeln(ErrorMessage("false.")); 204 | break; 205 | } 206 | } 207 | } 208 | } 209 | 210 | // fiber function 211 | UnificateResult unificate(Variant variant, UnificationUF unionFind) { 212 | yieldLeft(); 213 | 214 | variant = unionFind.root(variant); 215 | const Term term = variant.term; 216 | UnificateResult unificateResult = UnificateResult(false, false); 217 | 218 | if (term.token == Operator.comma) { 219 | // conjunction 220 | auto gen = new Generator!UnificateYield({ 221 | auto r = unificate(variant.children.front, unionFind); 222 | unificateResult.isCutted |= r.isCutted; 223 | }, 1<<20); 224 | foreach(result; gen) { 225 | if (result.isLeft) continue; 226 | auto uf = result.right; 227 | auto r = unificate(variant.children.back, uf); 228 | unificateResult.isCutted |= r.isCutted; 229 | unificateResult.found |= r.found; 230 | } 231 | } else if (term.token == Operator.semicolon) { 232 | // disjunction 233 | if (!unificateResult.isCutted) { 234 | auto r = unificate(variant.children.front, unionFind); 235 | unificateResult.isCutted |= r.isCutted; 236 | unificateResult.found |= r.found; 237 | } 238 | if (!unificateResult.isCutted) { 239 | auto r = unificate(variant.children.back, unionFind); 240 | unificateResult.isCutted |= r.isCutted; 241 | unificateResult.found |= r.found; 242 | } 243 | } else if (term.token == Operator.equal) { 244 | // unification 245 | UnificationUF newUnionFind = unionFind.clone; 246 | if (match(variant.children.front, variant.children.back, newUnionFind)) { 247 | yieldRight(newUnionFind); 248 | unificateResult.found |= true; 249 | } 250 | } else if (term.token == Operator.equalEqual) { 251 | // equality comparison 252 | if (unionFind.same(variant.children.front, variant.children.back)) { 253 | yieldRight(unionFind); 254 | unificateResult.found |= true; 255 | } 256 | } else if (term.token == Operator.eval) { 257 | // arithmetic evaluation 258 | auto result = Evaluator.calc(variant.children.back, unionFind); 259 | if (result.isLeft) { 260 | Messenger.add(result.left); 261 | } else { 262 | Number y = result.right; 263 | Variant xVar = unionFind.root(variant.children.front); 264 | xVar.term.token.castSwitch!( 265 | (Variable x) { 266 | UnificationUF newUnionFind = unionFind.clone; 267 | Variant yVar = new Variant(-1, new Term(y, []), []); 268 | newUnionFind.add(yVar); 269 | newUnionFind.unite(xVar, yVar); 270 | yieldRight(newUnionFind); 271 | unificateResult.found |= true; 272 | }, 273 | (Number x) { 274 | if (x == y) { 275 | yieldRight(unionFind); 276 | unificateResult.found |= true; 277 | } 278 | }, 279 | (Object _) { 280 | } 281 | ); 282 | } 283 | } else if (term.token.instanceOf!ComparisonOperator) { 284 | // arithmetic comparison 285 | auto op = cast(ComparisonOperator) term.token; 286 | auto result = Evaluator.calc(variant.children.front, unionFind).bind!( 287 | x => Evaluator.calc(variant.children.back, unionFind).fmap!( 288 | y => op.calc(x, y) 289 | ) 290 | ); 291 | if (result.isLeft) { 292 | Messenger.add(result.left); 293 | } else { 294 | if (result.right) { 295 | yieldRight(unionFind); 296 | unificateResult.found |= true; 297 | } 298 | } 299 | } else { 300 | auto predResult = BuiltInPredicate.unificateTraverse(variant, unionFind, &unificate); 301 | if (predResult.found) { 302 | unificateResult = predResult; 303 | } else { 304 | foreach(clause; _storage) { 305 | if (unificateResult.isCutted) break; 306 | Variant first, second; 307 | UnificationUF newUnionFind = unionFind ~ buildUnionFind(clause, first, second); 308 | if (match(variant, first, newUnionFind)) { 309 | clause.castSwitch!( 310 | (Fact fact) { 311 | yieldRight(newUnionFind); 312 | unificateResult.found |= true; 313 | }, 314 | (Rule rule) { 315 | auto r = unificate(second, newUnionFind); 316 | unificateResult.isCutted |= r.isCutted; 317 | unificateResult.found |= r.found; 318 | } 319 | ); 320 | } 321 | } 322 | 323 | } 324 | } 325 | 326 | return unificateResult; 327 | } 328 | 329 | void yieldLeft() { 330 | static CallUnificateNotifier notifier; 331 | if (!notifier) { 332 | notifier = new CallUnificateNotifier; 333 | } 334 | Left!(CallUnificateNotifier, UnificationUF)(notifier).yield; 335 | } 336 | 337 | void yieldRight(UnificationUF uf) { 338 | Right!(CallUnificateNotifier, UnificationUF)(uf).yield; 339 | } 340 | 341 | bool match(Variant left, Variant right, UnificationUF unionFind) { 342 | if (!left.isVariable && !right.isVariable) { 343 | return left.term.token == right.term.token && left.children.length==right.children.length && zip(left.children, right.children).all!(a => match(a[0], a[1], unionFind)); 344 | } else { 345 | Variant l = unionFind.root(left); 346 | Variant r = unionFind.root(right); 347 | if (unionFind.same(l, r)) { 348 | return true; 349 | } else if (!l.isVariable && !r.isVariable) { 350 | return match(l, r, unionFind); 351 | } else { 352 | unionFind.unite(l, r); 353 | return true; 354 | } 355 | } 356 | } 357 | 358 | UnificationUF buildUnionFind(Clause clause, ref Variant first, ref Variant second) { 359 | static long idGen = 0; 360 | static long idGen_underscore = 0; 361 | const long id = ++idGen; 362 | 363 | UnificationUF uf = new UnificationUF; 364 | 365 | Variant rec(Term term) { 366 | Variant v = new Variant( 367 | term.token.isUnderscore ? ++idGen_underscore : 368 | term.isVariable ? id 369 | : -1, 370 | term, 371 | term.children.map!(c => rec(c)).array 372 | ); 373 | uf.add(v); 374 | return v; 375 | } 376 | 377 | clause.castSwitch!( 378 | (Fact fact) { 379 | first = rec(fact.first); 380 | }, 381 | (Rule rule) { 382 | first = rec(rule.first); 383 | second = rec(rule.second); 384 | }, 385 | (Query query) { 386 | first = rec(query.first); 387 | } 388 | ); 389 | 390 | return uf; 391 | } 392 | 393 | invariant { 394 | assert(_storage.all!(clause => clause.instanceOf!Fact || clause.instanceOf!Rule)); 395 | } 396 | 397 | } 398 | -------------------------------------------------------------------------------- /src/dprolog/engine/Messenger.d: -------------------------------------------------------------------------------- 1 | module dprolog.engine.Messenger; 2 | 3 | import dprolog.util.Message; 4 | import dprolog.util.Singleton; 5 | 6 | import std.stdio; 7 | import std.container : DList; 8 | 9 | alias Messenger = Singleton!Messenger_; 10 | 11 | private class Messenger_ { 12 | 13 | private: 14 | DList!Message _messageList; 15 | 16 | public: 17 | this() { 18 | clear(); 19 | } 20 | 21 | @property bool empty() { 22 | return _messageList.empty; 23 | } 24 | 25 | void write(Message msg) { 26 | msg.write; 27 | stdout.flush; 28 | } 29 | 30 | void writeln(Message msg) { 31 | msg.writeln; 32 | stdout.flush; 33 | } 34 | 35 | void add(Message msg) { 36 | _messageList.insertBack(msg); 37 | } 38 | 39 | void show() in(!empty) do { 40 | _messageList.front.writeln; 41 | _messageList.removeFront; 42 | stdout.flush; 43 | } 44 | 45 | void showAll() { 46 | while(!empty) { 47 | show(); 48 | } 49 | } 50 | 51 | void clear() { 52 | _messageList.clear(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/dprolog/engine/Reader.d: -------------------------------------------------------------------------------- 1 | module dprolog.engine.Reader; 2 | 3 | import dprolog.engine.Messenger; 4 | import dprolog.engine.Executor; 5 | import dprolog.util.Message; 6 | import dprolog.util.Singleton; 7 | 8 | import std.format; 9 | import std.conv; 10 | import std.file; 11 | 12 | alias Reader = Singleton!Reader_; 13 | 14 | private class Reader_ { 15 | void read(dstring filePath) { 16 | if (filePath.exists) { 17 | Executor.execute(filePath.readText.to!dstring); 18 | } else { 19 | Messenger.add(ErrorMessage(format!"Error: file '%s' cannot be read"(filePath))); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/dprolog/engine/UnificationUF.d: -------------------------------------------------------------------------------- 1 | module dprolog.engine.UnificationUF; 2 | 3 | import dprolog.data.Variant; 4 | import dprolog.util.UnionFind; 5 | 6 | alias UnificationUF = UnionFind!( 7 | Variant, 8 | (Variant a, Variant b) => !a.isVariable ? -1 : !b.isVariable ? 1 : 0 9 | ); 10 | -------------------------------------------------------------------------------- /src/dprolog/engine/builtIn/BuiltIn.d: -------------------------------------------------------------------------------- 1 | module dprolog.engine.builtIn.BuiltIn; 2 | 3 | import dprolog.data.Term; 4 | import dprolog.data.Clause; 5 | import dprolog.converter.Converter; 6 | import dprolog.converter.Lexer; 7 | import dprolog.converter.Parser; 8 | import dprolog.converter.ClauseBuilder; 9 | import dprolog.util.functions; 10 | 11 | import std.range; 12 | import std.functional; 13 | 14 | abstract class BuiltIn { 15 | 16 | private: 17 | Lexer _lexer; 18 | Parser _parser; 19 | ClauseBuilder _clauseBuilder; 20 | 21 | protected: 22 | this() { 23 | _lexer = new Lexer; 24 | _parser = new Parser; 25 | _clauseBuilder = new ClauseBuilder; 26 | } 27 | 28 | Term toTerm(dstring src) { 29 | auto convert(S, T)(Converter!(S, T) converter) { 30 | return (S src) { 31 | converter.run(src); 32 | assert(!converter.hasError); 33 | return converter.get; 34 | }; 35 | } 36 | Clause[] clauseList = (src ~ ".").pipe!( 37 | a => convert(_lexer)(a), 38 | a => convert(_parser)(a), 39 | a => convert(_clauseBuilder)(a) 40 | ); 41 | assert(clauseList.length == 1 && clauseList.front.instanceOf!Fact); 42 | return (cast(Fact) clauseList.front).first; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/dprolog/engine/builtIn/BuiltInCommand.d: -------------------------------------------------------------------------------- 1 | module dprolog.engine.builtIn.BuiltInCommand; 2 | 3 | import dprolog.util.Message; 4 | import dprolog.util.Either; 5 | import dprolog.util.Singleton; 6 | import dprolog.data.Command; 7 | import dprolog.data.Term; 8 | import dprolog.sl.SL; 9 | import dprolog.engine.Engine; 10 | import dprolog.engine.Messenger; 11 | import dprolog.engine.Reader; 12 | import dprolog.engine.Consulter; 13 | import dprolog.engine.builtIn.BuiltIn; 14 | import dprolog.core.Linenoise; 15 | import dprolog.core.Shell; 16 | 17 | import std.conv; 18 | import std.file : getcwd; 19 | import std.range; 20 | import std.string; 21 | 22 | alias BuiltInCommand = Singleton!BuiltInCommand_; 23 | 24 | private class BuiltInCommand_ : BuiltIn { 25 | 26 | private: 27 | Command[] _commands; 28 | 29 | public: 30 | this() { 31 | super(); 32 | setCommands(); 33 | } 34 | 35 | bool traverse(Term term) { 36 | foreach(command; _commands) { 37 | if (command.isMatch(term)) { 38 | command.execute(term); 39 | return true; 40 | } 41 | } 42 | return false; 43 | } 44 | 45 | private: 46 | void setCommands() { 47 | // halt 48 | auto halt = buildCommand( 49 | "halt", 50 | term => Engine.halt() 51 | ); 52 | 53 | // clear screen 54 | auto clearScreen = buildCommand( 55 | "clear", 56 | term => Linenoise.clearScreen() 57 | ); 58 | 59 | // add rules 60 | auto addRules = buildCommand( 61 | "[user]", 62 | (term) { 63 | Consulter.consult(); 64 | writelnTrue(); 65 | } 66 | ); 67 | 68 | // read file 69 | auto readFile = buildCommand( 70 | "[FilePath]", 71 | (term) { 72 | dstring filePath = term.children.front.token.lexeme; 73 | if (filePath.front == '\'') { 74 | filePath = filePath[1..$-1]; 75 | } 76 | Reader.read(filePath); 77 | writelnTrue(); 78 | }, 79 | [ 80 | "FilePath": (Term term) => term.isAtom && term.children.empty 81 | ] 82 | ); 83 | 84 | // 42 85 | auto answerToEverything = buildCommand( 86 | "X", 87 | term => Messenger.writeln(DefaultMessage("42.")), 88 | [ 89 | "X": (Term term) => term.isVariable 90 | ] 91 | ); 92 | 93 | // pwd 94 | auto pwdCommand = buildCommand( 95 | "pwd", 96 | (term) { 97 | Messenger.writeln(InfoMessage("% " ~ getcwd())); 98 | writelnTrue(); 99 | } 100 | ); 101 | 102 | // ls 103 | auto lsCommand = buildCommand( 104 | "ls", 105 | (term) { 106 | Shell.executeLs.apply!( 107 | msg => Messenger.writeln(msg), 108 | (lines) { 109 | foreach(line; lines) { 110 | Message msg = InfoMessage("% " ~ line); 111 | Messenger.writeln(msg); 112 | } 113 | } 114 | ); 115 | writelnTrue(); 116 | } 117 | ); 118 | 119 | // ls with a path 120 | auto lsCommandWithPath = buildCommand( 121 | "ls(Path)", 122 | (term) { 123 | dstring path = term.children.front.token.lexeme; 124 | if (path.front == '\'') { 125 | path = path[1..$-1]; 126 | } 127 | 128 | Shell.executeLsWithPath(path.to!string).apply!( 129 | msg => Messenger.writeln(msg), 130 | (lines) { 131 | foreach(line; lines) { 132 | Message msg = InfoMessage("% " ~ line); 133 | Messenger.writeln(msg); 134 | } 135 | } 136 | ); 137 | writelnTrue(); 138 | }, 139 | [ 140 | "Path": (Term term) => term.isAtom && term.children.empty 141 | ] 142 | ); 143 | 144 | // cd with a path 145 | auto cdCommandWithPath = buildCommand( 146 | "cd(Path)", 147 | (term) { 148 | dstring path = term.children.front.token.lexeme; 149 | if (path.front == '\'') { 150 | path = path[1..$-1]; 151 | } 152 | 153 | Shell.executeCdWithPath(path.to!string).apply!( 154 | msg => Messenger.writeln(msg), 155 | (lines) { 156 | foreach(line; lines) { 157 | Message msg = InfoMessage("% " ~ line); 158 | Messenger.writeln(msg); 159 | } 160 | } 161 | ); 162 | writelnTrue(); 163 | }, 164 | [ 165 | "Path": (Term term) => term.isAtom && term.children.empty 166 | ] 167 | ); 168 | 169 | // sl 170 | auto slCommand = buildCommand( 171 | "sl", 172 | term => SL.run() 173 | ); 174 | 175 | _commands = [ 176 | halt, 177 | addRules, 178 | readFile, 179 | answerToEverything, 180 | clearScreen, 181 | pwdCommand, 182 | lsCommand, 183 | lsCommandWithPath, 184 | cdCommandWithPath, 185 | slCommand, 186 | ]; 187 | } 188 | 189 | Command buildCommand(dstring src, void delegate(Term) executeFun, bool delegate(Term)[dstring] validators = null) { 190 | Term targetTerm = toTerm(src); 191 | 192 | return new class() Command { 193 | 194 | override bool isMatch(Term term) { 195 | bool rec(Term src, Term dst) { 196 | if (dst.isVariable) { 197 | if (!validators[dst.token.lexeme](src)) return false; 198 | } else { 199 | if (src.token != dst.token) return false; 200 | if (src.children.length != dst.children.length) return false; 201 | foreach(l, r; zip(src.children, dst.children)) { 202 | if (!rec(l, r)) return false; 203 | } 204 | } 205 | return true; 206 | } 207 | return rec(term, targetTerm); 208 | } 209 | 210 | override void execute(Term term) { 211 | executeFun(term); 212 | } 213 | 214 | }; 215 | } 216 | 217 | void writelnTrue() { 218 | Messenger.writeln(DefaultMessage("true.")); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/dprolog/engine/builtIn/BuiltInPredicate.d: -------------------------------------------------------------------------------- 1 | module dprolog.engine.builtIn.BuiltInPredicate; 2 | 3 | import dprolog.data.Term; 4 | import dprolog.data.Variant; 5 | import dprolog.data.Predicate; 6 | import dprolog.engine.Executor; 7 | import dprolog.engine.UnificationUF; 8 | import dprolog.engine.builtIn.BuiltIn; 9 | import dprolog.util.Either; 10 | import dprolog.util.Singleton; 11 | 12 | import std.range; 13 | import std.concurrency : Generator, yield; 14 | 15 | alias BuiltInPredicate = Singleton!BuiltInPredicate_; 16 | 17 | private class BuiltInPredicate_ : BuiltIn { 18 | alias UnificateRecFun = Predicate.UnificateRecFun; 19 | 20 | private: 21 | Predicate[] _predicates; 22 | 23 | public: 24 | this() { 25 | super(); 26 | setPredicates(); 27 | } 28 | 29 | UnificateResult unificateTraverse(Variant variant, UnificationUF unionFind, UnificateRecFun unificateRecFun) { 30 | const Term term = variant.term; 31 | foreach(predicate; _predicates) { 32 | if (predicate.isMatch(term)) { 33 | auto result = predicate.unificate(variant, unionFind, unificateRecFun); 34 | if (result.found) return result; 35 | } 36 | } 37 | return UnificateResult(false, false); 38 | } 39 | 40 | private: 41 | void setPredicates() { 42 | // cut 43 | auto cutPred = buildPredicate( 44 | "!", 0, 45 | delegate UnificateResult(Variant variant, UnificationUF unionFind, UnificateRecFun unificateRecFun) { 46 | Right!(CallUnificateNotifier, UnificationUF)(unionFind).yield; 47 | return UnificateResult(true, true); 48 | } 49 | ); 50 | 51 | // true 52 | auto truePred = buildPredicate( 53 | "true", 0, 54 | delegate UnificateResult(Variant variant, UnificationUF unionFind, UnificateRecFun unificateRecFun) { 55 | Right!(CallUnificateNotifier, UnificationUF)(unionFind).yield; 56 | return UnificateResult(true, false); 57 | } 58 | ); 59 | 60 | // false 61 | auto falsePred = buildPredicate( 62 | "false", 0, 63 | delegate UnificateResult(Variant variant, UnificationUF unionFind, UnificateRecFun unificateRecFun) { 64 | return UnificateResult(false, false); 65 | } 66 | ); 67 | 68 | // fail 69 | auto failPred = buildPredicate( 70 | "fail", 0, 71 | delegate UnificateResult(Variant variant, UnificationUF unionFind, UnificateRecFun unificateRecFun) { 72 | return UnificateResult(false, false); 73 | } 74 | ); 75 | 76 | // repeat 77 | auto repeatPred = buildPredicate( 78 | "repeat", 0, 79 | delegate UnificateResult(Variant variant, UnificationUF unionFind, UnificateRecFun unificateRecFun) { 80 | while(true) { 81 | Right!(CallUnificateNotifier, UnificationUF)(unionFind).yield; 82 | } 83 | } 84 | ); 85 | 86 | // not 87 | auto notPred = buildPredicate( 88 | "not", 1, 89 | delegate UnificateResult(Variant variant, UnificationUF unionFind, UnificateRecFun unificateRecFun) { 90 | assert(variant.children.length == 1); 91 | auto child = variant.children.front; 92 | 93 | auto g = new Generator!UnificateYield({ 94 | unificateRecFun(child, unionFind); 95 | }, 1<<20); 96 | if(g.empty) { 97 | unionFind.yield; 98 | return UnificateResult(true, false); 99 | } else { 100 | return UnificateResult(false, false); 101 | } 102 | } 103 | ); 104 | 105 | _predicates = [ 106 | cutPred, 107 | truePred, 108 | falsePred, 109 | failPred, 110 | repeatPred, 111 | notPred, 112 | ]; 113 | } 114 | 115 | Predicate buildPredicate( 116 | dstring lexeme, 117 | size_t arity, 118 | UnificateResult delegate(Variant, UnificationUF, UnificateRecFun) unificateFun 119 | ) { 120 | return new class() Predicate { 121 | 122 | override bool isMatch(const Term term) { 123 | return term.isAtom && term.token.lexeme == lexeme && term.children.length == arity; 124 | } 125 | 126 | override UnificateResult unificate(Variant variant, UnificationUF unionFind, UnificateRecFun unificateRecFun) { 127 | return unificateFun(variant, unionFind, unificateRecFun); 128 | } 129 | 130 | }; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/dprolog/sl/SL.d: -------------------------------------------------------------------------------- 1 | module dprolog.sl.SL; 2 | 3 | import dprolog.core.tty : getColumns, getRows; 4 | import dprolog.core.Linenoise; 5 | import dprolog.engine.Messenger; 6 | import dprolog.util.Message; 7 | import dprolog.util.Either; 8 | import dprolog.util.Singleton; 9 | 10 | import std.conv; 11 | import std.algorithm; 12 | import std.range; 13 | import std.array; 14 | import std.typecons; 15 | import std.concurrency : Generator, yield; 16 | import core.thread; 17 | 18 | alias SL = Singleton!SL_; 19 | 20 | private class SL_ { 21 | void run() { 22 | getSLGenerator().apply!( 23 | msg => Messenger.writeln(msg), 24 | (gen) { 25 | while(!gen.empty) { 26 | auto sl = gen.front; 27 | gen.popFront; 28 | Linenoise.clearScreen; 29 | foreach(line; sl) { 30 | Messenger.add(InfoMessage(line)); 31 | } 32 | Messenger.showAll(); 33 | Thread.sleep(20.msecs); 34 | } 35 | Linenoise.clearScreen; 36 | } 37 | ); 38 | } 39 | 40 | private: 41 | Either!(Message, Generator!(string[])) getSLGenerator() { 42 | return getColumns.bind!( 43 | columns => getRows.fmap!( 44 | lines => tuple!("columns", "lines")(columns, lines) 45 | ) 46 | ).fmap!( 47 | size => new Generator!(string[])({ 48 | int columns = size.columns; 49 | int lines = size.lines; 50 | int slWidth = getSLWidth; 51 | int slHeight = getSLHeight; 52 | string[] spaceLines = "".repeat(max(0, lines - slHeight) / 2).array; 53 | foreach(i; 0..columns+slWidth) { 54 | int diff = columns - i - 1; 55 | string padding = ' '.repeat(max(0, diff)).array; 56 | auto sl = makeSL(i).map!( 57 | str => padding ~ str 58 | ).map!( 59 | str => str[clamp(-diff, 0, $)..$] 60 | ).map!( 61 | str => str[0..min(columns, $)] 62 | ).array; 63 | yield(spaceLines ~ sl); 64 | } 65 | }) 66 | ); 67 | } 68 | 69 | int getSLWidth() { 70 | auto sl = makeSL(0); 71 | return sl.map!"a.length".reduce!max.to!int; 72 | } 73 | 74 | int getSLHeight() { 75 | auto sl = makeSL(0); 76 | return sl.length.to!int; 77 | } 78 | 79 | string[] makeSL(int frame) in(frame >= 0) do { 80 | string[] head = headGen[frame%$]; 81 | string[] tail = tailGen[frame%$]; 82 | string[] smoke = smokeGen[frame/2%$]; 83 | return smoke ~ zip(head, tail).map!"a[0] ~ a[1]".array; 84 | } 85 | 86 | enum string[][] headGen = [ 87 | [ 88 | ` ==== ________ ___________ `, 89 | ` _D _| |_______/ \__I_I_____===__|_________| `, 90 | ` |(_)--- | H\________/ | | =|___ ___| `, 91 | ` / | | H | | | | ||_| |_|| `, 92 | ` | | | H |__--------------------| [___] | `, 93 | ` | ________|___H__/__|_____/[][]~\_______| | `, 94 | ` |/ | |-----------I_____I [][] [] D |=======|__ `, 95 | `__/ =| o |=-~~\ /~~\ /~~\ /~~\ ____Y___________|__ `, 96 | ` |/-=|___|= || || || |_____/~\___/ `, 97 | ` \_/ \O=====O=====O=====O_/ \_/ `, 98 | ], [ 99 | ` ==== ________ ___________ `, 100 | ` _D _| |_______/ \__I_I_____===__|_________| `, 101 | ` |(_)--- | H\________/ | | =|___ ___| `, 102 | ` / | | H | | | | ||_| |_|| `, 103 | ` | | | H |__--------------------| [___] | `, 104 | ` | ________|___H__/__|_____/[][]~\_______| | `, 105 | ` |/ | |-----------I_____I [][] [] D |=======|__ `, 106 | `__/ =| o |=-~~\ /~~\ /~~\ /~~\ ____Y___________|__ `, 107 | ` |/-=|___|=O=====O=====O=====O |_____/~\___/ `, 108 | ` \_/ \__/ \__/ \__/ \__/ \_/ `, 109 | ], [ 110 | ` ==== ________ ___________ `, 111 | ` _D _| |_______/ \__I_I_____===__|_________| `, 112 | ` |(_)--- | H\________/ | | =|___ ___| `, 113 | ` / | | H | | | | ||_| |_|| `, 114 | ` | | | H |__--------------------| [___] | `, 115 | ` | ________|___H__/__|_____/[][]~\_______| | `, 116 | ` |/ | |-----------I_____I [][] [] D |=======|__ `, 117 | `__/ =| o |=-O=====O=====O=====O \ ____Y___________|__ `, 118 | ` |/-=|___|= || || || |_____/~\___/ `, 119 | ` \_/ \__/ \__/ \__/ \__/ \_/ `, 120 | ], [ 121 | ` ==== ________ ___________ `, 122 | ` _D _| |_______/ \__I_I_____===__|_________| `, 123 | ` |(_)--- | H\________/ | | =|___ ___| `, 124 | ` / | | H | | | | ||_| |_|| `, 125 | ` | | | H |__--------------------| [___] | `, 126 | ` | ________|___H__/__|_____/[][]~\_______| | `, 127 | ` |/ | |-----------I_____I [][] [] D |=======|__ `, 128 | `__/ =| o |=-~O=====O=====O=====O\ ____Y___________|__ `, 129 | ` |/-=|___|= || || || |_____/~\___/ `, 130 | ` \_/ \__/ \__/ \__/ \__/ \_/ `, 131 | ], [ 132 | ` ==== ________ ___________ `, 133 | ` _D _| |_______/ \__I_I_____===__|_________| `, 134 | ` |(_)--- | H\________/ | | =|___ ___| `, 135 | ` / | | H | | | | ||_| |_|| `, 136 | ` | | | H |__--------------------| [___] | `, 137 | ` | ________|___H__/__|_____/[][]~\_______| | `, 138 | ` |/ | |-----------I_____I [][] [] D |=======|__ `, 139 | `__/ =| o |=-~~\ /~~\ /~~\ /~~\ ____Y___________|__ `, 140 | ` |/-=|___|= O=====O=====O=====O|_____/~\___/ `, 141 | ` \_/ \__/ \__/ \__/ \__/ \_/ `, 142 | ], [ 143 | ` ==== ________ ___________ `, 144 | ` _D _| |_______/ \__I_I_____===__|_________| `, 145 | ` |(_)--- | H\________/ | | =|___ ___| `, 146 | ` / | | H | | | | ||_| |_|| `, 147 | ` | | | H |__--------------------| [___] | `, 148 | ` | ________|___H__/__|_____/[][]~\_______| | `, 149 | ` |/ | |-----------I_____I [][] [] D |=======|__ `, 150 | `__/ =| o |=-~~\ /~~\ /~~\ /~~\ ____Y___________|__ `, 151 | ` |/-=|___|= || || || |_____/~\___/ `, 152 | ` \_/ \_O=====O=====O=====O/ \_/ `, 153 | ], 154 | ]; 155 | 156 | enum string[][] tailGen = [ 157 | [ 158 | ` `, 159 | ` `, 160 | ` _________________ `, 161 | ` _| \_____A `, 162 | ` =| | `, 163 | ` -| | `, 164 | `__|________________________|_ `, 165 | `|__________________________|_ `, 166 | ` |_D__D__D_| |_D__D__D_| `, 167 | ` \_/ \_/ \_/ \_/ `, 168 | ], 169 | ]; 170 | 171 | enum string[][] smokeGen = [ 172 | [ 173 | ` ( ) (@@) ( ) (@) () @@ O @ O @ O`, 174 | ` (@@@)`, 175 | ` ( )`, 176 | ` (@@@@)`, 177 | ` ( )`, 178 | ], [ 179 | ` ( ) (@@) ( ) (@) () @@ O @ O @ O`, 180 | ` (@@@)`, 181 | ` ( )`, 182 | ` (@@@@)`, 183 | ` ( )`, 184 | ], [ 185 | ` ( ) (@@) ( ) (@) () @@ O @ O @`, 186 | ` (@@@)`, 187 | ` ( )`, 188 | ` (@@@@)`, 189 | ` ( )`, 190 | ], [ 191 | ` (@@) ( ) (@) ( ) @@ () @ O @ O @`, 192 | ` ( )`, 193 | ` (@@@@)`, 194 | ` ( )`, 195 | ` (@@@)`, 196 | ], [ 197 | ` (@@) ( ) (@) ( ) @@ () @ O @ O @`, 198 | ` ( )`, 199 | ` (@@@@)`, 200 | ` ( )`, 201 | ` (@@@)`, 202 | ], [ 203 | ` (@@) ( ) (@) ( ) @@ () @ O @ O`, 204 | ` ( )`, 205 | ` (@@@@)`, 206 | ` ( )`, 207 | ` (@@@)`, 208 | ], 209 | ]; 210 | } 211 | -------------------------------------------------------------------------------- /src/dprolog/util/Either.d: -------------------------------------------------------------------------------- 1 | module dprolog.util.Either; 2 | 3 | import std.stdio; 4 | import std.format; 5 | import std.functional; 6 | import std.traits; 7 | 8 | struct Either(L, R) { 9 | 10 | private: 11 | L _left = L.init; 12 | R _right = R.init; 13 | 14 | bool _isLeft; 15 | 16 | package: 17 | this(L left) { 18 | _left = left; 19 | _isLeft = true; 20 | } 21 | 22 | this(R right) { 23 | _right = right; 24 | _isLeft = false; 25 | } 26 | 27 | public: 28 | @property bool isLeft() { 29 | return _isLeft; 30 | } 31 | 32 | @property bool isRight() { 33 | return !_isLeft; 34 | } 35 | 36 | @property L left() in(isLeft) do { 37 | return _left; 38 | } 39 | 40 | @property R right() in(isRight) do { 41 | return _right; 42 | } 43 | 44 | Either!(L, R) opAssign(L left) in(!isNull(left)) do { 45 | _left = left; 46 | _isLeft = true; 47 | return this; 48 | } 49 | 50 | Either!(L, R) opAssign(R right) in(!isNull(right)) { 51 | _right = right; 52 | _isLeft = false; 53 | return this; 54 | } 55 | 56 | bool opEquals(L2, R2)(Either!(L2, R2) that) { 57 | alias L1 = L, R1 = R; 58 | if (this.isLeft != that.isLeft) return false; 59 | if (this.isLeft) { 60 | return this.left == that.left; 61 | } else { 62 | return this.right == that.right; 63 | } 64 | } 65 | 66 | bool opEquals(T)(T that) 67 | if (!isInstanceOf!(TemplateOf!Either, T)) { 68 | static if (is(T : L)) { 69 | return isLeft && left == that; 70 | } else if (is(T : R)) { 71 | return isRight && right == that; 72 | } else { 73 | return false; 74 | } 75 | } 76 | 77 | string toString() { 78 | if (isLeft) { 79 | return format!"Left!(%s)(%s)"(typeid(L), left); 80 | } else { 81 | return format!"Right!(%s)(%s)"(typeid(R), right); 82 | } 83 | } 84 | 85 | } 86 | 87 | private struct Dummy { 88 | bool opEquals(O)(O o) { 89 | return false; 90 | } 91 | } 92 | 93 | private bool isNull(T)(T value) { 94 | static if (is(typeof(value is null))) { 95 | return ( 96 | isPointer!T || 97 | is(T == class) || 98 | is(T == interface) || 99 | is(T == function) || 100 | is(T == delegate) 101 | ) && value is null; 102 | } else { 103 | return false; 104 | } 105 | } 106 | 107 | Either!(L, Dummy) Left(L)(L left) in(!isNull(left)) do { 108 | return Either!(L, Dummy)(left); 109 | } 110 | 111 | Either!(L, R) Left(L, R)(L left) in(!isNull(left)) do { 112 | return Either!(L, R)(left); 113 | } 114 | 115 | Either!(Dummy, R) Right(R)(R right) in(!isNull(right)) do { 116 | return Either!(Dummy, R)(right); 117 | } 118 | 119 | Either!(L, R) Right(L, R)(R right) in(!isNull(right)) do { 120 | return Either!(L, R)(right); 121 | } 122 | 123 | 124 | // fmap :: Either!(L, R1) -> (R1 -> R2) -> Either!(L, R2) 125 | template fmap(alias fun, L, R1) { 126 | static if(!is(R1 == Dummy)) { 127 | static assert(is(typeof(unaryFun!fun(R1.init)))); 128 | alias R2 = typeof(unaryFun!fun(R1.init)); 129 | } else { 130 | alias R2 = R1; 131 | } 132 | Either!(L, R2) fmap(Either!(L, R1) e) { 133 | if (e.isLeft) { 134 | return Left!(L, R2)(e.left); 135 | } else { 136 | static if (!is(R1 == Dummy)) { 137 | return Right!(L, R2)(unaryFun!fun(e.right)); 138 | } else { 139 | assert(false); 140 | } 141 | } 142 | } 143 | } 144 | 145 | // bind :: Either!(L, R1) -> (R1 -> Either!(L, R2)) -> Either!(L, R2) 146 | template bind(alias fun, L, R1) { 147 | static if (!is(R1 == Dummy)) { 148 | static assert(isInstanceOf!(Either, typeof(unaryFun!fun(R1.init)))); 149 | alias E2 = typeof(unaryFun!fun(R1.init)); 150 | alias L2 = TemplateArgsOf!E2[0]; 151 | alias R2 = TemplateArgsOf!E2[1]; 152 | static assert(is(L == L2) || is(L == Dummy)); 153 | } else { 154 | alias R2 = R1; 155 | alias L2 = L; 156 | static assert(!is(L == Dummy)); 157 | } 158 | Either!(L2, R2) bind(Either!(L, R1) e) { 159 | if (e.isLeft) { 160 | static if (!is(L == Dummy)) { 161 | return Left!(L, R2)(e.left); 162 | } else { 163 | assert(false); 164 | } 165 | } else { 166 | static if (!is(R1 == Dummy)) { 167 | return unaryFun!fun(e.right); 168 | } else { 169 | assert(false); 170 | } 171 | } 172 | } 173 | } 174 | 175 | template apply(alias leftFun, alias rightFun, L, R) { 176 | static if (!is(L == Dummy)) { 177 | static assert(is(typeof(unaryFun!leftFun(L.init)))); 178 | } 179 | static if (!is(R == Dummy)) { 180 | static assert(is(typeof(unaryFun!rightFun(R.init)))); 181 | } 182 | 183 | void apply(Either!(L, R) e) { 184 | if (e.isLeft) { 185 | static if (!is(L == Dummy)) { 186 | unaryFun!leftFun(e.left); 187 | } else { 188 | assert(false); 189 | } 190 | } else { 191 | static if (!is(R == Dummy)) { 192 | unaryFun!rightFun(e.right); 193 | } else { 194 | assert(false); 195 | } 196 | } 197 | } 198 | } 199 | 200 | unittest { 201 | writeln(__FILE__, ": test Either 1"); 202 | 203 | Either!(string, T) find(T)(T[] xs, T value) { 204 | import std.algorithm : find; 205 | import std.range; 206 | auto res = xs.find(value); 207 | if (res.empty) return Left!(string, int)(format!"%s is not found"(value)); 208 | return Right!(string, int)(res.front); 209 | } 210 | 211 | int[] as = [1, 2, 3]; 212 | assert(find(as, 3) == 3); 213 | assert(find(as, 4) == format!"%s is not found"(4)); 214 | 215 | assert(find(as, 3).fmap!"a*a" == 3*3); 216 | assert(find(as, 4).fmap!"a*a" == format!"%s is not found"(4)); 217 | 218 | assert(Left("nothing").fmap!"a*a" == "nothing"); 219 | 220 | assert( 221 | as.Right.bind!( 222 | xs => find(xs, 3) 223 | ) == 3 224 | ); 225 | assert( 226 | as.Right.bind!( 227 | xs => find(xs, 4) 228 | ) == format!"%s is not found"(4) 229 | ); 230 | assert( 231 | Left("nothing").bind!( 232 | xs => find(xs, 3) 233 | ) == "nothing" 234 | ); 235 | } 236 | 237 | unittest { 238 | writeln(__FILE__, ": test Either 2"); 239 | 240 | long x; 241 | Right(3).apply!( 242 | left => assert(false), 243 | right => x = right 244 | ); 245 | assert(x == 3); 246 | 247 | string str; 248 | Left("yeah!").apply!( 249 | left => str = left, 250 | right => assert(false) 251 | ); 252 | assert(str == "yeah!"); 253 | } 254 | -------------------------------------------------------------------------------- /src/dprolog/util/GetOpt.d: -------------------------------------------------------------------------------- 1 | module dprolog.util.GetOpt; 2 | 3 | import dprolog.engine.Engine; 4 | import dprolog.engine.Messenger; 5 | import dprolog.engine.Reader; 6 | import dprolog.util.Singleton; 7 | 8 | import std.stdio; 9 | import std.format; 10 | import std.conv; 11 | import std.string; 12 | import std.algorithm; 13 | import std.getopt; 14 | import std.typecons; 15 | 16 | alias GetOpt = Singleton!GetOpt_; 17 | 18 | private class GetOpt_ { 19 | 20 | private: 21 | bool _shouldExit = false; 22 | 23 | enum helpOption = Option("h", "help", "Print this help message", false); 24 | enum versionOption = Option("v", "version", "Print version of dprolog", false); 25 | enum fileOption = Option("f", "file", "Read `VALUE` as an initialization file", true); 26 | enum verboseOption = Option("", "verbose", "Print diagnostic output", false); 27 | 28 | enum programVersion = import("dprolog_version.txt").chomp; 29 | enum compileDate = import("compile_date.txt").chomp; 30 | public: 31 | void run(string[] args) { 32 | bool helpMode; 33 | bool versionMode; 34 | string filePath; 35 | bool verboseMode; 36 | 37 | try { 38 | args.getopt( 39 | helpOption.params(helpMode).expand, 40 | versionOption.params(versionMode).expand, 41 | fileOption.params(filePath).expand, 42 | verboseOption.params(verboseMode).expand, 43 | ); 44 | } catch(Throwable e) { 45 | writeln(format!"Error processing arguments: %s"(e.msg)); 46 | writeln("Run 'dprolog --help' for usage information."); 47 | _shouldExit = true; 48 | return; 49 | } 50 | 51 | if (helpMode) { 52 | printUsage!([ 53 | helpOption, 54 | versionOption, 55 | fileOption, 56 | verboseOption, 57 | ]); 58 | _shouldExit = true; 59 | return; 60 | } 61 | 62 | if (versionMode) { 63 | format!"D-Prolog %s, built on %s"( 64 | programVersion, 65 | compileDate 66 | ).writeln; 67 | _shouldExit = true; 68 | return; 69 | } 70 | 71 | Engine.verboseMode = verboseMode; 72 | 73 | // read a file 74 | if (!filePath.empty) { 75 | Reader.read(filePath.to!dstring); 76 | Messenger.showAll(); 77 | } 78 | } 79 | 80 | bool shouldExit() { 81 | return _shouldExit; 82 | } 83 | 84 | private: 85 | struct Option { 86 | string shortOpt; 87 | string longOpt; 88 | string description; 89 | bool requiredValue; 90 | auto params(T)(ref T value) { 91 | return tuple( 92 | format!"%s%s"( 93 | longOpt, 94 | (shortOpt.empty ? "" : "|") ~ shortOpt 95 | ), 96 | description, 97 | &value 98 | ); 99 | } 100 | @property string shortString() { 101 | return shortOpt.empty ? "" : format!"-%s"(shortOpt); 102 | } 103 | @property string longString() { 104 | return format!"--%s%s"(longOpt, requiredValue ? "=VALUE" : ""); 105 | } 106 | } 107 | 108 | void printUsage(Option[] options)() { 109 | writeln("Usage:"); 110 | writeln(" dprolog []"); 111 | writeln; 112 | writeln("Options:"); 113 | enum len1 = options.map!"a.shortString.length".reduce!max.to!string; 114 | enum len2 = options.map!"a.longString.length".reduce!max.to!string; 115 | foreach(opt; options) { 116 | format!( 117 | " %"~ len1 ~"s %"~ len2 ~"s %s" 118 | )( 119 | opt.shortString, 120 | opt.longString, 121 | opt.description 122 | ).writeln; 123 | } 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/dprolog/util/Maybe.d: -------------------------------------------------------------------------------- 1 | module dprolog.util.Maybe; 2 | 3 | import std.stdio; 4 | import std.format; 5 | import std.functional; 6 | import std.traits; 7 | 8 | struct Maybe(T) { 9 | 10 | private: 11 | T value = T.init; 12 | bool _isJust = false; 13 | 14 | package: 15 | this(T value) { 16 | this.value = value; 17 | this._isJust = true; 18 | } 19 | 20 | public: 21 | @property bool isJust() { 22 | return _isJust; 23 | } 24 | 25 | @property bool isNone() { 26 | return !_isJust; 27 | } 28 | 29 | @property T get() in(isJust) do { 30 | return value; 31 | } 32 | 33 | Maybe!T opAssign(T value) in(!isNull(value)) do { 34 | this.value = value; 35 | this._isJust = true; 36 | return this; 37 | } 38 | 39 | bool opEquals(T2)(Maybe!T2 that) { 40 | alias T1 = T; 41 | if (this.isJust != that.isJust) return false; 42 | if (this.isJust) { 43 | return this.get == that.get; 44 | } else { 45 | return true; 46 | } 47 | } 48 | 49 | bool opEquals(S)(S that) 50 | if (!isInstanceOf!(TemplateOf!Maybe, S)) { 51 | static if (is(S : T)) { 52 | return isJust && this.get == value; 53 | } else { 54 | return false; 55 | } 56 | } 57 | 58 | string toString() { 59 | if (isJust) { 60 | return format!"Maybe!%s(%s)"(typeid(T), value); 61 | } else { 62 | return format!"Maybe!%s(None)"(typeid(T)); 63 | } 64 | } 65 | 66 | } 67 | 68 | private struct Dummy { 69 | bool opEquals(O)(O o) { 70 | return false; 71 | } 72 | } 73 | 74 | private bool isNull(T)(T value) { 75 | static if (is(typeof(value is null))) { 76 | return ( 77 | isPointer!T || 78 | is(T == class) || 79 | is(T == interface) || 80 | is(T == function) || 81 | is(T == delegate) 82 | ) && value is null; 83 | } else { 84 | return false; 85 | } 86 | } 87 | 88 | Maybe!T Just(T)(T value) in(!isNull(value)) do { 89 | return Maybe!T(value); 90 | } 91 | 92 | Maybe!T None(T)() { 93 | return Maybe!T(); 94 | } 95 | 96 | Maybe!Dummy None() { 97 | return None!Dummy; 98 | } 99 | 100 | // fmap :: Maybe!T -> (T -> S) -> Maybe!S 101 | // fmap :: bool -> (() -> S) -> Maybe!S 102 | template fmap(alias fun) { 103 | template fmap(T) { 104 | static if (!is(T == Dummy)) { 105 | static assert(is(typeof(unaryFun!fun(T.init)))); 106 | alias S = typeof(unaryFun!fun(T.init)); 107 | } else { 108 | alias S = T; 109 | } 110 | Maybe!S fmap(Maybe!T m) { 111 | if (m.isNone) { 112 | return None!S; 113 | } else { 114 | static if (!is(T == Dummy)) { 115 | return Just!S(unaryFun!fun(m.get)); 116 | } else { 117 | assert(false); 118 | } 119 | } 120 | } 121 | } 122 | static if (is(typeof(fun()))) { 123 | alias S = typeof(fun()); 124 | Maybe!S fmap(bool isTrue) { 125 | return isTrue ? Just(fun()) : None!S; 126 | } 127 | } 128 | } 129 | 130 | // fmap :: bool -> (lazy T) -> Maybe!T 131 | Maybe!T fmap(T)(bool isTrue, lazy T value) { 132 | return isTrue ? Just(value) : None!T; 133 | } 134 | 135 | // bind :: Maybe!T -> (T -> Maybe!S) -> Maybe!S 136 | template bind(alias fun, T) { 137 | static if (!is(T == Dummy)) { 138 | static assert(isInstanceOf!(Maybe, typeof(unaryFun!fun(T.init)))); 139 | alias S = TemplateArgsOf!(typeof(unaryFun!fun(T.init)))[0]; 140 | } else { 141 | alias S = T; 142 | } 143 | Maybe!S bind(Maybe!T m) { 144 | if (m.isNone) { 145 | return None!S; 146 | } else { 147 | static if (!is(T == Dummy)) { 148 | return unaryFun!fun(m.get); 149 | } else { 150 | assert(false); 151 | } 152 | } 153 | } 154 | } 155 | 156 | void apply(alias fun, bool enforce = false, T)(Maybe!T m) 157 | if(is(typeof(unaryFun!fun(T.init)))) { 158 | if (m.isNone) { 159 | assert(!enforce, "`m` is None, but should be Just because the enforce option is true."); 160 | } else { 161 | fun(m.get); 162 | } 163 | } 164 | 165 | unittest { 166 | writeln(__FILE__, ": test Maybe"); 167 | 168 | Maybe!T find(T)(T[] xs, T value) { 169 | import std.algorithm : find; 170 | import std.range; 171 | auto res = xs.find(value); 172 | if (res.empty) return None!T; 173 | return Just(res.front); 174 | } 175 | 176 | int[] as = [1, 2, 3]; 177 | assert(find(as, 3) == 3); 178 | assert(find(as, 4) == None); 179 | 180 | assert(find(as, 3).fmap!"a*a" == 3*3); 181 | assert(find(as, 4).fmap!"a*a" == None); 182 | 183 | assert(None.fmap!"a*a" == None); 184 | 185 | assert( 186 | as.Just.bind!( 187 | xs => find(xs, 3) 188 | ) == 3 189 | ); 190 | assert( 191 | as.Just.bind!( 192 | xs => find(xs, 4) 193 | ) == None 194 | ); 195 | assert( 196 | None.bind!( 197 | xs => find(xs, 3) 198 | ) == None 199 | ); 200 | } 201 | -------------------------------------------------------------------------------- /src/dprolog/util/Message.d: -------------------------------------------------------------------------------- 1 | module dprolog.util.Message; 2 | 3 | import dprolog.util.colorize; 4 | 5 | import std.conv; 6 | import std.traits; 7 | 8 | Message DefaultMessage(String)(String text) 9 | if (isSomeString!String) { 10 | return text.makeMessage(ForegroundColor.None, BackgroundColor.None); 11 | } 12 | 13 | Message InfoMessage(String)(String text) 14 | if (isSomeString!String) { 15 | return text.makeMessage(ForegroundColor.Green, BackgroundColor.None); 16 | } 17 | 18 | Message VerboseMessage(String)(String text) 19 | if (isSomeString!String) { 20 | return text.makeMessage(ForegroundColor.Cyan, BackgroundColor.None); 21 | } 22 | 23 | Message WarningMessage(String)(String text) 24 | if (isSomeString!String) { 25 | return text.makeMessage(ForegroundColor.Yellow, BackgroundColor.None); 26 | } 27 | 28 | Message ErrorMessage(String)(String text) 29 | if (isSomeString!String) { 30 | return text.makeMessage(ForegroundColor.Red, BackgroundColor.None); 31 | } 32 | 33 | private Message makeMessage(String)(String text, ForegroundColor fgColor, BackgroundColor bgColor) 34 | if (isSomeString!String) { 35 | return Message(text.to!dstring.colorize(fgColor).colorize(bgColor)); 36 | } 37 | 38 | struct Message { 39 | private dstring text; 40 | private this(dstring text) { 41 | this.text = text; 42 | } 43 | string toString() const { 44 | return text.to!string; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/dprolog/util/Singleton.d: -------------------------------------------------------------------------------- 1 | module dprolog.util.Singleton; 2 | 3 | import std.format; 4 | 5 | static class Singleton(T) { 6 | private static T instance; 7 | static auto opDispatch(string name, Args...)(auto ref Args args) { 8 | if (!instance) { 9 | instance = new T(); 10 | } 11 | return mixin(format!"instance.%s(args)"(name)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/dprolog/util/UnionFind.d: -------------------------------------------------------------------------------- 1 | module dprolog.util.UnionFind; 2 | 3 | import std.stdio; 4 | import std.format; 5 | import std.array; 6 | import std.typecons; 7 | import std.algorithm; 8 | import std.functional; 9 | 10 | class UnionFind(T, alias less = (a, b) => 0) 11 | if (is(typeof(binaryFun!less(T.init, T.init)) : long)) { 12 | 13 | /* 14 | S := {x | same(x, a)} ⇒ ∀x ∈ S-{a}, less(a, x)<0 ⇒ ∀x ∈ S, root(x) == a 15 | */ 16 | 17 | private: 18 | alias lessFun = binaryFun!less; 19 | Node[T] storage; 20 | 21 | public: 22 | this(Node[T] storage = null) { 23 | this.storage = storage; 24 | } 25 | 26 | void add(T value) { 27 | if (value !in storage) { 28 | storage[value] = new Node(value); 29 | } 30 | } 31 | 32 | void unite(T a, T b) { 33 | unite(storage[a], storage[b]); 34 | } 35 | 36 | bool same(T a, T b) { 37 | return same(storage[a], storage[b]); 38 | } 39 | 40 | T root(T value) { 41 | return find(storage[value]).value; 42 | } 43 | 44 | void clear() { 45 | storage = null; 46 | } 47 | 48 | auto opBinary(string op)(typeof(this) that) if (op == "~") { 49 | auto newUF = this.clone; 50 | foreach(k, v; that.storage) { 51 | newUF.storage[k] = v; 52 | } 53 | return newUF; 54 | } 55 | 56 | auto clone() { 57 | Node[T] newStorage = storage.dup; 58 | foreach(ref node; newStorage.byValue) { 59 | node = node.clone; 60 | } 61 | foreach(node; newStorage.byValue) { 62 | node.parent = node.parent is null ? null : newStorage[node.parent.value]; 63 | } 64 | return new UnionFind(newStorage); 65 | } 66 | 67 | override string toString() { 68 | return format!"UnionFind(%-(\n\t%s,%)\n)"( 69 | storage.byKey.map!( 70 | k => format!"%s => %s"(k, root(k)) 71 | ).array 72 | ); 73 | } 74 | 75 | private: 76 | void unite(Node x, Node y) { 77 | x = find(x); 78 | y = find(y); 79 | if (same(x, y)) return; 80 | 81 | if (lessFun(x.value, y.value)<0 || (lessFun(x.value, y.value)==0 && x.rank>y.rank)) { 82 | x.rank = max(x.rank, y.rank + 1); 83 | storage[y.value].parent = x; 84 | } else { 85 | y.rank = max(x.rank + 1, y.rank); 86 | storage[x.value].parent = y; 87 | } 88 | } 89 | 90 | bool same(Node x, Node y) { 91 | return find(x) == find(y); 92 | } 93 | 94 | Node find(Node node) { 95 | if (node.parent is null) { 96 | return node; 97 | } else { 98 | node.parent = find(node.parent); 99 | return node.parent; 100 | } 101 | } 102 | 103 | class Node { 104 | T value; 105 | long rank = 0; 106 | Node parent; 107 | 108 | this(T value, long rank = 0, Node parent = null) { 109 | this.value = value; 110 | this.rank = rank; 111 | this.parent = parent; 112 | } 113 | 114 | Node clone() { 115 | return new Node(value, rank, parent); 116 | } 117 | } 118 | } 119 | 120 | 121 | /* ---------- Unit Tests ---------- */ 122 | 123 | unittest { 124 | writeln(__FILE__, ": test"); 125 | 126 | auto uf1 = new UnionFind!(long, (a, b) => a - b); 127 | 128 | assert(uf1.storage.length == 0); 129 | 130 | auto uf2 = uf1.clone; 131 | uf1.add(1); uf1.add(2); uf1.add(3); 132 | uf1.unite(1, 2); 133 | 134 | assert(uf1.storage.length == 3); 135 | assert(uf1.same(1, 2) && !uf1.same(2, 3) && !uf1.same(3, 1)); 136 | assert(uf1.root(1) == 1 && uf1.root(2) == 1 && uf1.root(3) == 3); 137 | 138 | assert(uf2.storage.length == 0); 139 | 140 | auto uf3 = uf1.clone; 141 | uf1.unite(2, 3); 142 | 143 | assert(uf1.storage.length == 3); 144 | assert(uf1.same(1, 2) && uf1.same(2, 3) && uf1.same(3, 1)); 145 | 146 | assert(uf3.storage.length == 3); 147 | assert(uf3.same(1, 2) && !uf3.same(2, 3) && !uf3.same(3, 1)); 148 | 149 | uf2.add(0); 150 | auto uf4 = uf1 ~ uf2; 151 | 152 | assert(uf1.storage.length == 3); 153 | assert(uf1.same(1, 2) && uf1.same(2, 3) && uf1.same(3, 1)); 154 | 155 | assert(uf4.storage.length == 4); 156 | assert(uf4.same(1, 2) && uf4.same(2, 3) && uf4.same(3, 1) && !uf4.same(0, 1) && !uf4.same(0, 2) && !uf4.same(0, 3)); 157 | 158 | uf4.unite(0, 1); 159 | 160 | assert(uf1.storage.length == 3); 161 | assert(uf1.same(1, 2) && uf1.same(2, 3) && uf1.same(3, 1)); 162 | 163 | assert(uf4.storage.length == 4); 164 | assert(uf4.same(1, 2) && uf4.same(2, 3) && uf4.same(3, 1) && uf4.same(0, 1) && uf4.same(0, 2) && uf4.same(0, 3)); 165 | } 166 | -------------------------------------------------------------------------------- /src/dprolog/util/colorize.d: -------------------------------------------------------------------------------- 1 | module dprolog.util.colorize; 2 | 3 | import std.conv; 4 | import std.format; 5 | import std.traits; 6 | 7 | enum ForegroundColor { 8 | None = -1, 9 | Black = 30, 10 | Red, 11 | Green, 12 | Yellow, 13 | Blue, 14 | Magenta, 15 | Cyan, 16 | White, 17 | } 18 | 19 | enum BackgroundColor { 20 | None = -1, 21 | Black = 40, 22 | Red, 23 | Green, 24 | Yellow, 25 | Blue, 26 | Magenta, 27 | Cyan, 28 | White 29 | } 30 | 31 | bool isColor(Color)() { 32 | return is(Color == ForegroundColor) || is(Color == BackgroundColor); 33 | } 34 | 35 | @property String colorize(String, Color)(String text, Color color) 36 | if (isSomeString!String && isColor!Color) { 37 | if (color == Color.None) return text; 38 | return format!"\033[%dm%s\033[0m"(color, text).to!String; 39 | } 40 | 41 | import std.stdio; 42 | 43 | unittest { 44 | writeln(__FILE__, ": test color print"); 45 | foreach(bgColor; EnumMembers!BackgroundColor) { 46 | foreach(fgColor; EnumMembers!ForegroundColor) { 47 | format!"(%d, %d)"(bgColor, fgColor).colorize(bgColor).colorize(fgColor).write; 48 | } 49 | writeln; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/dprolog/util/functions.d: -------------------------------------------------------------------------------- 1 | module dprolog.util.functions; 2 | 3 | bool instanceOf(S, T)(const T obj) { 4 | return cast(S) obj !is null; 5 | } 6 | -------------------------------------------------------------------------------- /tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkark/d-prolog/d77ad685aa8331e95b079a036e5d7ad6286c03cf/tmp/.gitkeep --------------------------------------------------------------------------------