├── .gitignore
├── LICENSE
├── README.md
├── ch04_arith
├── README.md
├── interpreter.cpp
├── interpreter.hpp
└── test.cpp
├── ch07_untyped
├── README.md
├── interpreter.cpp
├── interpreter.hpp
└── test.cpp
├── ch08_tyarith
├── README.md
├── interpreter.cpp
├── interpreter.hpp
└── test.cpp
├── ch10_simplebool
├── README.md
├── interpreter.cpp
├── interpreter.hpp
└── test.cpp
├── ch11_fullsimple
├── README.md
├── interpreter.cpp
├── interpreter.hpp
└── test.cpp
├── ch17_rcdjoinsub
├── README.md
├── interpreter.cpp
├── interpreter.hpp
└── test.cpp
└── ch18_fullref
├── README.md
├── examples
├── 01_object_generators.ml
├── 02_classes.ml
├── 03_self.ml
├── 04_self_late_binding.ml
├── 05_self_late_binding_ref.ml
└── example_run.log
├── interpreter.cpp
├── interpreter.hpp
└── test.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | /**/*.out*
2 | tmp/**
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Kareem Ergawy
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 | # Types and Programming Languages
2 |
3 | Implementations of programming languages and type systems studied in [Types and Programming Languages](https://www.cis.upenn.edu/~bcpierce/tapl/).
4 |
5 | Each subdirectory implements one of the languages studied in the book. Each such implementation consists of a lexer, parser, interpreter, and type system for the language implemented.
6 |
7 | ### Directory Structure
8 |
9 | Each implementation contains 3 files:
10 | - **interpreter.hpp**: This is the main part containing the actual implementation of the language.
11 | - **interpreter.cpp**: This file contains a simple main() method to invoke the interpreter. For now, it only accepts a single command line argument consisting of the program to be evaluated.
12 | - **test.cpp**: Contains tests for the separate components of an interpreter: lexer, parser, and interpreter.
13 |
14 | ### Status
15 |
16 | Language | Directory | Status
17 | --- | --- | ---
18 | Untyped Arithmetic Expressions | [ch04_arith](ch04_arith) | :heavy_check_mark: Lexer + Tests
:heavy_check_mark: Parser + Tests
:heavy_check_mark: Interpreter + Tests
19 | The Untyped Lmabda Calculus | [ch07_untyped](ch07_untyped) | :heavy_check_mark: Lexer + Tests
:heavy_check_mark: Parser + Tests
:heavy_check_mark: Interpreter + Tests
20 | Typed Arithmetic Expressions | [ch08_tyarith](ch08_tyarith) | :heavy_check_mark: Lexer + Tests
:heavy_check_mark: Parser + Tests
:heavy_check_mark: Interpreter & Type Checker + Tests
21 | Simply Typed Lambda Calculus | [ch10_simplebool](ch10_simplebool) | :heavy_check_mark: Lexer + Tests
:heavy_check_mark: Parser + Tests
:heavy_check_mark: Type Checker + Tests
:heavy_check_mark: Interpreter + Tests
22 | Typed Lambda Calculus (with various extensions) | [ch11_fullsimple](ch11_fullsimple) | __Natural numbers (Nat) type support__
:heavy_check_mark: Lexer + Tests
:heavy_check_mark: Parser + Tests
:heavy_check_mark: Type Checker + Tests
__Records and Projections__
:heavy_check_mark: Lexer + Tests
:heavy_check_mark: Parser + Tests
:heavy_check_mark: Type Checker + Tests
23 | Typed Lambda Calculus with Subtyping | [ch17_rcdjoinsub](ch17_rcdjoinsub) | :heavy_check_mark: Lexer + Tests
:heavy_check_mark: Parser + Tests
:heavy_check_mark: Interpreter + Tests
24 | Typed Lambda Calculus with Imperative Objects | [ch18_fullref](ch18_fullref)
[Example Programs](ch18_fullref/examples) | __Let bindings support__
:heavy_check_mark: Lexer + Tests
:heavy_check_mark: Parser + Tests
:heavy_check_mark: Interpreter + Tests
__References (Ref, Source, Sink)__
:heavy_check_mark: Lexer + Tests
:heavy_check_mark: Parser + Tests
:heavy_check_mark: Interpreter + Tests
__Sequencing__
:heavy_check_mark: Lexer + Tests
:heavy_check_mark: Parser + Tests
:heavy_check_mark: Interpreter + Tests
__Recursion__
:heavy_check_mark: Lexer + Tests
:heavy_check_mark: Parser + Tests
:heavy_check_mark: Interpreter + Tests
25 |
26 | ### Usage
27 |
28 | #### Running Tests
29 |
30 | ```bash
31 | cd ch##_
32 | clang++ --std=c++17 test.cpp && ./a.out
33 | ```
34 |
35 | #### Interpreter
36 |
37 | ```bash
38 | cd ch##_
39 | clang++ --std=c++17 interpreter.cpp && ./a.out "input program"
40 | ```
41 |
42 |
--------------------------------------------------------------------------------
/ch04_arith/README.md:
--------------------------------------------------------------------------------
1 | # Untyped Arithmetic Expressions
2 |
3 | ## Syntax
4 |
5 | ### Terms
6 |
7 | ```
8 | t ::=
9 | true
10 | false
11 | if t then t else t
12 | 0
13 | succ t
14 | pred t
15 | iszero t
16 | ```
17 |
18 | ### Values
19 |
20 | ```
21 | v ::=
22 | true
23 | false
24 | nv
25 |
26 | nv ::=
27 | 0
28 | succ nv
29 | ```
30 |
31 | ## Evaluation Rules
32 |
33 | TODO
34 |
--------------------------------------------------------------------------------
/ch04_arith/interpreter.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "interpreter.hpp"
4 |
5 | int main(int argc, char* argv[]) {
6 | if (argc < 2) {
7 | std::cerr
8 | << "Error: expected input program as a command line argument.\n";
9 | return 1;
10 | }
11 |
12 | parser::Parser parser{std::istringstream{argv[1]}};
13 | auto program = parser.ParseProgram();
14 | std::cout << " " << program << "\n";
15 |
16 | interpreter::Interpreter interpreter;
17 | std::cout << "=> " << interpreter.Interpret(program) << "\n";
18 |
19 | return 0;
20 | }
21 |
--------------------------------------------------------------------------------
/ch04_arith/interpreter.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | namespace lexer {
8 | struct Token {
9 | enum class Category {
10 | CONSTANT_TRUE,
11 | CONSTANT_FALSE,
12 | CONSTANT_ZERO,
13 |
14 | KEYWORD_IF,
15 | KEYWORD_THEN,
16 | KEYWORD_ELSE,
17 | KEYWORD_SUCC,
18 | KEYWORD_PRED,
19 | KEYWORD_ISZERO,
20 |
21 | MARKER_ERROR,
22 | MARKER_END
23 | };
24 |
25 | bool operator==(const Token& other) const {
26 | return category == other.category && text == other.text;
27 | }
28 |
29 | bool operator!=(const Token& other) const { return !(*this == other); }
30 |
31 | std::string DebugString() const;
32 |
33 | Category category;
34 | std::string text;
35 | };
36 |
37 | class Lexer {
38 | public:
39 | Lexer(std::istringstream&& in) : in_(std::move(in)) {}
40 |
41 | Token NextToken() {
42 | Token token;
43 |
44 | if (in_ >> token.text) {
45 | if (token.text == "true") {
46 | token.category = Token::Category::CONSTANT_TRUE;
47 | } else if (token.text == "false") {
48 | token.category = Token::Category::CONSTANT_FALSE;
49 | } else if (token.text == "0") {
50 | token.category = Token::Category::CONSTANT_ZERO;
51 | } else if (token.text == "if") {
52 | token.category = Token::Category::KEYWORD_IF;
53 | } else if (token.text == "then") {
54 | token.category = Token::Category::KEYWORD_THEN;
55 | } else if (token.text == "else") {
56 | token.category = Token::Category::KEYWORD_ELSE;
57 | } else if (token.text == "succ") {
58 | token.category = Token::Category::KEYWORD_SUCC;
59 | } else if (token.text == "pred") {
60 | token.category = Token::Category::KEYWORD_PRED;
61 | } else if (token.text == "iszero") {
62 | token.category = Token::Category::KEYWORD_ISZERO;
63 | } else {
64 | token.category = Token::Category::MARKER_ERROR;
65 | }
66 | } else {
67 | token.category = Token::Category::MARKER_END;
68 | }
69 |
70 | return token;
71 | }
72 |
73 | private:
74 | std::istringstream in_;
75 | };
76 |
77 | std::ostream& operator<<(std::ostream& out, Token::Category token_category) {
78 | switch (token_category) {
79 | case Token::Category::CONSTANT_TRUE:
80 | out << "true";
81 | break;
82 | case Token::Category::CONSTANT_FALSE:
83 | out << "false";
84 | break;
85 | case Token::Category::CONSTANT_ZERO:
86 | out << "0";
87 | break;
88 |
89 | case Token::Category::KEYWORD_IF:
90 | out << "if";
91 | break;
92 | case Token::Category::KEYWORD_THEN:
93 | out << "then";
94 | break;
95 | case Token::Category::KEYWORD_ELSE:
96 | out << "else";
97 | break;
98 | case Token::Category::KEYWORD_SUCC:
99 | out << "succ";
100 | break;
101 | case Token::Category::KEYWORD_PRED:
102 | out << "pred";
103 | break;
104 | case Token::Category::KEYWORD_ISZERO:
105 | out << "iszero";
106 | break;
107 |
108 | case Token::Category::MARKER_ERROR:
109 | out << "";
110 | break;
111 | case Token::Category::MARKER_END:
112 | out << "";
113 | break;
114 |
115 | default:
116 | out << "";
117 | }
118 |
119 | return out;
120 | }
121 |
122 | std::string Token::DebugString() const {
123 | std::ostringstream ss;
124 | ss << "{text: " << text << ", category: " << category << "}";
125 | return ss.str();
126 | }
127 |
128 | } // namespace lexer
129 |
130 | namespace parser {
131 |
132 | class Term {
133 | using Cat = lexer::Token::Category;
134 | friend std::ostream& operator<<(std::ostream& out,
135 | const Term& token_category);
136 |
137 | public:
138 | Term(Cat first_token_category, std::vector sub_terms = {})
139 | : first_token_category_(first_token_category), sub_terms_(sub_terms) {}
140 |
141 | Term() = default;
142 | Term(const Term&) = default;
143 | Term(Term&&) = default;
144 | Term& operator=(const Term&) = default;
145 | Term& operator=(Term&&) = default;
146 |
147 | Cat Category() const { return first_token_category_; }
148 | Term& SubTerm(int i) { return sub_terms_[i]; }
149 |
150 | std::string ASTString(int indentation = 0) const {
151 | std::ostringstream out;
152 | std::string prefix = std::string(indentation, ' ');
153 |
154 | switch (Category()) {
155 | case Cat::CONSTANT_ZERO:
156 | case Cat::CONSTANT_TRUE:
157 | case Cat::CONSTANT_FALSE:
158 | out << prefix << Category();
159 | break;
160 |
161 | case Cat::KEYWORD_IF:
162 | out << prefix << Category() << "\n";
163 | out << sub_terms_[0].ASTString(indentation + 2) << "\n";
164 | out << prefix << Cat::KEYWORD_ELSE << "\n";
165 | out << sub_terms_[1].ASTString(indentation + 2) << "\n";
166 | out << prefix << Cat::KEYWORD_THEN << "\n";
167 | out << sub_terms_[2].ASTString(indentation + 2);
168 | break;
169 |
170 | case Cat::KEYWORD_SUCC:
171 | case Cat::KEYWORD_PRED:
172 | case Cat::KEYWORD_ISZERO:
173 | out << prefix << Category() << "\n";
174 | out << sub_terms_[0].ASTString(indentation + 2);
175 | break;
176 |
177 | default:
178 | break;
179 | }
180 |
181 | return out.str();
182 | }
183 |
184 | bool operator==(const Term& other) const {
185 | if (Category() != other.Category()) {
186 | return false;
187 | }
188 |
189 | bool res = true;
190 |
191 | switch (Category()) {
192 | case Cat::CONSTANT_ZERO:
193 | case Cat::CONSTANT_TRUE:
194 | case Cat::CONSTANT_FALSE:
195 | break;
196 |
197 | case Cat::KEYWORD_IF:
198 | for (int i = 0; i < 3; ++i) {
199 | res = res && (sub_terms_[i] == other.sub_terms_[i]);
200 |
201 | if (!res) {
202 | break;
203 | }
204 | }
205 |
206 | break;
207 |
208 | case Cat::KEYWORD_SUCC:
209 | case Cat::KEYWORD_PRED:
210 | case Cat::KEYWORD_ISZERO:
211 | res = res && (sub_terms_[0] == other.sub_terms_[0]);
212 |
213 | break;
214 |
215 | default:
216 | break;
217 | }
218 |
219 | return res;
220 | }
221 |
222 | bool operator!=(const Term& other) const { return !(*this == other); }
223 |
224 | private:
225 | // Category of the first token in a term speicifies the type of the term.
226 | Cat first_token_category_;
227 | std::vector sub_terms_;
228 | };
229 |
230 | std::ostream& operator<<(std::ostream& out, const Term& term) {
231 | using Category = lexer::Token::Category;
232 | out << term.first_token_category_;
233 |
234 | if (term.first_token_category_ == Category::KEYWORD_IF) {
235 | out << " (" << term.sub_terms_[0] << ") " << Category::KEYWORD_THEN
236 | << " (" << term.sub_terms_[1] << ") " << Category::KEYWORD_ELSE
237 | << " (" << term.sub_terms_[2] << ")";
238 | } else if (term.first_token_category_ == Category::KEYWORD_SUCC ||
239 | term.first_token_category_ == Category::KEYWORD_PRED ||
240 | term.first_token_category_ == Category::KEYWORD_ISZERO) {
241 | out << " (" << term.sub_terms_[0] << ")";
242 | }
243 |
244 | return out;
245 | }
246 |
247 | class Parser {
248 | public:
249 | Parser(std::istringstream&& in) : lexer_(std::move(in)) {}
250 |
251 | Term ParseProgram() {
252 | auto program = NextTerm();
253 |
254 | if (lexer_.NextToken().category != lexer::Token::Category::MARKER_END) {
255 | throw std::invalid_argument(
256 | "Error: extra input after program end.");
257 | }
258 |
259 | return program;
260 | }
261 |
262 | private:
263 | Term NextTerm() {
264 | using Category = lexer::Token::Category;
265 | auto token = lexer_.NextToken();
266 |
267 | std::vector sub_terms;
268 |
269 | switch (token.category) {
270 | // Possible terms:
271 | case Category::CONSTANT_TRUE:
272 | case Category::CONSTANT_FALSE:
273 | break;
274 |
275 | case Category::CONSTANT_ZERO:
276 | break;
277 |
278 | case Category::KEYWORD_IF: {
279 | // Add condition sub-term.
280 | sub_terms.emplace_back(NextTerm());
281 |
282 | if (lexer_.NextToken().category != Category::KEYWORD_THEN) {
283 | // Parsing error:
284 | throw std::invalid_argument(
285 | "Error: invalid if-then-else term.");
286 | }
287 |
288 | // Add then branch sub-term.
289 | sub_terms.emplace_back(NextTerm());
290 |
291 | if (lexer_.NextToken().category != Category::KEYWORD_ELSE) {
292 | // Parsing error:
293 | throw std::invalid_argument(
294 | "Error: invalid if-then-else term.");
295 | }
296 |
297 | // Add then else sub-term.
298 | sub_terms.emplace_back(NextTerm());
299 | } break;
300 |
301 | case Category::KEYWORD_SUCC: {
302 | auto sub_term = NextTerm();
303 | sub_terms.emplace_back(sub_term);
304 | } break;
305 |
306 | case Category::KEYWORD_PRED:
307 | case Category::KEYWORD_ISZERO: {
308 | auto sub_term = NextTerm();
309 | sub_terms.emplace_back(sub_term);
310 | } break;
311 |
312 | // End of input (parse error):
313 | case Category::MARKER_END:
314 | throw std::invalid_argument("Error: reached end of input.");
315 |
316 | // Lexing errors:
317 | case Category::MARKER_ERROR:
318 | throw std::invalid_argument(
319 | "Error: invalid token: " + token.text + ".");
320 |
321 | // Parsing errors:
322 | case Category::KEYWORD_THEN:
323 | case Category::KEYWORD_ELSE:
324 | throw std::invalid_argument("Error: invalid term start " +
325 | token.text + ".");
326 | }
327 |
328 | return Term(token.category, sub_terms);
329 | }
330 |
331 | private:
332 | lexer::Lexer lexer_;
333 | };
334 | } // namespace parser
335 |
336 | namespace interpreter {
337 | using lexer::Token;
338 | using parser::Term;
339 |
340 | class Interpreter {
341 | public:
342 | std::string Interpret(Term program) {
343 | Term res = Eval(program);
344 | return AsString(IsValue(res) ? res
345 | : Term(Token::Category::MARKER_ERROR));
346 | }
347 |
348 | private:
349 | std::string AsString(Term value) {
350 | std::ostringstream ss;
351 | ss << value;
352 | auto term_str = ss.str();
353 |
354 | if (IsNumericValue(value)) {
355 | std::size_t start_pos = 0;
356 | int num = 0;
357 |
358 | while ((start_pos = term_str.find("succ", start_pos)) !=
359 | std::string::npos) {
360 | ++num;
361 | ++start_pos;
362 | }
363 |
364 | return std::to_string(num);
365 | }
366 |
367 | return term_str;
368 | }
369 |
370 | Term Eval(Term term) {
371 | try {
372 | Term res = Eval1(term);
373 | return Eval(res);
374 | } catch (std::invalid_argument&) {
375 | return term;
376 | }
377 | }
378 |
379 | Term Eval1(Term term) {
380 | switch (term.Category()) {
381 | case Token::Category::KEYWORD_IF: {
382 | return Eval1If(term);
383 | }
384 |
385 | case Token::Category::KEYWORD_SUCC: {
386 | term.SubTerm(0) = Eval1(term.SubTerm(0));
387 | return term;
388 | }
389 |
390 | case Token::Category::KEYWORD_PRED: {
391 | return Eval1Pred(term);
392 | }
393 |
394 | case Token::Category::KEYWORD_ISZERO: {
395 | return Eval1IsZero(term);
396 | }
397 |
398 | default:
399 | throw std::invalid_argument("No applicable rule.");
400 | }
401 | }
402 |
403 | Term Eval1If(Term term) {
404 | switch (term.SubTerm(0).Category()) {
405 | case Token::Category::CONSTANT_TRUE: {
406 | return term.SubTerm(1);
407 | }
408 |
409 | case Token::Category::CONSTANT_FALSE: {
410 | return term.SubTerm(2);
411 | }
412 |
413 | default:
414 | term.SubTerm(0) = Eval1(term.SubTerm(0));
415 | return term;
416 | }
417 | }
418 |
419 | Term Eval1Pred(Term term) {
420 | switch (term.SubTerm(0).Category()) {
421 | case Token::Category::CONSTANT_ZERO: {
422 | return Term(Token::Category::CONSTANT_ZERO);
423 | }
424 |
425 | case Token::Category::KEYWORD_SUCC: {
426 | if (IsNumericValue(term.SubTerm(0))) {
427 | return term.SubTerm(0).SubTerm(0);
428 | } else {
429 | term.SubTerm(0) = Eval1(term.SubTerm(0));
430 | return term;
431 | }
432 | }
433 |
434 | default:
435 | term.SubTerm(0) = Eval1(term.SubTerm(0));
436 | return term;
437 | }
438 | }
439 |
440 | Term Eval1IsZero(Term term) {
441 | switch (term.SubTerm(0).Category()) {
442 | case Token::Category::CONSTANT_ZERO: {
443 | return Term(Token::Category::CONSTANT_TRUE);
444 | }
445 |
446 | case Token::Category::KEYWORD_SUCC: {
447 | if (IsNumericValue(term.SubTerm(0))) {
448 | return Term(Token::Category::CONSTANT_FALSE);
449 | } else {
450 | term.SubTerm(0) = Eval1(term.SubTerm(0));
451 | return term;
452 | }
453 | }
454 |
455 | default:
456 | term.SubTerm(0) = Eval1(term.SubTerm(0));
457 | return term;
458 | }
459 | }
460 |
461 | bool IsNumericValue(Term term) {
462 | return term.Category() == Token::Category::CONSTANT_ZERO ||
463 | (term.Category() == Token::Category::KEYWORD_SUCC &&
464 | IsNumericValue(term.SubTerm(0)));
465 | }
466 |
467 | bool IsValue(Term term) {
468 | return term.Category() == Token::Category::CONSTANT_TRUE ||
469 | term.Category() == Token::Category::CONSTANT_FALSE ||
470 | IsNumericValue(term);
471 | }
472 | };
473 | } // namespace interpreter
474 |
--------------------------------------------------------------------------------
/ch04_arith/test.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "interpreter.hpp"
4 |
5 | namespace lexer {
6 | namespace test {
7 | void Run();
8 | }
9 | } // namespace lexer
10 |
11 | namespace parser {
12 | namespace test {
13 | void Run();
14 | }
15 | } // namespace parser
16 |
17 | namespace interpreter {
18 | namespace test {
19 | void Run();
20 | }
21 | } // namespace interpreter
22 |
23 | int main() {
24 | lexer::test::Run();
25 | parser::test::Run();
26 | interpreter::test::Run();
27 |
28 | return 0;
29 | }
30 |
31 | namespace utils {
32 | namespace test {
33 | namespace color {
34 |
35 | std::string kRed{"\033[1;31m"};
36 | std::string kGreen{"\033[1;32m"};
37 | std::string kYellow{"\033[1;33m"};
38 | std::string kReset{"\033[0m"};
39 |
40 | } // namespace color
41 | } // namespace test
42 | } // namespace utils
43 |
44 | namespace lexer {
45 | namespace test {
46 |
47 | using Category = Token::Category;
48 | using TestData = std::pair>;
49 | using namespace utils::test;
50 |
51 | std::vector kData = {
52 | // All valid tokens:
53 | TestData{"true false if else then 0 succ pred iszero",
54 | {Token{Category::CONSTANT_TRUE, "true"},
55 | Token{Category::CONSTANT_FALSE, "false"},
56 | Token{Category::KEYWORD_IF, "if"},
57 | Token{Category::KEYWORD_ELSE, "else"},
58 | Token{Category::KEYWORD_THEN, "then"},
59 | Token{Category::CONSTANT_ZERO, "0"},
60 | Token{Category::KEYWORD_SUCC, "succ"},
61 | Token{Category::KEYWORD_PRED, "pred"},
62 | Token{Category::KEYWORD_ISZERO, "iszero"}}},
63 | // Invalid tokens:
64 | TestData{"x", {Token{Category::MARKER_ERROR, "x"}}},
65 | TestData{"1", {Token{Category::MARKER_ERROR, "1"}}},
66 | };
67 |
68 | void Run() {
69 | std::cout << color::kYellow << "[Lexer] Running " << kData.size()
70 | << " tests...\n"
71 | << color::kReset;
72 | int num_failed = 0;
73 |
74 | for (const auto& test : kData) {
75 | Lexer lexer{std::istringstream{test.first}};
76 |
77 | bool failed = false;
78 | auto actual_token = lexer.NextToken();
79 | auto expected_token_iter = std::begin(test.second);
80 |
81 | for (; actual_token.category != Token::Category::MARKER_END &&
82 | expected_token_iter != std::end(test.second);
83 | actual_token = lexer.NextToken(), ++expected_token_iter) {
84 | if (actual_token != *expected_token_iter) {
85 | std::cout << color::kRed << "Test failed:" << color::kReset
86 | << "\n";
87 |
88 | std::cout << " Input program: " << test.first << "\n";
89 |
90 | std::cout << color::kGreen
91 | << " Expected token: " << color::kReset
92 | << expected_token_iter->DebugString() << ", "
93 | << color::kRed << "actual token: " << color::kReset
94 | << actual_token.DebugString() << "\n";
95 | failed = true;
96 | break;
97 | }
98 | }
99 |
100 | if (!failed && (actual_token.category != Token::Category::MARKER_END ||
101 | expected_token_iter != std::end(test.second))) {
102 | std::cout << "Test failed:\n Input program: " << test.first
103 | << "\n Unexpected number of tokens.\n";
104 | failed = true;
105 | }
106 |
107 | if (failed) {
108 | ++num_failed;
109 | }
110 | }
111 |
112 | std::cout << color::kYellow << "Results: " << color::kReset
113 | << (kData.size() - num_failed) << " out of " << kData.size()
114 | << " tests passed.\n";
115 | }
116 | } // namespace test
117 | } // namespace lexer
118 |
119 | namespace parser {
120 | namespace test {
121 |
122 | using namespace utils::test;
123 | using Category = lexer::Token::Category;
124 |
125 | struct TestData {
126 | std::string input_program_;
127 | // The absense of an expected AST means that: for the test being specified,
128 | // a parse error is expected.
129 | std::optional expected_ast_;
130 | };
131 |
132 | std::vector kData{
133 | TestData{"0", Term(Category::CONSTANT_ZERO)},
134 | TestData{"true", Term(Category::CONSTANT_TRUE)},
135 | TestData{"false", Term(Category::CONSTANT_FALSE)},
136 | TestData{"if false then true else 0",
137 | Term(Category::KEYWORD_IF, {Term(Category::CONSTANT_FALSE),
138 | Term(Category::CONSTANT_TRUE),
139 | Term(Category::CONSTANT_ZERO)})},
140 | TestData{"if false then true else false",
141 | Term(Category::KEYWORD_IF, {Term(Category::CONSTANT_FALSE),
142 | Term(Category::CONSTANT_TRUE),
143 | Term(Category::CONSTANT_FALSE)})},
144 | TestData{
145 | "if false then true else succ 0",
146 | Term(Category::KEYWORD_IF,
147 | {Term(Category::CONSTANT_FALSE), Term(Category::CONSTANT_TRUE),
148 | Term(Category::KEYWORD_SUCC, {Term(Category::CONSTANT_ZERO)})})},
149 | TestData{
150 | "if false then true else succ succ 0",
151 | Term(Category::KEYWORD_IF,
152 | {Term(Category::CONSTANT_FALSE), Term(Category::CONSTANT_TRUE),
153 | Term(Category::KEYWORD_SUCC,
154 | {Term(Category::KEYWORD_SUCC,
155 | {Term(Category::CONSTANT_ZERO)})})})},
156 | TestData{
157 | "if false then true else succ succ succ 0",
158 | Term(Category::KEYWORD_IF,
159 | {Term(Category::CONSTANT_FALSE), Term(Category::CONSTANT_TRUE),
160 | Term(Category::KEYWORD_SUCC,
161 | {Term(Category::KEYWORD_SUCC,
162 | {Term(Category::KEYWORD_SUCC,
163 | {Term(Category::CONSTANT_ZERO)})})})})},
164 | TestData{
165 | "if succ 0 then succ 0 else true",
166 | Term(Category::KEYWORD_IF,
167 | {Term(Category::KEYWORD_SUCC, {Term(Category::CONSTANT_ZERO)}),
168 | Term(Category::KEYWORD_SUCC, {Term(Category::CONSTANT_ZERO)}),
169 | Term(Category::CONSTANT_TRUE)})},
170 | TestData{"if true then false else true",
171 | Term(Category::KEYWORD_IF, {Term(Category::CONSTANT_TRUE),
172 | Term(Category::CONSTANT_FALSE),
173 | Term(Category::CONSTANT_TRUE)})},
174 | TestData{"if true then succ 0 else 0",
175 | Term(Category::KEYWORD_IF, {Term(Category::CONSTANT_TRUE),
176 | Term(Category::KEYWORD_SUCC,
177 | {Term(Category::CONSTANT_ZERO)}),
178 | Term(Category::CONSTANT_ZERO)})},
179 | TestData{"if true then succ 0 else true",
180 | Term(Category::KEYWORD_IF, {Term(Category::CONSTANT_TRUE),
181 | Term(Category::KEYWORD_SUCC,
182 | {Term(Category::CONSTANT_ZERO)}),
183 | Term(Category::CONSTANT_TRUE)})},
184 | TestData{
185 | "if true then true else succ 0",
186 | Term(Category::KEYWORD_IF,
187 | {Term(Category::CONSTANT_TRUE), Term(Category::CONSTANT_TRUE),
188 | Term(Category::KEYWORD_SUCC, {Term(Category::CONSTANT_ZERO)})})},
189 |
190 | TestData{
191 | "if if true then false else true then true else false",
192 | Term(Category::KEYWORD_IF,
193 | {Term(Category::KEYWORD_IF, {Term(Category::CONSTANT_TRUE),
194 | Term(Category::CONSTANT_FALSE),
195 | Term(Category::CONSTANT_TRUE)}),
196 | Term(Category::CONSTANT_TRUE), Term(Category::CONSTANT_FALSE)})},
197 | TestData{"iszero 0",
198 | Term(Category::KEYWORD_ISZERO, {Term(Category::CONSTANT_ZERO)})},
199 | TestData{"iszero pred succ succ 0",
200 | Term(Category::KEYWORD_ISZERO,
201 | {Term(Category::KEYWORD_PRED,
202 | {Term(Category::KEYWORD_SUCC,
203 | {Term(Category::KEYWORD_SUCC,
204 | {Term(Category::CONSTANT_ZERO)})})})})},
205 | TestData{"pred pred 0", Term(Category::KEYWORD_PRED,
206 | {Term(Category::KEYWORD_PRED,
207 | {Term(Category::CONSTANT_ZERO)})})},
208 | TestData{"pred pred if true then true else false",
209 | Term(Category::KEYWORD_PRED,
210 | {Term(Category::KEYWORD_PRED,
211 | {Term(Category::KEYWORD_IF,
212 | {Term(Category::CONSTANT_TRUE),
213 | Term(Category::CONSTANT_TRUE),
214 | Term(Category::CONSTANT_FALSE)})})})},
215 | TestData{"pred succ 0", Term(Category::KEYWORD_PRED,
216 | {Term(Category::KEYWORD_SUCC,
217 | {Term(Category::CONSTANT_ZERO)})})},
218 | TestData{"pred succ if true then true else false",
219 | Term(Category::KEYWORD_PRED,
220 | {Term(Category::KEYWORD_SUCC,
221 | {Term(Category::KEYWORD_IF,
222 | {Term(Category::CONSTANT_TRUE),
223 | Term(Category::CONSTANT_TRUE),
224 | Term(Category::CONSTANT_FALSE)})})})},
225 | TestData{"pred succ pred 0",
226 | Term(Category::KEYWORD_PRED,
227 | {Term(Category::KEYWORD_SUCC,
228 | {Term(Category::KEYWORD_PRED,
229 | {Term(Category::CONSTANT_ZERO)})})})},
230 | TestData{"pred succ pred succ 0",
231 | Term(Category::KEYWORD_PRED,
232 | {Term(Category::KEYWORD_SUCC,
233 | {Term(Category::KEYWORD_PRED,
234 | {Term(Category::KEYWORD_SUCC,
235 | {Term(Category::CONSTANT_ZERO)})})})})},
236 | TestData{
237 | "succ if true then true else false",
238 | Term(Category::KEYWORD_SUCC,
239 | {Term(Category::KEYWORD_IF, {Term(Category::CONSTANT_TRUE),
240 | Term(Category::CONSTANT_TRUE),
241 | Term(Category::CONSTANT_FALSE)})})},
242 | TestData{"succ succ true", Term(Category::KEYWORD_SUCC,
243 | {Term(Category::KEYWORD_SUCC,
244 | {Term(Category::CONSTANT_TRUE)})})},
245 |
246 | // Expected parse errors.
247 | TestData{"succ"},
248 | TestData{"if else false"},
249 | TestData{"if if true then false else true then true else"},
250 | TestData{"if if true then false else true then true else false if"},
251 | TestData{"if then else succ pred"},
252 | TestData{"if then else succ pred 0 true false"},
253 | TestData{"if then else succ pred 0 true false test"},
254 | TestData{"if true"},
255 | TestData{"if true else false"},
256 | TestData{"if true then succ 1 else true"},
257 | TestData{"pred"},
258 | TestData{"pred pred"},
259 | TestData{"pred succ"},
260 | TestData{"pred succ 1"},
261 | TestData{"pred succ if true then true false"},
262 | TestData{"succ"},
263 | TestData{"succ 1"},
264 | TestData{"succ pred 0 pred"},
265 | TestData{"succ pred 0 pred 0"},
266 | TestData{"succ pred 0 presd"},
267 | TestData{"succ succ 1"},
268 | };
269 |
270 | void Run() {
271 | std::cout << color::kYellow << "[Parser] Running " << kData.size()
272 | << " tests...\n"
273 | << color::kReset;
274 | int num_failed = 0;
275 |
276 | for (const auto& test : kData) {
277 | Parser parser{std::istringstream{test.input_program_}};
278 | Term res;
279 |
280 | try {
281 | res = parser.ParseProgram();
282 |
283 | if (*test.expected_ast_ != res) {
284 | std::cout << color::kRed << "Test failed:" << color::kReset
285 | << "\n";
286 |
287 | std::cout << " Input program: " << test.input_program_ << "\n";
288 |
289 | std::cout << color::kGreen
290 | << " Expected AST: " << color::kReset << "\n"
291 | << test.expected_ast_->ASTString(4) << "\n";
292 |
293 | std::cout << color::kRed << " Actual AST: " << color::kReset
294 | << "\n"
295 | << res.ASTString(4) << "\n";
296 |
297 | ++num_failed;
298 | }
299 | } catch (std::exception& ex) {
300 | if (test.expected_ast_) {
301 | // Unexpected parse error.
302 | std::cout << color::kRed << "Test failed:" << color::kReset
303 | << "\n";
304 |
305 | std::cout << " Input program: " << test.input_program_ << "\n";
306 |
307 | std::cout << color::kGreen
308 | << " Expected AST: " << color::kReset << "\n"
309 | << test.expected_ast_->ASTString(4) << "\n";
310 |
311 | std::cout << color::kRed << " Parsing failed." << color::kReset
312 | << "\n";
313 |
314 | ++num_failed;
315 | }
316 |
317 | continue;
318 | }
319 |
320 | // Unexpected parse success.
321 | if (!test.expected_ast_) {
322 | std::cout << color::kRed << "Test failed:" << color::kReset << "\n";
323 |
324 | std::cout << " Input program: " << test.input_program_ << "\n";
325 |
326 | std::cout << color::kGreen << " Expected parsing error"
327 | << color::kReset << "\n";
328 |
329 | std::cout << color::kRed << " Parsed AST: " << color::kReset
330 | << "\n"
331 | << res.ASTString(4) << "\n";
332 |
333 | ++num_failed;
334 | }
335 | }
336 |
337 | std::cout << color::kYellow << "Results: " << color::kReset
338 | << (kData.size() - num_failed) << " out of " << kData.size()
339 | << " tests passed.\n";
340 | }
341 |
342 | } // namespace test
343 | } // namespace parser
344 |
345 | namespace interpreter {
346 | namespace test {
347 |
348 | using namespace utils::test;
349 |
350 | struct TestData {
351 | std::string input_program_;
352 | std::string expected_eval_result_;
353 | };
354 |
355 | std::vector kData{
356 | TestData{"0", "0"},
357 | TestData{"true", "true"},
358 | TestData{"false", "false"},
359 | TestData{"if false then true else 0", "0"},
360 | TestData{"if false then true else false", "false"},
361 | TestData{"if false then true else succ 0", "1"},
362 | TestData{"if false then true else succ succ 0", "2"},
363 | TestData{"if false then true else succ succ succ 0", "3"},
364 | TestData{"if succ 0 then succ 0 else true", ""},
365 | TestData{"if true then false else true", "false"},
366 | TestData{"if true then succ 0 else 0", "1"},
367 | TestData{"if true then succ 0 else true", "1"},
368 | TestData{"if true then true else succ 0", "true"},
369 | TestData{"if if true then false else true then true else false", "false"},
370 | TestData{"iszero 0", "true"},
371 | TestData{"iszero pred succ succ 0", "false"},
372 | TestData{"pred pred 0", "0"},
373 | TestData{"pred pred if true then true else false", ""},
374 | TestData{"pred succ 0", "0"},
375 | TestData{"pred succ if true then true else false", ""},
376 | TestData{"pred succ pred 0", "0"},
377 | TestData{"pred succ pred succ 0", "0"},
378 | TestData{"succ if true then true else false", ""},
379 | TestData{"succ succ true", ""},
380 | };
381 |
382 | void Run() {
383 | std::cout << color::kYellow << "[Interpreter] Running " << kData.size()
384 | << " tests...\n"
385 | << color::kReset;
386 | int num_failed = 0;
387 |
388 | for (const auto& test : kData) {
389 | Interpreter interpreter{};
390 | std::string actual_eval_res;
391 |
392 | try {
393 | actual_eval_res = interpreter.Interpret(
394 | parser::Parser{std::istringstream{test.input_program_}}
395 | .ParseProgram());
396 | } catch (std::exception& ex) {
397 | std::cout << color::kRed << "Test failed:" << color::kReset << "\n";
398 |
399 | std::cout << " Input program: " << test.input_program_ << "\n";
400 |
401 | std::cout << color::kGreen
402 | << " Expected evaluation result: " << color::kReset
403 | << test.expected_eval_result_ << "\n";
404 |
405 | std::cout << color::kRed << " Parsing failed." << color::kReset
406 | << "\n";
407 |
408 | ++num_failed;
409 | continue;
410 | }
411 |
412 | if (actual_eval_res != test.expected_eval_result_) {
413 | std::cout << color::kRed << "Test failed:" << color::kReset << "\n";
414 |
415 | std::cout << " Input program: " << test.input_program_ << "\n";
416 |
417 | std::cout << color::kGreen
418 | << " Expected evaluation result: " << color::kReset
419 | << test.expected_eval_result_ << "\n";
420 |
421 | std::cout << color::kRed
422 | << " Actual evaluation result: " << color::kReset
423 | << actual_eval_res << "\n";
424 |
425 | ++num_failed;
426 | }
427 | }
428 |
429 | std::cout << color::kYellow << "Results: " << color::kReset
430 | << (kData.size() - num_failed) << " out of " << kData.size()
431 | << " tests passed.\n";
432 | }
433 | } // namespace test
434 | } // namespace interpreter
435 |
436 |
--------------------------------------------------------------------------------
/ch07_untyped/README.md:
--------------------------------------------------------------------------------
1 | # The Untyped Lmabda Calculus
2 | ## Notes
3 |
4 | - A more readable and easy-to-understand lexer was implemeneted in [Simple Typed Lambda Calculus](../ch10_simplebool).
5 |
6 | ## Syntax
7 |
8 | ### Terms
9 |
10 | ```
11 | t ::=
12 | x
13 | l x. t
14 | t t
15 | ```
16 |
17 | ### Values
18 |
19 | ```
20 | v ::=
21 | x
22 | l x. t
23 | ```
24 |
25 | ## Evaluation Rules
26 |
27 | TODO
28 |
--------------------------------------------------------------------------------
/ch07_untyped/interpreter.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "interpreter.hpp"
4 |
5 | int main(int argc, char* argv[]) {
6 | if (argc < 2) {
7 | std::cerr
8 | << "Error: expected input program as a command line argument.\n";
9 | return 1;
10 | }
11 |
12 | parser::Parser parser{std::istringstream{argv[1]}};
13 | auto program = parser.ParseProgram();
14 |
15 | std::cout << " " << program << "\n";
16 |
17 | interpreter::Interpreter interpreter;
18 | interpreter.Interpret(program);
19 |
20 | std::cout << "=> " << program << "\n";
21 |
22 | return 0;
23 | }
24 |
--------------------------------------------------------------------------------
/ch07_untyped/interpreter.hpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | namespace lexer {
9 | class Token {
10 | public:
11 | enum class Category {
12 | VARIABLE,
13 | LAMBDA,
14 | LAMBDA_DOT,
15 | OPEN_PAREN,
16 | CLOSE_PAREN,
17 | MARKER_END,
18 | MARKER_INVALID
19 | };
20 |
21 | Token(Category category = Category::MARKER_INVALID, std::string text = "")
22 | : category_(category),
23 | text_(category == Category::VARIABLE ? text : "") {}
24 |
25 | bool operator==(const Token& other) const {
26 | return category_ == other.category_ && text_ == other.text_;
27 | }
28 |
29 | bool operator!=(const Token& other) const { return !(*this == other); }
30 |
31 | Category GetCategory() const { return category_; }
32 |
33 | std::string GetText() const { return text_; }
34 |
35 | private:
36 | Category category_ = Category::MARKER_INVALID;
37 | std::string text_;
38 | };
39 |
40 | std::ostream& operator<<(std::ostream& out, Token token);
41 |
42 | class Lexer {
43 | static const std::string kLambdaInputSymbol;
44 |
45 | public:
46 | Lexer(std::istringstream&& in) : in_(std::move(in)) {}
47 |
48 | // TODO I think this can be significantly simplified by:
49 | //
50 | // 1. Adding a pre-processing step that inserts a space before and after
51 | // separators (., (, and )).
52 | //
53 | // 2. Separating the code of reading the next token from the input character
54 | // buffer from the code of managing the output token buffer.
55 | //
56 | // NOTE The idea described above was implemented in "Simply Typed Lambda
57 | // Calculus" (ch10_simplebool).
58 | Token NextToken() {
59 | if (is_cached_token_valid) {
60 | is_cached_token_valid = false;
61 | return cached_token;
62 | }
63 |
64 | Token token;
65 |
66 | char next_char = 0;
67 | std::ostringstream token_text_out;
68 | while (in_.get(next_char) && !IsSeparator(next_char)) {
69 | token_text_out << next_char;
70 | }
71 |
72 | Token::Category token_category;
73 | auto token_text = token_text_out.str();
74 |
75 | if (token_text == kLambdaInputSymbol) {
76 | token = Token(Token::Category::LAMBDA);
77 | } else if (IsVariableName(token_text)) {
78 | token = Token(Token::Category::VARIABLE, token_text);
79 | } else if (next_char == '(') {
80 | // There is no token before next_char, then create a token of it and
81 | // clear next_char.
82 | token = Token(Token::Category::OPEN_PAREN);
83 | next_char = 0;
84 | } else if (next_char == ')') {
85 | // There is no token before next_char, then create a token of it and
86 | // clear next_char.
87 | token = Token(Token::Category::CLOSE_PAREN);
88 | next_char = 0;
89 | } else if (next_char == '.') {
90 | // There is no token before next_char, then create a token of it and
91 | // clear next_char.
92 | token = Token(Token::Category::LAMBDA_DOT);
93 | next_char = 0;
94 | } else if (!token_text.empty()) {
95 | token = Token(Token::Category::MARKER_INVALID);
96 | } else if (!in_) {
97 | token = Token(Token::Category::MARKER_END);
98 | } else {
99 | // Must be whitespace, eat it.
100 | token = NextToken();
101 | }
102 |
103 | // There is a token before next_char, then return that token and cache
104 | // next_char's token for next call to NextToken().
105 | if (next_char == '.') {
106 | is_cached_token_valid = true;
107 | cached_token = Token(Token::Category::LAMBDA_DOT);
108 | } else if (next_char == '(') {
109 | is_cached_token_valid = true;
110 | cached_token = Token(Token::Category::OPEN_PAREN);
111 | } else if (next_char == ')') {
112 | is_cached_token_valid = true;
113 | cached_token = Token(Token::Category::CLOSE_PAREN);
114 | }
115 |
116 | return token;
117 | }
118 |
119 | private:
120 | bool IsSeparator(char c) const {
121 | return std::string{" .()"}.find(c) != std::string::npos;
122 | }
123 |
124 | bool IsVariableName(const std::string& text) const {
125 | if (text.empty()) {
126 | return false;
127 | }
128 |
129 | for (char c : text) {
130 | if (!std::islower(c) && !std::isupper(c) && (c != '_')) {
131 | return false;
132 | }
133 | }
134 |
135 | return true;
136 | }
137 |
138 | private:
139 | std::istringstream in_;
140 | bool is_cached_token_valid = false;
141 | Token cached_token;
142 | };
143 |
144 | const std::string Lexer::kLambdaInputSymbol = "l";
145 |
146 | std::ostream& operator<<(std::ostream& out, Token token) {
147 | switch (token.GetCategory()) {
148 | case Token::Category::LAMBDA:
149 | out << "lambda";
150 | break;
151 | case Token::Category::VARIABLE:
152 | out << token.GetText();
153 | break;
154 | case Token::Category::LAMBDA_DOT:
155 | out << ".";
156 | break;
157 | case Token::Category::OPEN_PAREN:
158 | out << "(";
159 | break;
160 | case Token::Category::CLOSE_PAREN:
161 | out << ")";
162 | break;
163 | case Token::Category::MARKER_END:
164 | out << "";
165 | break;
166 |
167 | case Token::Category::MARKER_INVALID:
168 | out << "";
169 | break;
170 |
171 | default:
172 | out << "";
173 | }
174 |
175 | return out;
176 | }
177 | } // namespace lexer
178 |
179 | namespace parser {
180 |
181 | class Term {
182 | friend std::ostream& operator<<(std::ostream&, const Term&);
183 |
184 | public:
185 | static Term Lambda(std::string arg_name) {
186 | Term result;
187 | result.lambda_arg_name_ = arg_name;
188 | result.is_lambda_ = true;
189 |
190 | return result;
191 | }
192 |
193 | static Term Variable(std::string var_name, int de_bruijn_idx) {
194 | Term result;
195 | result.variable_name_ = var_name;
196 | result.de_bruijn_idx_ = de_bruijn_idx;
197 | result.is_variable_ = true;
198 |
199 | return result;
200 | }
201 |
202 | static Term Application(std::unique_ptr lhs,
203 | std::unique_ptr rhs) {
204 | Term result;
205 | result.is_application_ = true;
206 | result.application_lhs_ = std::move(lhs);
207 | result.application_rhs_ = std::move(rhs);
208 |
209 | return result;
210 | }
211 |
212 | Term() = default;
213 |
214 | Term(const Term&) = delete;
215 | Term& operator=(const Term&) = delete;
216 |
217 | Term(Term&&) = default;
218 | Term& operator=(Term&&) = default;
219 |
220 | ~Term() = default;
221 |
222 | bool IsLambda() const { return is_lambda_; }
223 |
224 | void MarkLambdaAsComplete() { is_complete_lambda_ = true; }
225 |
226 | bool IsVariable() const { return is_variable_; }
227 |
228 | bool IsApplication() const { return is_application_; }
229 |
230 | bool IsInvalid() const {
231 | if (IsLambda()) {
232 | return lambda_arg_name_.empty() || !lambda_body_;
233 | } else if (IsVariable()) {
234 | return variable_name_.empty();
235 | } else if (IsApplication()) {
236 | return !application_lhs_ || !application_rhs_;
237 | }
238 |
239 | return true;
240 | }
241 |
242 | bool IsEmpty() const {
243 | return !IsLambda() && !IsVariable() && !IsApplication();
244 | }
245 |
246 | Term& Combine(Term&& term) {
247 | if (term.IsInvalid()) {
248 | throw std::invalid_argument(
249 | "Term::Combine() received an invalid Term.");
250 | }
251 |
252 | if (IsLambda()) {
253 | if (lambda_body_) {
254 | // If the lambda body was completely parsed, then combining this
255 | // term and the argument term means applying this lambda to the
256 | // argument.
257 | if (is_complete_lambda_) {
258 | *this =
259 | Application(std::make_unique(std::move(*this)),
260 | std::make_unique(std::move(term)));
261 |
262 | is_lambda_ = false;
263 | lambda_body_ = nullptr;
264 | lambda_arg_name_ = "";
265 | is_complete_lambda_ = false;
266 | } else {
267 | lambda_body_->Combine(std::move(term));
268 | }
269 | } else {
270 | lambda_body_ = std::make_unique(std::move(term));
271 | }
272 | } else if (IsVariable()) {
273 | *this = Application(std::make_unique(std::move(*this)),
274 | std::make_unique(std::move(term)));
275 |
276 | is_variable_ = false;
277 | variable_name_ = "";
278 | } else if (IsApplication()) {
279 | *this = Application(std::make_unique(std::move(*this)),
280 | std::make_unique(std::move(term)));
281 | } else {
282 | *this = std::move(term);
283 | }
284 |
285 | return *this;
286 | }
287 |
288 | /*
289 | * Shifts the de Bruijn indices of all free variables inside this Term up by
290 | * distance amount. For an example use, see Term::Substitute(int, Term&).
291 | */
292 | void Shift(int distance) {
293 | std::function walk = [&distance, &walk](
294 | int binding_context_size,
295 | Term& term) {
296 | if (term.IsVariable()) {
297 | if (term.de_bruijn_idx_ >= binding_context_size) {
298 | term.de_bruijn_idx_ += distance;
299 | }
300 | } else if (term.IsLambda()) {
301 | walk(binding_context_size + 1, *term.lambda_body_);
302 | } else if (term.IsApplication()) {
303 | walk(binding_context_size, *term.application_lhs_);
304 | walk(binding_context_size, *term.application_rhs_);
305 | } else {
306 | throw std::invalid_argument("Trying to shift an invalid term.");
307 | }
308 | };
309 |
310 | walk(0, *this);
311 | }
312 |
313 | /**
314 | * Substitutes variable (that is, the de Brijun idex of a variable) with the
315 | * term sub.
316 | */
317 | void Substitute(int variable, Term& sub) {
318 | if (IsInvalid() || sub.IsInvalid()) {
319 | throw std::invalid_argument(
320 | "Trying to substitute using invalid terms.");
321 | }
322 |
323 | std::function walk = [&variable, &sub, &walk](
324 | int binding_context_size,
325 | Term& term) {
326 | if (term.IsVariable()) {
327 | // Adjust variable according to the current binding
328 | // depth before comparing term's index.
329 | if (term.de_bruijn_idx_ == variable + binding_context_size) {
330 | // Shift sub up by binding_context_size distance since sub
331 | // is now substituted in binding_context_size deep context.
332 | auto clone = sub.Clone();
333 | clone.Shift(binding_context_size);
334 | std::swap(term, clone);
335 | }
336 | } else if (term.IsLambda()) {
337 | walk(binding_context_size + 1, *term.lambda_body_);
338 | } else if (term.IsApplication()) {
339 | walk(binding_context_size, *term.application_lhs_);
340 | walk(binding_context_size, *term.application_rhs_);
341 | }
342 | };
343 |
344 | walk(0, *this);
345 | }
346 |
347 | Term& LambdaBody() const {
348 | if (!IsLambda()) {
349 | throw std::invalid_argument("Invalid Lambda term.");
350 | }
351 |
352 | return *lambda_body_;
353 | }
354 |
355 | Term& ApplicationLHS() const {
356 | if (!IsApplication()) {
357 | throw std::invalid_argument("Invalide application term.");
358 | }
359 |
360 | return *application_lhs_;
361 | }
362 |
363 | Term& ApplicationRHS() const {
364 | if (!IsApplication()) {
365 | throw std::invalid_argument("Invalide application term.");
366 | }
367 |
368 | return *application_rhs_;
369 | }
370 |
371 | bool operator==(const Term& other) const {
372 | if (IsLambda() && other.IsLambda()) {
373 | return LambdaBody() == other.LambdaBody();
374 | }
375 |
376 | if (IsVariable() && other.IsVariable()) {
377 | return de_bruijn_idx_ == other.de_bruijn_idx_;
378 | }
379 |
380 | if (IsApplication() && other.IsApplication()) {
381 | return (ApplicationLHS() == other.ApplicationLHS()) &&
382 | (ApplicationRHS() == other.ApplicationRHS());
383 | }
384 |
385 | return false;
386 | }
387 |
388 | bool operator!=(const Term& other) const { return !(*this == other); }
389 |
390 | std::string ASTString(int indentation = 0) const {
391 | std::ostringstream out;
392 | std::string prefix = std::string(indentation, ' ');
393 |
394 | if (IsLambda()) {
395 | out << prefix << "λ " << lambda_arg_name_ << "\n";
396 | out << lambda_body_->ASTString(indentation + 2);
397 | } else if (IsVariable()) {
398 | out << prefix << variable_name_ << "[" << de_bruijn_idx_ << "]";
399 | } else if (IsApplication()) {
400 | out << prefix << "<-\n";
401 | out << application_lhs_->ASTString(indentation + 2) << "\n";
402 | out << application_rhs_->ASTString(indentation + 2);
403 | }
404 |
405 | return out.str();
406 | }
407 |
408 | Term Clone() const {
409 | if (IsInvalid()) {
410 | throw std::logic_error("Trying to clone an invalid term.");
411 | }
412 |
413 | if (IsLambda()) {
414 | return std::move(
415 | Lambda(lambda_arg_name_).Combine(lambda_body_->Clone()));
416 | } else if (IsVariable()) {
417 | return Variable(variable_name_, de_bruijn_idx_);
418 | } else if (IsApplication()) {
419 | return Application(
420 | std::make_unique(application_lhs_->Clone()),
421 | std::make_unique(application_rhs_->Clone()));
422 | }
423 |
424 | std::ostringstream error_ss;
425 | error_ss << "Couldn't clone term: " << *this;
426 | throw std::logic_error(error_ss.str());
427 | }
428 |
429 | private:
430 | bool is_lambda_ = false;
431 | std::string lambda_arg_name_ = "";
432 | std::unique_ptr lambda_body_{};
433 | // Marks whether parsing for the body of the lambda term is finished or not.
434 | bool is_complete_lambda_ = false;
435 |
436 | bool is_variable_ = false;
437 | std::string variable_name_ = "";
438 | int de_bruijn_idx_ = -1;
439 |
440 | bool is_application_ = false;
441 | std::unique_ptr application_lhs_{};
442 | std::unique_ptr application_rhs_{};
443 | };
444 |
445 | std::ostream& operator<<(std::ostream& out, const Term& term) {
446 | if (term.IsInvalid()) {
447 | out << "";
448 | } else if (term.IsVariable()) {
449 | out << "[" << term.variable_name_ << "=" << term.de_bruijn_idx_ << "]";
450 | } else if (term.IsLambda()) {
451 | out << "{λ " << term.lambda_arg_name_ << ". " << *term.lambda_body_
452 | << "}";
453 | } else if (term.IsApplication()) {
454 | out << "(" << *term.application_lhs_ << " <- " << *term.application_rhs_
455 | << ")";
456 | } else {
457 | out << "";
458 | }
459 |
460 | return out;
461 | }
462 |
463 | class Parser {
464 | using Token = lexer::Token;
465 |
466 | public:
467 | Parser(std::istringstream&& in) : lexer_(std::move(in)) {}
468 |
469 | Term ParseProgram() {
470 | Token next_token;
471 | std::vector term_stack;
472 | term_stack.emplace_back(Term());
473 | int balance_parens = 0;
474 | // For each '(', records the size of term_stack when the '(' was parsed.
475 | // This is used later when the corresponding ')' is parsed to know how
476 | // many Terms from term_stack should be popped (i.e. their parsing is
477 | // know to be complete).
478 | std::vector stack_size_on_open_paren;
479 | // Contains a list of bound variables in order of binding. For example,
480 | // for a term λ x. λ y. x y, this list would eventually contains {"x" ,
481 | // "y"} in that order. This is used to assign de Bruijn indices/static
482 | // distances to bound variables (ref: tapl,§6.1).
483 | std::vector bound_variables;
484 |
485 | while ((next_token = lexer_.NextToken()).GetCategory() !=
486 | Token::Category::MARKER_END) {
487 | if (next_token.GetCategory() == Token::Category::LAMBDA) {
488 | auto lambda_arg = ParseVariable();
489 | bound_variables.push_back(lambda_arg.GetText());
490 | ParseDot();
491 |
492 | // If the current stack top is empty, use its slot for the
493 | // lambda.
494 | if (term_stack.back().IsEmpty()) {
495 | term_stack.back() = Term::Lambda(lambda_arg.GetText());
496 | } else {
497 | // Else, push a new term on the stack to start building the
498 | // lambda term.
499 | term_stack.emplace_back(Term::Lambda(lambda_arg.GetText()));
500 | }
501 | } else if (next_token.GetCategory() == Token::Category::VARIABLE) {
502 | auto bound_variable_it =
503 | std::find(std::begin(bound_variables),
504 | std::end(bound_variables), next_token.GetText());
505 | int de_bruijn_idx = -1;
506 |
507 | if (bound_variable_it != std::end(bound_variables)) {
508 | de_bruijn_idx = std::distance(bound_variable_it,
509 | std::end(bound_variables)) -
510 | 1;
511 | } else {
512 | // The naming context for free variables (ref: tapl,§6.1.2)
513 | // is chosen to be the ASCII code of a variable's name.
514 | //
515 | // NOTE: Only single-character variable names are currecntly
516 | // supported as free variables.
517 | if (next_token.GetText().length() != 1) {
518 | std::ostringstream error_ss;
519 | error_ss << "Unexpected token: " << next_token;
520 | throw std::invalid_argument(error_ss.str());
521 | }
522 |
523 | de_bruijn_idx =
524 | bound_variables.size() +
525 | (std::tolower(next_token.GetText()[0]) - 'a');
526 | }
527 |
528 | term_stack.back().Combine(
529 | Term::Variable(next_token.GetText(), de_bruijn_idx));
530 | } else if (next_token.GetCategory() ==
531 | Token::Category::OPEN_PAREN) {
532 | stack_size_on_open_paren.emplace_back(term_stack.size());
533 | term_stack.emplace_back(Term());
534 | ++balance_parens;
535 | } else if (next_token.GetCategory() ==
536 | Token::Category::CLOSE_PAREN) {
537 | while (!term_stack.empty() &&
538 | !stack_size_on_open_paren.empty() &&
539 | term_stack.size() > stack_size_on_open_paren.back()) {
540 | if (term_stack.back().IsLambda()) {
541 | // Mark the λ as complete so that terms to its right
542 | // won't be combined to its body.
543 | term_stack.back().MarkLambdaAsComplete();
544 | // λ's variable is no longer part of the current binding
545 | // context, therefore pop it.
546 | bound_variables.pop_back();
547 | }
548 |
549 | CombineStackTop(term_stack);
550 | }
551 |
552 | --balance_parens;
553 |
554 | if (!stack_size_on_open_paren.empty()) {
555 | stack_size_on_open_paren.pop_back();
556 | }
557 | } else {
558 | std::ostringstream error_ss;
559 | error_ss << "Unexpected token: " << next_token;
560 | throw std::invalid_argument(error_ss.str());
561 | }
562 | }
563 |
564 | if (balance_parens != 0) {
565 | throw std::invalid_argument(
566 | "Invalid term: probably because a ( is not matched by a )");
567 | }
568 |
569 | while (term_stack.size() > 1) {
570 | CombineStackTop(term_stack);
571 | }
572 |
573 | if (term_stack.back().IsInvalid()) {
574 | throw std::invalid_argument("Invalid term.");
575 | }
576 |
577 | return std::move(term_stack.back());
578 | }
579 |
580 | void CombineStackTop(std::vector& term_stack) {
581 | if (term_stack.size() < 2) {
582 | throw std::invalid_argument(
583 | "Invalid term: probably because a ( is not matched by a )");
584 | }
585 |
586 | Term top = std::move(term_stack.back());
587 | term_stack.pop_back();
588 | term_stack.back().Combine(std::move(top));
589 | }
590 |
591 | Token ParseVariable() {
592 | auto token = lexer_.NextToken();
593 |
594 | return (token.GetCategory() == Token::Category::VARIABLE)
595 | ? token
596 | : throw std::logic_error("Expected to parse a variable.");
597 | }
598 |
599 | Token ParseDot() {
600 | auto token = lexer_.NextToken();
601 |
602 | return (token.GetCategory() == Token::Category::LAMBDA_DOT)
603 | ? token
604 | : throw std::logic_error("Expected to parse a dot.");
605 | }
606 |
607 | private:
608 | lexer::Lexer lexer_;
609 | };
610 | } // namespace parser
611 |
612 | namespace interpreter {
613 | class Interpreter {
614 | using Term = parser::Term;
615 |
616 | public:
617 | void Interpret(Term& program) { Eval(program); }
618 |
619 | void Eval(Term& term) {
620 | try {
621 | Eval1(term);
622 | Eval(term);
623 | } catch (std::invalid_argument&) {
624 | }
625 | }
626 |
627 | void Eval1(Term& term) {
628 | auto term_subst_top = [](Term& s, Term& t) {
629 | // Adjust the free variables in s by increasing their static
630 | // distances by 1. That's because s will now be embedded one level
631 | // deeper in t (i.e. t's bound variable will be replaced by s).
632 | s.Shift(1);
633 | t.Substitute(0, s);
634 | // Because of the substitution, one level of abstraction was peeled
635 | // off. Account for that by decreasing the static distances of the
636 | // free variables in t by 1.
637 | t.Shift(-1);
638 | // NOTE: For more details see: tapl,§6.3.
639 | };
640 |
641 | if (term.IsApplication() && term.ApplicationLHS().IsLambda() &&
642 | IsValue(term.ApplicationRHS())) {
643 | term_subst_top(term.ApplicationRHS(),
644 | term.ApplicationLHS().LambdaBody());
645 | std::swap(term, term.ApplicationLHS().LambdaBody());
646 | } else if (term.IsApplication() && IsValue(term.ApplicationLHS())) {
647 | Eval1(term.ApplicationRHS());
648 | } else if (term.IsApplication()) {
649 | Eval1(term.ApplicationLHS());
650 | } else {
651 | throw std::invalid_argument("No applicable rule.");
652 | }
653 | }
654 |
655 | bool IsValue(const Term& term) {
656 | return term.IsLambda() || term.IsVariable();
657 | }
658 | };
659 | } // namespace interpreter
660 |
--------------------------------------------------------------------------------
/ch07_untyped/test.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "interpreter.hpp"
5 |
6 | namespace lexer {
7 | namespace test {
8 | void Run();
9 | }
10 | } // namespace lexer
11 |
12 | namespace parser {
13 | namespace test {
14 | void Run();
15 | }
16 | } // namespace parser
17 |
18 | namespace interpreter {
19 | namespace test {
20 | void Run();
21 | }
22 | } // namespace interpreter
23 |
24 | int main() {
25 | lexer::test::Run();
26 | parser::test::Run();
27 | interpreter::test::Run();
28 |
29 | return 0;
30 | }
31 |
32 | namespace utils {
33 | namespace test {
34 | namespace color {
35 |
36 | std::string kRed{"\033[1;31m"};
37 | std::string kGreen{"\033[1;32m"};
38 | std::string kYellow{"\033[1;33m"};
39 | std::string kReset{"\033[0m"};
40 |
41 | } // namespace color
42 | } // namespace test
43 | } // namespace utils
44 |
45 | namespace lexer {
46 | namespace test {
47 |
48 | using Category = Token::Category;
49 | using TestData = std::pair>;
50 | using namespace utils::test;
51 |
52 | std::vector kData = {
53 | // Valid tokens (non-variables):
54 | TestData{"l . ( )",
55 | {Token{Category::LAMBDA}, Token{Category::LAMBDA_DOT},
56 | Token{Category::OPEN_PAREN}, Token{Category::CLOSE_PAREN}}},
57 | // Valid tokens (variables):
58 | TestData{"x y L test _",
59 | {Token{Category::VARIABLE, "x"}, Token{Category::VARIABLE, "y"},
60 | Token{Category::VARIABLE, "L"}, Token{Category::VARIABLE, "test"},
61 | Token{Category::VARIABLE, "_"}}},
62 | // Invalid single-character tokens:
63 | TestData{"! @ # $ % ^ & * - + = ? / < > ' \" \\ | [ ] { }",
64 | {Token{Category::MARKER_INVALID}, Token{Category::MARKER_INVALID},
65 | Token{Category::MARKER_INVALID}, Token{Category::MARKER_INVALID},
66 | Token{Category::MARKER_INVALID}, Token{Category::MARKER_INVALID},
67 | Token{Category::MARKER_INVALID}, Token{Category::MARKER_INVALID},
68 | Token{Category::MARKER_INVALID}, Token{Category::MARKER_INVALID},
69 | Token{Category::MARKER_INVALID}, Token{Category::MARKER_INVALID},
70 | Token{Category::MARKER_INVALID}, Token{Category::MARKER_INVALID},
71 | Token{Category::MARKER_INVALID}, Token{Category::MARKER_INVALID},
72 | Token{Category::MARKER_INVALID}, Token{Category::MARKER_INVALID},
73 | Token{Category::MARKER_INVALID}, Token{Category::MARKER_INVALID},
74 | Token{Category::MARKER_INVALID}, Token{Category::MARKER_INVALID},
75 | Token{Category::MARKER_INVALID}}},
76 |
77 | TestData{
78 | "!@ x*",
79 | {Token{Category::MARKER_INVALID}, Token{Category::MARKER_INVALID}}},
80 | }; // namespace test
81 |
82 | void Run() {
83 | std::cout << color::kYellow << "[Lexer] Running " << kData.size()
84 | << " tests...\n"
85 | << color::kReset;
86 | int num_failed = 0;
87 |
88 | for (const auto& test : kData) {
89 | Lexer lexer{std::istringstream{test.first}};
90 |
91 | bool failed = false;
92 | auto actual_token = lexer.NextToken();
93 | auto expected_token_iter = std::begin(test.second);
94 |
95 | for (; actual_token.GetCategory() != Token::Category::MARKER_END &&
96 | expected_token_iter != std::end(test.second);
97 | actual_token = lexer.NextToken(), ++expected_token_iter) {
98 | if (actual_token != *expected_token_iter) {
99 | std::cout << color::kRed << "Test failed:" << color::kReset
100 | << "\n";
101 |
102 | std::cout << " Input program: " << test.first << "\n";
103 |
104 | std::cout << color::kGreen
105 | << " Expected token: " << color::kReset
106 | << *expected_token_iter << ", " << color::kRed
107 | << "actual token: " << color::kReset << actual_token
108 | << "\n";
109 | failed = true;
110 | break;
111 | }
112 | }
113 |
114 | if (!failed &&
115 | (actual_token.GetCategory() != Token::Category::MARKER_END ||
116 | expected_token_iter != std::end(test.second))) {
117 | std::cout << "Test failed:\n Input program: " << test.first
118 | << "\n Unexpected number of tokens.\n";
119 | failed = true;
120 | }
121 |
122 | if (failed) {
123 | ++num_failed;
124 | }
125 | }
126 |
127 | std::cout << color::kYellow << "Results: " << color::kReset
128 | << (kData.size() - num_failed) << " out of " << kData.size()
129 | << " tests passed.\n";
130 | }
131 | } // namespace test
132 | } // namespace lexer
133 |
134 | namespace {
135 | using namespace parser;
136 |
137 | std::unique_ptr VariableUP(std::string name, int de_bruijn_idx) {
138 | return std::make_unique(Term::Variable(name, de_bruijn_idx));
139 | }
140 |
141 | std::unique_ptr ApplicationUP(std::unique_ptr lhs,
142 | std::unique_ptr rhs) {
143 | return std::make_unique(
144 | Term::Application(std::move(lhs), std::move(rhs)));
145 | }
146 |
147 | Term Lambda(std::string arg_name, Term&& body) {
148 | return std::move(Term::Lambda(arg_name).Combine(std::move(body)));
149 | }
150 |
151 | std::unique_ptr LambdaUP(std::string arg_name, Term&& body) {
152 | return std::make_unique(Lambda(arg_name, std::move(body)));
153 | }
154 |
155 | } // namespace
156 |
157 | namespace parser {
158 | namespace test {
159 |
160 | using namespace utils::test;
161 | using Category = lexer::Token::Category;
162 |
163 | struct TestData {
164 | std::string input_program_;
165 | // The absense of an expected AST means that: for the test being specified,
166 | // a parse error is expected.
167 | std::optional expected_ast_;
168 | };
169 |
170 | std::vector kData{};
171 |
172 | void InitData() {
173 | kData.emplace_back(TestData{"x", Term::Variable("x", 23)});
174 |
175 | kData.emplace_back(TestData{
176 | "x y", Term::Application(VariableUP("x", 23), VariableUP("y", 24))});
177 |
178 | kData.emplace_back(TestData{
179 | "(x y)", Term::Application(VariableUP("x", 23), VariableUP("y", 24))});
180 |
181 | kData.emplace_back(
182 | TestData{"((x y))",
183 | Term::Application(VariableUP("x", 23), VariableUP("y", 24))});
184 |
185 | kData.emplace_back(TestData{
186 | "x y x", Term::Application(
187 | ApplicationUP(VariableUP("x", 23), VariableUP("y", 24)),
188 | VariableUP("x", 23))});
189 |
190 | kData.emplace_back(TestData{
191 | "(x y) x", Term::Application(
192 | ApplicationUP(VariableUP("x", 23), VariableUP("y", 24)),
193 | VariableUP("x", 23))});
194 |
195 | kData.emplace_back(TestData{
196 | "((x y) x)", Term::Application(ApplicationUP(VariableUP("x", 23),
197 | VariableUP("y", 24)),
198 | VariableUP("x", 23))});
199 |
200 | kData.emplace_back(TestData{
201 | "((x y)) (z)", Term::Application(ApplicationUP(VariableUP("x", 23),
202 | VariableUP("y", 24)),
203 | VariableUP("z", 25))});
204 |
205 | kData.emplace_back(TestData{
206 | "((x y)) z", Term::Application(ApplicationUP(VariableUP("x", 23),
207 | VariableUP("y", 24)),
208 | VariableUP("z", 25))});
209 |
210 | kData.emplace_back(TestData{
211 | "((x y) z)", Term::Application(ApplicationUP(VariableUP("x", 23),
212 | VariableUP("y", 24)),
213 | VariableUP("z", 25))});
214 |
215 | kData.emplace_back(TestData{
216 | "(l x. x a)", Lambda("x", Term::Application(VariableUP("x", 0),
217 | VariableUP("a", 1)))});
218 |
219 | kData.emplace_back(TestData{
220 | "(l x. x y l y. y l z. z)",
221 | Lambda(
222 | "x",
223 | Term::Application(
224 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
225 | LambdaUP("y", Term::Application(
226 | VariableUP("y", 0),
227 | LambdaUP("z", Term::Variable("z", 0))))))});
228 |
229 | kData.emplace_back(
230 | TestData{"(l x. x) (l y. y)",
231 | Term::Application(LambdaUP("x", Term::Variable("x", 0)),
232 | LambdaUP("y", Term::Variable("y", 0)))});
233 |
234 | kData.emplace_back(
235 | TestData{"(l x. x) l y. y",
236 | Term::Application(LambdaUP("x", Term::Variable("x", 0)),
237 | LambdaUP("y", Term::Variable("y", 0)))});
238 |
239 | kData.emplace_back(TestData{
240 | "(l x. x) (l y. y) l z. z",
241 | Term::Application(ApplicationUP(LambdaUP("x", Term::Variable("x", 0)),
242 | LambdaUP("y", Term::Variable("y", 0))),
243 | LambdaUP("z", Term::Variable("z", 0)))});
244 |
245 | kData.emplace_back(TestData{
246 | "(l x. x) l y. y l z. z",
247 | Term::Application(
248 | LambdaUP("x", Term::Variable("x", 0)),
249 | LambdaUP("y", Term::Application(
250 | VariableUP("y", 0),
251 | LambdaUP("z", Term::Variable("z", 0)))))});
252 |
253 | kData.emplace_back(
254 | TestData{"(l x. x) l y. y a",
255 | Term::Application(
256 | LambdaUP("x", Term::Variable("x", 0)),
257 | LambdaUP("y", Term::Application(VariableUP("y", 0),
258 | VariableUP("a", 1))))});
259 |
260 | kData.emplace_back(
261 | TestData{"(l x. x) l y. y x",
262 | Term::Application(
263 | LambdaUP("x", Term::Variable("x", 0)),
264 | LambdaUP("y", Term::Application(VariableUP("y", 0),
265 | VariableUP("x", 24))))});
266 |
267 | kData.emplace_back(
268 | TestData{"(l x. x) l y. y z",
269 | Term::Application(
270 | LambdaUP("x", Term::Variable("x", 0)),
271 | LambdaUP("y", Term::Application(VariableUP("y", 0),
272 | VariableUP("x", 26))))});
273 |
274 | kData.emplace_back(TestData{
275 | "(l x. x) x", Term::Application(LambdaUP("x", Term::Variable("x", 0)),
276 | VariableUP("x", 23))});
277 |
278 | kData.emplace_back(TestData{
279 | "(l x. x) y", Term::Application(LambdaUP("x", Term::Variable("x", 0)),
280 | VariableUP("y", 24))});
281 |
282 | kData.emplace_back(
283 | TestData{"(x l y. y)",
284 | Term::Application(VariableUP("x", 23),
285 | LambdaUP("y", Term::Variable("y", 0)))});
286 |
287 | kData.emplace_back(TestData{
288 | "(x y)", Term::Application(VariableUP("x", 23), VariableUP("y", 24))});
289 |
290 | kData.emplace_back(TestData{
291 | "(x y) x", Term::Application(
292 | ApplicationUP(VariableUP("x", 23), VariableUP("y", 24)),
293 | VariableUP("x", 23))});
294 |
295 | kData.emplace_back(TestData{
296 | "(x y) z", Term::Application(
297 | ApplicationUP(VariableUP("x", 23), VariableUP("y", 24)),
298 | VariableUP("z", 25))});
299 |
300 | kData.emplace_back(TestData{"(x)", Term::Variable("x", 23)});
301 |
302 | kData.emplace_back(TestData{
303 | "l x . (l y.((x y) x))",
304 | Lambda("x",
305 | Lambda("y", Term::Application(ApplicationUP(VariableUP("x", 1),
306 | VariableUP("y", 0)),
307 | VariableUP("x", 1))))});
308 |
309 | kData.emplace_back(TestData{
310 | "l x. (l y. (y x))",
311 | Lambda("x", Lambda("y", Term::Application(VariableUP("y", 0),
312 | VariableUP("x", 1))))});
313 |
314 | kData.emplace_back(TestData{
315 | "l x. (x y)", Lambda("x", Term::Application(VariableUP("x", 0),
316 | VariableUP("y", 25)))});
317 |
318 | kData.emplace_back(
319 | TestData{"l x. (x)", Lambda("x", Term::Variable("x", 0))});
320 |
321 | kData.emplace_back(TestData{
322 | "l x. ((x y) (l z. z))",
323 | Lambda("x", Term::Application(
324 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
325 | LambdaUP("z", Term::Variable("z", 0))))});
326 |
327 | kData.emplace_back(TestData{
328 | "l x. ((x y) (z))",
329 | Lambda("x", Term::Application(
330 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
331 | VariableUP("z", 26)))});
332 |
333 | kData.emplace_back(TestData{
334 | "l x. ((x y) z)",
335 | Lambda("x", Term::Application(
336 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
337 | VariableUP("z", 26)))});
338 |
339 | kData.emplace_back(TestData{
340 | "l x. (x (y z))",
341 | Lambda("x", Term::Application(VariableUP("x", 0),
342 | ApplicationUP(VariableUP("y", 25),
343 | VariableUP("z", 26))))});
344 |
345 | kData.emplace_back(TestData{
346 | "l x. (x) (y) (z)",
347 | Lambda("x", Term::Application(
348 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
349 | VariableUP("z", 26)))});
350 |
351 | kData.emplace_back(TestData{
352 | "l x. (x l y. y) z",
353 | Lambda("x", Term::Application(
354 | ApplicationUP(VariableUP("x", 0),
355 | LambdaUP("y", Term::Variable("y", 0))),
356 | VariableUP("z", 26)))});
357 |
358 | kData.emplace_back(TestData{
359 | "l x. (x y l z. z)",
360 | Lambda("x", Term::Application(
361 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
362 | LambdaUP("z", Term::Variable("z", 0))))});
363 |
364 | kData.emplace_back(TestData{
365 | "l x. (x y z)",
366 | Lambda("x", Term::Application(
367 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
368 | VariableUP("z", 26)))});
369 |
370 | kData.emplace_back(TestData{
371 | "l x. (x y) (z)",
372 | Lambda("x", Term::Application(
373 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
374 | VariableUP("z", 26)))});
375 |
376 | kData.emplace_back(TestData{
377 | "l x. (x y) l z. z",
378 | Lambda("x", Term::Application(
379 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
380 | LambdaUP("z", Term::Variable("z", 0))))});
381 |
382 | kData.emplace_back(TestData{
383 | "l x. (x y) z",
384 | Lambda("x", Term::Application(
385 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
386 | VariableUP("z", 26)))});
387 |
388 | kData.emplace_back(TestData{
389 | "l x. (x) l y. y",
390 | Lambda("x", Term::Application(VariableUP("x", 0),
391 | LambdaUP("y", Term::Variable("y", 0))))});
392 |
393 | kData.emplace_back(TestData{
394 | "l x. (x) y", Lambda("x", Term::Application(VariableUP("x", 0),
395 | VariableUP("y", 25)))});
396 |
397 | kData.emplace_back(TestData{
398 | "l x. (x) y (z)",
399 | Lambda("x", Term::Application(
400 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
401 | VariableUP("z", 26)))});
402 |
403 | kData.emplace_back(TestData{
404 | "l x. (x) y z",
405 | Lambda("x", Term::Application(
406 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
407 | VariableUP("z", 26)))});
408 |
409 | kData.emplace_back(TestData{
410 | "l x. l y. (x y) x",
411 | Lambda("x",
412 | Lambda("y", Term::Application(ApplicationUP(VariableUP("x", 1),
413 | VariableUP("y", 0)),
414 | VariableUP("x", 1))))});
415 |
416 | kData.emplace_back(TestData{
417 | "l x. l y. x y",
418 | Lambda("x", Lambda("y", Term::Application(VariableUP("x", 1),
419 | VariableUP("y", 0))))});
420 |
421 | kData.emplace_back(TestData{
422 | "l x. l y. x y a",
423 | Lambda("x",
424 | Lambda("y", Term::Application(ApplicationUP(VariableUP("x", 1),
425 | VariableUP("y", 0)),
426 | VariableUP("a", 2))))});
427 |
428 | kData.emplace_back(TestData{
429 | "l x. l y. x y x",
430 | Lambda("x",
431 | Lambda("y", Term::Application(ApplicationUP(VariableUP("x", 1),
432 | VariableUP("y", 0)),
433 | VariableUP("x", 1))))});
434 |
435 | kData.emplace_back(TestData{
436 | "l x. l y. x y x y",
437 | Lambda("x",
438 | Lambda("y", Term::Application(
439 | ApplicationUP(ApplicationUP(VariableUP("x", 1),
440 | VariableUP("y", 0)),
441 | VariableUP("x", 1)),
442 | VariableUP("y", 0))))});
443 |
444 | kData.emplace_back(TestData{
445 | "l x. l y. x y y",
446 | Lambda("x",
447 | Lambda("y", Term::Application(ApplicationUP(VariableUP("x", 1),
448 | VariableUP("y", 0)),
449 | VariableUP("y", 0))))});
450 |
451 | kData.emplace_back(TestData{
452 | "l x. l y. x y z",
453 | Lambda("x",
454 | Lambda("y", Term::Application(ApplicationUP(VariableUP("x", 1),
455 | VariableUP("y", 0)),
456 | VariableUP("z", 27))))});
457 |
458 | kData.emplace_back(TestData{
459 | "l x. l y. y", Lambda("x", Lambda("y", Term::Variable("y", 0)))});
460 |
461 | kData.emplace_back(TestData{
462 | "l x. x y z",
463 | Lambda("x", Term::Application(
464 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
465 | VariableUP("z", 26)))});
466 |
467 | kData.emplace_back(TestData{
468 | "l x. x (l y. y)",
469 | Lambda("x", Term::Application(VariableUP("x", 0),
470 | LambdaUP("y", Term::Variable("y", 0))))});
471 |
472 | kData.emplace_back(TestData{
473 | "l x. x (l y. y) l z. z",
474 | Lambda("x", Term::Application(
475 | ApplicationUP(VariableUP("x", 0),
476 | LambdaUP("y", Term::Variable("y", 0))),
477 | LambdaUP("z", Term::Variable("z", 0))))});
478 |
479 | kData.emplace_back(TestData{
480 | "l x. x (l y. y) l z. (z w)",
481 | Lambda("x",
482 | Term::Application(
483 | ApplicationUP(VariableUP("x", 0),
484 | LambdaUP("y", Term::Variable("y", 0))),
485 | LambdaUP("z", Term::Application(VariableUP("z", 0),
486 | VariableUP("w", 24)))))});
487 |
488 | kData.emplace_back(TestData{
489 | "l x. x (l y. y) z",
490 | Lambda("x", Term::Application(
491 | ApplicationUP(VariableUP("x", 0),
492 | LambdaUP("y", Term::Variable("y", 0))),
493 | VariableUP("z", 26)))});
494 |
495 | kData.emplace_back(TestData{
496 | "l x. x (y l z. z)",
497 | Lambda("x",
498 | Term::Application(
499 | VariableUP("x", 0),
500 | ApplicationUP(VariableUP("y", 25),
501 | LambdaUP("z", Term::Variable("z", 0)))))});
502 |
503 | kData.emplace_back(TestData{
504 | "l x. x (y z)",
505 | Lambda("x", Term::Application(VariableUP("x", 0),
506 | ApplicationUP(VariableUP("y", 25),
507 | VariableUP("z", 26))))});
508 |
509 | kData.emplace_back(TestData{
510 | "l x. x (y) (z)",
511 | Lambda("x", Term::Application(
512 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
513 | VariableUP("z", 26)))});
514 |
515 | kData.emplace_back(TestData{
516 | "l x. x (y) z",
517 | Lambda("x", Term::Application(
518 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
519 | VariableUP("z", 26)))});
520 |
521 | kData.emplace_back(TestData{
522 | "l x. x l y. y",
523 | Lambda("x",
524 | Term::Application(VariableUP("x", 0),
525 | LambdaUP("y", (Term::Variable("y", 0)))))});
526 |
527 | kData.emplace_back(TestData{
528 | "l x. x l y. x y",
529 | Lambda("x",
530 | Term::Application(
531 | VariableUP("x", 0),
532 | LambdaUP("y", Term::Application(VariableUP("x", 1),
533 | VariableUP("y", 0)))))});
534 |
535 | kData.emplace_back(TestData{
536 | "l x. x l y. x a",
537 | Lambda("x",
538 | Term::Application(
539 | VariableUP("x", 0),
540 | LambdaUP("y", Term::Application(VariableUP("x", 1),
541 | VariableUP("a", 2)))))});
542 |
543 | kData.emplace_back(TestData{
544 | "l x. x l y. y l z. z w",
545 | Lambda(
546 | "x",
547 | Term::Application(
548 | VariableUP("x", 0),
549 | LambdaUP("y", Term::Application(
550 | VariableUP("y", 0),
551 | LambdaUP("z", Term::Application(
552 | VariableUP("z", 0),
553 | VariableUP("w", 25)))))))});
554 |
555 | kData.emplace_back(TestData{
556 | "l x. x l y. y z",
557 | Lambda("x",
558 | Term::Application(
559 | VariableUP("x", 0),
560 | LambdaUP("y", Term::Application(VariableUP("y", 0),
561 | VariableUP("z", 27)))))});
562 |
563 | kData.emplace_back(TestData{
564 | "l x. x l y. y z w",
565 | Lambda("x", Term::Application(
566 | VariableUP("x", 0),
567 | LambdaUP("y", Term::Application(
568 | ApplicationUP(VariableUP("y", 0),
569 | VariableUP("z", 27)),
570 | VariableUP("w", 24)))))});
571 |
572 | kData.emplace_back(TestData{
573 | "l x. x x y",
574 | Lambda("x", Term::Application(
575 | ApplicationUP(VariableUP("x", 0), VariableUP("x", 0)),
576 | VariableUP("y", 25)))});
577 |
578 | kData.emplace_back(TestData{
579 | "l x. x y", Lambda("x", Term::Application(VariableUP("x", 0),
580 | VariableUP("y", 25)))});
581 |
582 | kData.emplace_back(TestData{
583 | "l x. x y l y. y l z. z",
584 | Lambda(
585 | "x",
586 | Term::Application(
587 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
588 | LambdaUP("y", Term::Application(
589 | VariableUP("y", 0),
590 | LambdaUP("z", Term::Variable("z", 0))))))});
591 |
592 | kData.emplace_back(TestData{
593 | "l x. x y l y. y z",
594 | Lambda("x",
595 | Term::Application(
596 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
597 | LambdaUP("y", Term::Application(VariableUP("y", 0),
598 | VariableUP("z", 27)))))});
599 |
600 | kData.emplace_back(TestData{
601 | "l x. x y l z. z",
602 | Lambda("x", Term::Application(
603 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
604 | LambdaUP("z", Term::Variable("z", 0))))});
605 |
606 | kData.emplace_back(TestData{
607 | "l x. x z l y. y",
608 | Lambda("x", Term::Application(
609 | ApplicationUP(VariableUP("x", 0), VariableUP("z", 26)),
610 | LambdaUP("y", Term::Variable("y", 0))))});
611 |
612 | kData.emplace_back(TestData{
613 | "l x. x y z",
614 | Lambda("x", Term::Application(
615 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
616 | VariableUP("z", 26)))});
617 |
618 | kData.emplace_back(TestData{
619 | "l x. x y z w",
620 | Lambda("x", Term::Application(
621 | ApplicationUP(ApplicationUP(VariableUP("x", 0),
622 | VariableUP("y", 25)),
623 | VariableUP("z", 26)),
624 | VariableUP("w", 23)))});
625 |
626 | kData.emplace_back(TestData{
627 | "l x.(l y.((x y) x))",
628 | Lambda("x",
629 | Lambda("y", Term::Application(ApplicationUP(VariableUP("x", 1),
630 | VariableUP("y", 0)),
631 | VariableUP("x", 1))))});
632 |
633 | kData.emplace_back(TestData{"l x.x", Lambda("x", Term::Variable("x", 0))});
634 |
635 | kData.emplace_back(
636 | TestData{"l y. (y)", Lambda("x", Term::Variable("x", 0))});
637 |
638 | kData.emplace_back(TestData{
639 | "l y. (y) x", Lambda("x", Term::Application(VariableUP("y", 0),
640 | VariableUP("x", 24)))});
641 |
642 | kData.emplace_back(TestData{
643 | "l y. x l x. y",
644 | Lambda("y", Term::Application(VariableUP("x", 24),
645 | LambdaUP("x", Term::Variable("y", 1))))});
646 |
647 | kData.emplace_back(TestData{
648 | "l y. x y", Lambda("y", Term::Application(VariableUP("x", 24),
649 | VariableUP("y", 0)))});
650 |
651 | kData.emplace_back(TestData{
652 | "l y. x y z",
653 | Lambda("y", Term::Application(
654 | ApplicationUP(VariableUP("x", 24), VariableUP("y", 0)),
655 | VariableUP("z", 26)))});
656 |
657 | kData.emplace_back(TestData{
658 | "l y. x y z a",
659 | Lambda("y", Term::Application(
660 | ApplicationUP(ApplicationUP(VariableUP("x", 24),
661 | VariableUP("y", 0)),
662 | VariableUP("z", 26)),
663 | VariableUP("a", 1)))});
664 |
665 | kData.emplace_back(TestData{"x", Term::Variable("x", 23)});
666 |
667 | kData.emplace_back(
668 | TestData{"x (l y. y)",
669 | Term::Application(VariableUP("x", 23),
670 | LambdaUP("y", Term::Variable("y", 0)))});
671 |
672 | kData.emplace_back(TestData{
673 | "x (y z)", Term::Application(VariableUP("x", 23),
674 | ApplicationUP(VariableUP("y", 24),
675 | VariableUP("z", 25)))});
676 |
677 | kData.emplace_back(TestData{
678 | "x (y) z", Term::Application(
679 | ApplicationUP(VariableUP("x", 23), VariableUP("y", 24)),
680 | VariableUP("z", 25))});
681 |
682 | kData.emplace_back(TestData{
683 | "x l x. l y. x y x y",
684 | Term::Application(
685 | VariableUP("x", 23),
686 | LambdaUP("x", Lambda("y", Term::Application(
687 | ApplicationUP(
688 | ApplicationUP(VariableUP("x", 1),
689 | VariableUP("y", 0)),
690 | VariableUP("x", 1)),
691 | VariableUP("y", 0)))))});
692 |
693 | kData.emplace_back(TestData{
694 | "x l y. y", Term::Application(VariableUP("x", 23),
695 | LambdaUP("y", Term::Variable("y", 0)))});
696 |
697 | kData.emplace_back(TestData{
698 | "x y", Term::Application(VariableUP("x", 23), VariableUP("y", 24))});
699 |
700 | kData.emplace_back(TestData{
701 | "x y z x",
702 | Term::Application(ApplicationUP(ApplicationUP(VariableUP("x", 23),
703 | VariableUP("y", 24)),
704 | VariableUP("z", 25)),
705 | VariableUP("x", 23))});
706 |
707 | kData.emplace_back(TestData{
708 | "(l z. l x. x) (l y. y)",
709 | Term::Application(LambdaUP("z", Lambda("x", Term::Variable("x", 0))),
710 | LambdaUP("y", Term::Variable("y", 0)))});
711 |
712 | kData.emplace_back(TestData{
713 | "(l x. x y l y. y l z. z) x",
714 | Term::Application(
715 | LambdaUP(
716 | "x",
717 | Term::Application(
718 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
719 | LambdaUP("y", Term::Application(
720 | VariableUP("y", 0),
721 | LambdaUP("z", Term::Variable("z", 0)))))),
722 | VariableUP("x", 23))});
723 |
724 | kData.emplace_back(TestData{
725 | "l x. x (l y. y) (l z. z) w",
726 | Lambda("x",
727 | Term::Application(
728 | ApplicationUP(
729 | ApplicationUP(VariableUP("x", 0),
730 | LambdaUP("y", Term::Variable("y", 0))),
731 | LambdaUP("z", Term::Variable("z", 0))),
732 | VariableUP("w", 23)))});
733 |
734 | kData.emplace_back(TestData{
735 | "l x. x (x y) l z. z",
736 | Lambda("x", Term::Application(
737 | ApplicationUP(VariableUP("x", 0),
738 | ApplicationUP(VariableUP("x", 0),
739 | VariableUP("y", 25))),
740 | LambdaUP("z", Term::Variable("z", 0))))});
741 |
742 | kData.emplace_back(TestData{
743 | "(l x. x) ((l x. x) (l z. (l x. x) z))",
744 | Term::Application(
745 | LambdaUP("x", Term::Variable("x", 0)),
746 | ApplicationUP(
747 | LambdaUP("x", Term::Variable("x", 0)),
748 | LambdaUP("z", Term::Application(
749 | LambdaUP("x", Term::Variable("x", 0)),
750 | VariableUP("z", 0)))))});
751 |
752 | // Some examples from tapl,§5.2
753 | // true = l t. l f. t
754 | // fals = l t. l f. f
755 | // test = l b. l m. l n. b m n
756 | // test true v w
757 | kData.emplace_back(TestData{
758 | "(l b. l m. l n. b m n) (l t. l f. t) v w",
759 | Term::Application(
760 | ApplicationUP(
761 | ApplicationUP(
762 | LambdaUP(
763 | "b",
764 | Lambda("m", Lambda("n", Term::Application(
765 | ApplicationUP(
766 | VariableUP("b", 2),
767 | VariableUP("m", 1)),
768 | VariableUP("n", 0))))),
769 | LambdaUP("t", Lambda("f", Term::Variable("t", 1)))),
770 | VariableUP("v", 21)),
771 | VariableUP("w", 22))});
772 |
773 | // Invalid programs:
774 | kData.emplace_back(TestData{"((x y)) (z"});
775 | kData.emplace_back(TestData{"(l x. x l y. y a"});
776 | kData.emplace_back(TestData{"(x y) x)"});
777 | kData.emplace_back(TestData{"l . y"});
778 | kData.emplace_back(TestData{"l x . (x))"});
779 | kData.emplace_back(TestData{"l x."});
780 | kData.emplace_back(TestData{"l x. ((x (y z))"});
781 | kData.emplace_back(TestData{"l x. x (l y. y l z. z"});
782 | kData.emplace_back(TestData{"l x. x (l y. y) (l z. z) w)"});
783 | kData.emplace_back(TestData{"l x. x'"});
784 | kData.emplace_back(TestData{"l x. x) (l y. y)"});
785 | kData.emplace_back(TestData{"l x. xa"});
786 | kData.emplace_back(TestData{"l x.l y. y x'"});
787 | }
788 |
789 | void Run() {
790 | InitData();
791 | std::cout << color::kYellow << "[Parser] Running " << kData.size()
792 | << " tests...\n"
793 | << color::kReset;
794 | int num_failed = 0;
795 |
796 | for (const auto& test : kData) {
797 | Parser parser{std::istringstream{test.input_program_}};
798 | Term res;
799 |
800 | try {
801 | res = parser.ParseProgram();
802 |
803 | if (*test.expected_ast_ != res) {
804 | std::cout << color::kRed << "Test failed:" << color::kReset
805 | << "\n";
806 |
807 | std::cout << " Input program: " << test.input_program_ << "\n";
808 |
809 | std::cout << color::kGreen
810 | << " Expected AST: " << color::kReset << "\n"
811 | << test.expected_ast_->ASTString(4) << "\n";
812 |
813 | std::cout << color::kRed << " Actual AST: " << color::kReset
814 | << "\n"
815 | << res.ASTString(4) << "\n";
816 |
817 | ++num_failed;
818 | }
819 | } catch (std::exception& ex) {
820 | if (test.expected_ast_) {
821 | // Unexpected parse error.
822 | std::cout << color::kRed << "Test failed:" << color::kReset
823 | << "\n";
824 |
825 | std::cout << " Input program: " << test.input_program_ << "\n";
826 |
827 | std::cout << color::kGreen
828 | << " Expected AST: " << color::kReset << "\n"
829 | << test.expected_ast_->ASTString(4) << "\n";
830 |
831 | std::cout << color::kRed << " Parsing failed." << color::kReset
832 | << "\n";
833 |
834 | ++num_failed;
835 | }
836 |
837 | continue;
838 | }
839 |
840 | // Unexpected parse success.
841 | if (!test.expected_ast_) {
842 | std::cout << color::kRed << "Test failed:" << color::kReset << "\n";
843 |
844 | std::cout << " Input program: " << test.input_program_ << "\n";
845 |
846 | std::cout << color::kGreen << " Expected parsing error"
847 | << color::kReset << "\n";
848 |
849 | std::cout << color::kRed << " Parsed AST: " << color::kReset
850 | << "\n"
851 | << res.ASTString(4) << "\n";
852 |
853 | ++num_failed;
854 | }
855 | }
856 |
857 | std::cout << color::kYellow << "Results: " << color::kReset
858 | << (kData.size() - num_failed) << " out of " << kData.size()
859 | << " tests passed.\n";
860 | }
861 |
862 | } // namespace test
863 | } // namespace parser
864 |
865 | namespace interpreter {
866 | namespace test {
867 |
868 | using namespace utils::test;
869 | using namespace parser;
870 |
871 | struct TestData {
872 | std::string input_program_;
873 | Term expected_eval_result_;
874 | };
875 |
876 | std::vector kData;
877 |
878 | void InitData() {
879 | kData.emplace_back(TestData{"x", Term::Variable("x", 23)});
880 |
881 | kData.emplace_back(TestData{"l x. x", Lambda("x", Term::Variable("x", 0))});
882 |
883 | kData.emplace_back(TestData{
884 | "(x y)", Term::Application(VariableUP("x", 23), VariableUP("y", 24))});
885 |
886 | kData.emplace_back(TestData{
887 | "x y x", Term::Application(
888 | ApplicationUP(VariableUP("x", 23), VariableUP("y", 24)),
889 | VariableUP("x", 23))});
890 |
891 | kData.emplace_back(TestData{
892 | "((x y)) (z)", Term::Application(ApplicationUP(VariableUP("x", 23),
893 | VariableUP("y", 24)),
894 | VariableUP("z", 25))});
895 |
896 | kData.emplace_back(TestData{
897 | "(l x. x a)", Lambda("x", Term::Application(VariableUP("x", 0),
898 | VariableUP("a", 1)))});
899 |
900 | kData.emplace_back(TestData{
901 | "(l x. x y l y. y l z. z)",
902 | Lambda(
903 | "x",
904 | Term::Application(
905 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
906 | LambdaUP("y", Term::Application(
907 | VariableUP("y", 0),
908 | LambdaUP("z", Term::Variable("z", 0))))))});
909 |
910 | kData.emplace_back(
911 | TestData{"(l x. x) l y. y", Lambda("y", Term::Variable("y", 0))});
912 |
913 | kData.emplace_back(TestData{"(l x. x) (l y. y) l z. z",
914 | Lambda("z", Term::Variable("z", 0))});
915 |
916 | kData.emplace_back(TestData{
917 | "(l x. x) l y. y l z. z",
918 | Lambda("y", Term::Application(VariableUP("y", 0),
919 | LambdaUP("z", Term::Variable("z", 0))))});
920 |
921 | kData.emplace_back(TestData{
922 | "(l x. x) l y. y a",
923 | Lambda("y",
924 | Term::Application(VariableUP("y", 0), VariableUP("a", 1)))});
925 |
926 | kData.emplace_back(TestData{
927 | "(l x. x) l y. y x",
928 | Lambda("y",
929 | Term::Application(VariableUP("y", 0), VariableUP("x", 24)))});
930 |
931 | kData.emplace_back(TestData{
932 | "(l x. x) l y. y z",
933 | Lambda("y",
934 | Term::Application(VariableUP("y", 0), VariableUP("z", 26)))});
935 |
936 | kData.emplace_back(TestData{"(l x. x) x", Term::Variable("x", 23)});
937 |
938 | kData.emplace_back(TestData{"(l x. x) y", Term::Variable("y", 24)});
939 |
940 | kData.emplace_back(
941 | TestData{"(x l y. y)",
942 | Term::Application(VariableUP("x", 23),
943 | LambdaUP("y", Term::Variable("y", 0)))});
944 |
945 | kData.emplace_back(TestData{"(x)", Term::Variable("x", 23)});
946 |
947 | kData.emplace_back(TestData{
948 | "l x . (l y.((x y) x))",
949 | Lambda("x",
950 | Lambda("y", Term::Application(ApplicationUP(VariableUP("x", 1),
951 | VariableUP("y", 0)),
952 | VariableUP("x", 1))))});
953 |
954 | kData.emplace_back(TestData{
955 | "l x. (x y)", Lambda("x", Term::Application(VariableUP("x", 0),
956 | VariableUP("y", 25)))});
957 |
958 | kData.emplace_back(TestData{
959 | "l x. (x) (y) (z)",
960 | Lambda("x", Term::Application(
961 | ApplicationUP(VariableUP("x", 0), VariableUP("y", 25)),
962 | VariableUP("z", 26)))});
963 |
964 | kData.emplace_back(
965 | TestData{"x (l y. y)",
966 | Term::Application(VariableUP("x", 23),
967 | LambdaUP("y", Term::Variable("y", 0)))});
968 |
969 | kData.emplace_back(TestData{"(l z. l x. x) (l y. y)",
970 | Lambda("x", Term::Variable("x", 0))});
971 |
972 | kData.emplace_back(TestData{
973 | "(l x. x y l y. y l z. z) x",
974 | Term::Application(
975 | ApplicationUP(VariableUP("x", 23), VariableUP("y", 24)),
976 | LambdaUP("y", Term::Application(
977 | VariableUP("y", 0),
978 | LambdaUP("z", Term::Variable("z", 0)))))});
979 |
980 | kData.emplace_back(TestData{
981 | "(l x. x) ((l x. x) (l z. (l x. x) z))",
982 | Lambda("z", Term::Application(LambdaUP("x", Term::Variable("x", 0)),
983 | VariableUP("z", 0)))});
984 |
985 | kData.emplace_back(TestData{"(l t. l f. t) v w", Term::Variable("v", 21)});
986 |
987 | // Some examples from tapl,§5.2
988 | // true = l t. l f. t
989 | // false = l t. l f. f
990 | // test = l b. l m. l n. b m n (b is evaluates to a bool)
991 | // test true v w => v
992 | kData.emplace_back(TestData{"(l b. l m. l n. b m n) (l t. l f. t) v w",
993 | Term::Variable("v", 21)});
994 |
995 | // and = l b. l c. b c false;
996 | // and true true => true
997 | kData.emplace_back(
998 | TestData{"(l b. l c. b c l t. l f. f) (l t. l f. t) (l t. l f. t)",
999 | Lambda("t", Lambda("f", Term::Variable("t", 1)))});
1000 | // and true false => false
1001 | kData.emplace_back(
1002 | TestData{"(l b. l c. b c l t. l f. f) (l t. l f. t) (l t. l f. f)",
1003 | Lambda("t", Lambda("f", Term::Variable("f", 0)))});
1004 |
1005 | kData.emplace_back(
1006 | TestData{"(l x. x x) y",
1007 | Term::Application(VariableUP("y", 24), VariableUP("y", 24))});
1008 |
1009 | kData.emplace_back(
1010 | TestData{"(l x. (l z. x z) x) y",
1011 | Term::Application(VariableUP("y", 24), VariableUP("y", 24))});
1012 | }
1013 |
1014 | void Run() {
1015 | InitData();
1016 | std::cout << color::kYellow << "[Interpreter] Running " << kData.size()
1017 | << " tests...\n"
1018 | << color::kReset;
1019 | int num_failed = 0;
1020 |
1021 | for (const auto& test : kData) {
1022 | Interpreter interpreter{};
1023 | parser::Term actual_eval_res;
1024 |
1025 | try {
1026 | parser::Parser parser{std::istringstream{test.input_program_}};
1027 | auto program = parser.ParseProgram();
1028 | interpreter.Interpret(program);
1029 | actual_eval_res = std::move(program);
1030 | } catch (std::exception& ex) {
1031 | std::cout << color::kRed << "Test failed:" << color::kReset << "\n";
1032 |
1033 | std::cout << " Input program: " << test.input_program_ << "\n";
1034 |
1035 | std::cout << color::kGreen
1036 | << " Expected evaluation result: " << color::kReset
1037 | << test.expected_eval_result_ << "\n";
1038 |
1039 | std::cout << color::kRed << " Parsing failed." << color::kReset
1040 | << "\n";
1041 |
1042 | ++num_failed;
1043 | continue;
1044 | }
1045 |
1046 | if (actual_eval_res != test.expected_eval_result_) {
1047 | std::cout << color::kRed << "Test failed:" << color::kReset << "\n";
1048 |
1049 | std::cout << " Input program: " << test.input_program_ << "\n";
1050 |
1051 | std::cout << color::kGreen
1052 | << " Expected evaluation result: " << color::kReset
1053 | << test.expected_eval_result_ << "\n";
1054 |
1055 | std::cout << color::kRed
1056 | << " Actual evaluation result: " << color::kReset
1057 | << actual_eval_res << "\n";
1058 |
1059 | ++num_failed;
1060 | }
1061 | }
1062 |
1063 | std::cout << color::kYellow << "Results: " << color::kReset
1064 | << (kData.size() - num_failed) << " out of " << kData.size()
1065 | << " tests passed.\n";
1066 | }
1067 | } // namespace test
1068 | } // namespace interpreter
1069 |
1070 |
--------------------------------------------------------------------------------
/ch08_tyarith/README.md:
--------------------------------------------------------------------------------
1 | # Typed Arithmetic Expressions
2 |
3 | ## Syntax
4 |
5 | ### Terms
6 |
7 | ```
8 | t ::=
9 | true
10 | false
11 | if t then t else t
12 | 0
13 | succ t
14 | pred t
15 | iszero t
16 | ```
17 |
18 | ### Values
19 |
20 | ```
21 | v ::=
22 | true
23 | false
24 | nv
25 |
26 | nv ::=
27 | 0
28 | succ nv
29 | ```
30 |
31 | ```
32 | T ::=
33 | Bool
34 | Nat
35 | ```
36 |
37 | ## Typing Rules
38 |
39 | TODO
40 |
41 | ## Evaluation Rules
42 |
43 | TODO
44 |
--------------------------------------------------------------------------------
/ch08_tyarith/interpreter.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "interpreter.hpp"
4 |
5 | int main(int argc, char* argv[]) {
6 | if (argc < 2) {
7 | std::cerr
8 | << "Error: expected input program as a command line argument.\n";
9 | return 1;
10 | }
11 |
12 | parser::Parser parser{std::istringstream{argv[1]}};
13 | type_checker::TypeChecker checker;
14 | auto program = parser.ParseProgram();
15 | std::cout << " " << program << ": " << checker.TypeOf(program) << "\n";
16 |
17 | interpreter::Interpreter interpreter;
18 | auto res = interpreter.Interpret(program);
19 | std::cout << "=> " << res.first << ": " << res.second << "\n";
20 |
21 | return 0;
22 | }
23 |
--------------------------------------------------------------------------------
/ch08_tyarith/interpreter.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | namespace lexer {
9 | struct Token {
10 | enum class Category {
11 | CONSTANT_TRUE,
12 | CONSTANT_FALSE,
13 | CONSTANT_ZERO,
14 |
15 | KEYWORD_IF,
16 | KEYWORD_THEN,
17 | KEYWORD_ELSE,
18 | KEYWORD_SUCC,
19 | KEYWORD_PRED,
20 | KEYWORD_ISZERO,
21 |
22 | MARKER_ERROR,
23 | MARKER_END
24 | };
25 |
26 | bool operator==(const Token& other) const {
27 | return category == other.category && text == other.text;
28 | }
29 |
30 | bool operator!=(const Token& other) const { return !(*this == other); }
31 |
32 | std::string DebugString() const;
33 |
34 | Category category;
35 | std::string text;
36 | };
37 |
38 | class Lexer {
39 | public:
40 | Lexer(std::istringstream&& in) : in_(std::move(in)) {}
41 |
42 | Token NextToken() {
43 | Token token;
44 |
45 | if (in_ >> token.text) {
46 | if (token.text == "true") {
47 | token.category = Token::Category::CONSTANT_TRUE;
48 | } else if (token.text == "false") {
49 | token.category = Token::Category::CONSTANT_FALSE;
50 | } else if (token.text == "0") {
51 | token.category = Token::Category::CONSTANT_ZERO;
52 | } else if (token.text == "if") {
53 | token.category = Token::Category::KEYWORD_IF;
54 | } else if (token.text == "then") {
55 | token.category = Token::Category::KEYWORD_THEN;
56 | } else if (token.text == "else") {
57 | token.category = Token::Category::KEYWORD_ELSE;
58 | } else if (token.text == "succ") {
59 | token.category = Token::Category::KEYWORD_SUCC;
60 | } else if (token.text == "pred") {
61 | token.category = Token::Category::KEYWORD_PRED;
62 | } else if (token.text == "iszero") {
63 | token.category = Token::Category::KEYWORD_ISZERO;
64 | } else {
65 | token.category = Token::Category::MARKER_ERROR;
66 | }
67 | } else {
68 | token.category = Token::Category::MARKER_END;
69 | }
70 |
71 | return token;
72 | }
73 |
74 | private:
75 | std::istringstream in_;
76 | };
77 |
78 | std::ostream& operator<<(std::ostream& out, Token::Category token_category) {
79 | switch (token_category) {
80 | case Token::Category::CONSTANT_TRUE:
81 | out << "true";
82 | break;
83 | case Token::Category::CONSTANT_FALSE:
84 | out << "false";
85 | break;
86 | case Token::Category::CONSTANT_ZERO:
87 | out << "0";
88 | break;
89 |
90 | case Token::Category::KEYWORD_IF:
91 | out << "if";
92 | break;
93 | case Token::Category::KEYWORD_THEN:
94 | out << "then";
95 | break;
96 | case Token::Category::KEYWORD_ELSE:
97 | out << "else";
98 | break;
99 | case Token::Category::KEYWORD_SUCC:
100 | out << "succ";
101 | break;
102 | case Token::Category::KEYWORD_PRED:
103 | out << "pred";
104 | break;
105 | case Token::Category::KEYWORD_ISZERO:
106 | out << "iszero";
107 | break;
108 |
109 | case Token::Category::MARKER_ERROR:
110 | out << "";
111 | break;
112 | case Token::Category::MARKER_END:
113 | out << "";
114 | break;
115 |
116 | default:
117 | out << "";
118 | }
119 |
120 | return out;
121 | }
122 |
123 | std::string Token::DebugString() const {
124 | std::ostringstream ss;
125 | ss << "{text: " << text << ", category: " << category << "}";
126 | return ss.str();
127 | }
128 |
129 | } // namespace lexer
130 |
131 | namespace parser {
132 |
133 | class Term {
134 | using Cat = lexer::Token::Category;
135 | friend std::ostream& operator<<(std::ostream& out,
136 | const Term& token_category);
137 |
138 | public:
139 | Term(Cat first_token_category, std::vector sub_terms = {})
140 | : first_token_category_(first_token_category), sub_terms_(sub_terms) {}
141 |
142 | Term() = default;
143 | Term(const Term&) = default;
144 | Term(Term&&) = default;
145 | Term& operator=(const Term&) = default;
146 | Term& operator=(Term&&) = default;
147 |
148 | Cat Category() const { return first_token_category_; }
149 | Term& SubTerm(int i) { return sub_terms_[i]; }
150 |
151 | std::string ASTString(int indentation = 0) const {
152 | std::ostringstream out;
153 | std::string prefix = std::string(indentation, ' ');
154 |
155 | switch (Category()) {
156 | case Cat::CONSTANT_ZERO:
157 | case Cat::CONSTANT_TRUE:
158 | case Cat::CONSTANT_FALSE:
159 | out << prefix << Category();
160 | break;
161 |
162 | case Cat::KEYWORD_IF:
163 | out << prefix << Category() << "\n";
164 | out << sub_terms_[0].ASTString(indentation + 2) << "\n";
165 | out << prefix << Cat::KEYWORD_ELSE << "\n";
166 | out << sub_terms_[1].ASTString(indentation + 2) << "\n";
167 | out << prefix << Cat::KEYWORD_THEN << "\n";
168 | out << sub_terms_[2].ASTString(indentation + 2);
169 | break;
170 |
171 | case Cat::KEYWORD_SUCC:
172 | case Cat::KEYWORD_PRED:
173 | case Cat::KEYWORD_ISZERO:
174 | out << prefix << Category() << "\n";
175 | out << sub_terms_[0].ASTString(indentation + 2);
176 | break;
177 |
178 | default:
179 | break;
180 | }
181 |
182 | return out.str();
183 | }
184 |
185 | bool operator==(const Term& other) const {
186 | if (Category() != other.Category()) {
187 | return false;
188 | }
189 |
190 | bool res = true;
191 |
192 | switch (Category()) {
193 | case Cat::CONSTANT_ZERO:
194 | case Cat::CONSTANT_TRUE:
195 | case Cat::CONSTANT_FALSE:
196 | break;
197 |
198 | case Cat::KEYWORD_IF:
199 | for (int i = 0; i < 3; ++i) {
200 | res = res && (sub_terms_[i] == other.sub_terms_[i]);
201 |
202 | if (!res) {
203 | break;
204 | }
205 | }
206 |
207 | break;
208 |
209 | case Cat::KEYWORD_SUCC:
210 | case Cat::KEYWORD_PRED:
211 | case Cat::KEYWORD_ISZERO:
212 | res = res && (sub_terms_[0] == other.sub_terms_[0]);
213 |
214 | break;
215 |
216 | default:
217 | break;
218 | }
219 |
220 | return res;
221 | }
222 |
223 | bool operator!=(const Term& other) const { return !(*this == other); }
224 |
225 | private:
226 | // Category of the first token in a term speicifies the type of the term.
227 | Cat first_token_category_;
228 | std::vector sub_terms_;
229 | };
230 |
231 | std::ostream& operator<<(std::ostream& out, const Term& term) {
232 | using Category = lexer::Token::Category;
233 | out << term.first_token_category_;
234 |
235 | if (term.first_token_category_ == Category::KEYWORD_IF) {
236 | out << " (" << term.sub_terms_[0] << ") " << Category::KEYWORD_THEN
237 | << " (" << term.sub_terms_[1] << ") " << Category::KEYWORD_ELSE
238 | << " (" << term.sub_terms_[2] << ")";
239 | } else if (term.first_token_category_ == Category::KEYWORD_SUCC ||
240 | term.first_token_category_ == Category::KEYWORD_PRED ||
241 | term.first_token_category_ == Category::KEYWORD_ISZERO) {
242 | out << " (" << term.sub_terms_[0] << ")";
243 | }
244 |
245 | return out;
246 | }
247 |
248 | class Parser {
249 | public:
250 | Parser(std::istringstream&& in) : lexer_(std::move(in)) {}
251 |
252 | Term ParseProgram() {
253 | auto program = NextTerm();
254 |
255 | if (lexer_.NextToken().category != lexer::Token::Category::MARKER_END) {
256 | throw std::invalid_argument(
257 | "Error: extra input after program end.");
258 | }
259 |
260 | return program;
261 | }
262 |
263 | private:
264 | Term NextTerm() {
265 | using Category = lexer::Token::Category;
266 | auto token = lexer_.NextToken();
267 |
268 | std::vector sub_terms;
269 |
270 | switch (token.category) {
271 | // Possible terms:
272 | case Category::CONSTANT_TRUE:
273 | case Category::CONSTANT_FALSE:
274 | break;
275 |
276 | case Category::CONSTANT_ZERO:
277 | break;
278 |
279 | case Category::KEYWORD_IF: {
280 | // Add condition sub-term.
281 | sub_terms.emplace_back(NextTerm());
282 |
283 | if (lexer_.NextToken().category != Category::KEYWORD_THEN) {
284 | // Parsing error:
285 | throw std::invalid_argument(
286 | "Error: invalid if-then-else term.");
287 | }
288 |
289 | // Add then branch sub-term.
290 | sub_terms.emplace_back(NextTerm());
291 |
292 | if (lexer_.NextToken().category != Category::KEYWORD_ELSE) {
293 | // Parsing error:
294 | throw std::invalid_argument(
295 | "Error: invalid if-then-else term.");
296 | }
297 |
298 | // Add then else sub-term.
299 | sub_terms.emplace_back(NextTerm());
300 | } break;
301 |
302 | case Category::KEYWORD_SUCC: {
303 | auto sub_term = NextTerm();
304 | sub_terms.emplace_back(sub_term);
305 | } break;
306 |
307 | case Category::KEYWORD_PRED:
308 | case Category::KEYWORD_ISZERO: {
309 | auto sub_term = NextTerm();
310 | sub_terms.emplace_back(sub_term);
311 | } break;
312 |
313 | // End of input (parse error):
314 | case Category::MARKER_END:
315 | throw std::invalid_argument("Error: reached end of input.");
316 |
317 | // Lexing errors:
318 | case Category::MARKER_ERROR:
319 | throw std::invalid_argument(
320 | "Error: invalid token: " + token.text + ".");
321 |
322 | // Parsing errors:
323 | case Category::KEYWORD_THEN:
324 | case Category::KEYWORD_ELSE:
325 | throw std::invalid_argument("Error: invalid term start " +
326 | token.text + ".");
327 | }
328 |
329 | return Term(token.category, sub_terms);
330 | }
331 |
332 | private:
333 | lexer::Lexer lexer_;
334 | };
335 | } // namespace parser
336 |
337 | namespace type_checker {
338 | enum class Type { Bool, Nat, IllTyped };
339 |
340 | std::ostream& operator<<(std::ostream& out, Type type) {
341 | switch (type) {
342 | case Type::Bool:
343 | out << "Bool";
344 | break;
345 |
346 | case Type::Nat:
347 | out << "Nat";
348 | break;
349 |
350 | default:
351 | out << "Ⱦ";
352 | }
353 |
354 | return out;
355 | }
356 |
357 | using lexer::Token;
358 | using parser::Term;
359 |
360 | class TypeChecker {
361 | public:
362 | Type TypeOf(Term term) {
363 | Type res = Type::IllTyped;
364 |
365 | if (term.Category() == Token::Category::CONSTANT_TRUE ||
366 | term.Category() == Token::Category::CONSTANT_FALSE) {
367 | res = Type::Bool;
368 | } else if (term.Category() == Token::Category::CONSTANT_ZERO) {
369 | res = Type::Nat;
370 | } else if (term.Category() == Token::Category::KEYWORD_IF) {
371 | auto cond_type = TypeOf(term.SubTerm(0));
372 |
373 | if (cond_type != Type::Bool) {
374 | res = Type::IllTyped;
375 | } else {
376 | auto then_type = TypeOf(term.SubTerm(1));
377 | auto else_type = TypeOf(term.SubTerm(2));
378 |
379 | if (then_type != else_type) {
380 | res = Type::IllTyped;
381 | } else {
382 | res = then_type;
383 | }
384 | }
385 | } else if (term.Category() == Token::Category::KEYWORD_SUCC ||
386 | term.Category() == Token::Category::KEYWORD_PRED) {
387 | auto subterm_type = TypeOf(term.SubTerm(0));
388 |
389 | if (subterm_type != Type::Nat) {
390 | res = Type::IllTyped;
391 | } else {
392 | res = Type::Nat;
393 | }
394 | } else if (term.Category() == Token::Category::KEYWORD_ISZERO) {
395 | auto subterm_type = TypeOf(term.SubTerm(0));
396 |
397 | if (subterm_type != Type::Nat) {
398 | res = Type::IllTyped;
399 | } else {
400 | res = Type::Bool;
401 | }
402 | }
403 |
404 | return res;
405 | }
406 | };
407 | } // namespace type_checker
408 |
409 | namespace interpreter {
410 | using lexer::Token;
411 | using parser::Term;
412 |
413 | class Interpreter {
414 | public:
415 | std::pair Interpret(Term program) {
416 | // This is a Curry-style interperter. It trys to evaluate terms even
417 | // those that are ill-typed (ref: tapl,§9.6).
418 | Term res = Eval(program);
419 | type_checker::Type res_type = type_checker::TypeChecker().TypeOf(res);
420 |
421 | return {
422 | AsString(IsValue(res) ? res
423 | : Term(Token::Category::MARKER_ERROR, {})),
424 | res_type};
425 | }
426 |
427 | private:
428 | std::string AsString(Term value) {
429 | std::ostringstream ss;
430 | ss << value;
431 | auto term_str = ss.str();
432 |
433 | if (IsNumericValue(value)) {
434 | std::size_t start_pos = 0;
435 | int num = 0;
436 |
437 | while ((start_pos = term_str.find("succ", start_pos)) !=
438 | std::string::npos) {
439 | ++num;
440 | ++start_pos;
441 | }
442 |
443 | return std::to_string(num);
444 | }
445 |
446 | return term_str;
447 | }
448 |
449 | Term Eval(Term term) {
450 | try {
451 | Term res = Eval1(term);
452 | return Eval(res);
453 | } catch (std::invalid_argument&) {
454 | return term;
455 | }
456 | }
457 |
458 | Term Eval1(Term term) {
459 | switch (term.Category()) {
460 | case Token::Category::KEYWORD_IF: {
461 | return Eval1If(term);
462 | }
463 |
464 | case Token::Category::KEYWORD_SUCC: {
465 | term.SubTerm(0) = Eval1(term.SubTerm(0));
466 | return term;
467 | }
468 |
469 | case Token::Category::KEYWORD_PRED: {
470 | return Eval1Pred(term);
471 | }
472 |
473 | case Token::Category::KEYWORD_ISZERO: {
474 | return Eval1IsZero(term);
475 | }
476 |
477 | default:
478 | throw std::invalid_argument("No applicable rule.");
479 | }
480 | }
481 |
482 | Term Eval1If(Term term) {
483 | switch (term.SubTerm(0).Category()) {
484 | case Token::Category::CONSTANT_TRUE: {
485 | return term.SubTerm(1);
486 | }
487 |
488 | case Token::Category::CONSTANT_FALSE: {
489 | return term.SubTerm(2);
490 | }
491 |
492 | default:
493 | term.SubTerm(0) = Eval1(term.SubTerm(0));
494 | return term;
495 | }
496 | }
497 |
498 | Term Eval1Pred(Term term) {
499 | switch (term.SubTerm(0).Category()) {
500 | case Token::Category::CONSTANT_ZERO: {
501 | return Term(Token::Category::CONSTANT_ZERO);
502 | }
503 |
504 | case Token::Category::KEYWORD_SUCC: {
505 | if (IsNumericValue(term.SubTerm(0))) {
506 | return term.SubTerm(0).SubTerm(0);
507 | } else {
508 | term.SubTerm(0) = Eval1(term.SubTerm(0));
509 | return term;
510 | }
511 | }
512 |
513 | default:
514 | term.SubTerm(0) = Eval1(term.SubTerm(0));
515 | return term;
516 | }
517 | }
518 |
519 | Term Eval1IsZero(Term term) {
520 | switch (term.SubTerm(0).Category()) {
521 | case Token::Category::CONSTANT_ZERO: {
522 | return Term(Token::Category::CONSTANT_TRUE);
523 | }
524 |
525 | case Token::Category::KEYWORD_SUCC: {
526 | if (IsNumericValue(term.SubTerm(0))) {
527 | return Term(Token::Category::CONSTANT_FALSE);
528 | } else {
529 | term.SubTerm(0) = Eval1(term.SubTerm(0));
530 | return term;
531 | }
532 | }
533 |
534 | default:
535 | term.SubTerm(0) = Eval1(term.SubTerm(0));
536 | return term;
537 | }
538 | }
539 |
540 | bool IsNumericValue(Term term) {
541 | return term.Category() == Token::Category::CONSTANT_ZERO ||
542 | (term.Category() == Token::Category::KEYWORD_SUCC &&
543 | IsNumericValue(term.SubTerm(0)));
544 | }
545 |
546 | bool IsValue(Term term) {
547 | return term.Category() == Token::Category::CONSTANT_TRUE ||
548 | term.Category() == Token::Category::CONSTANT_FALSE ||
549 | IsNumericValue(term);
550 | }
551 | };
552 | } // namespace interpreter
553 |
--------------------------------------------------------------------------------
/ch08_tyarith/test.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "interpreter.hpp"
4 |
5 | namespace lexer {
6 | namespace test {
7 | void Run();
8 | }
9 | } // namespace lexer
10 |
11 | namespace parser {
12 | namespace test {
13 | void Run();
14 | }
15 | } // namespace parser
16 |
17 | namespace interpreter {
18 | namespace test {
19 | void Run();
20 | }
21 | } // namespace interpreter
22 |
23 | int main() {
24 | lexer::test::Run();
25 | parser::test::Run();
26 | interpreter::test::Run();
27 |
28 | return 0;
29 | }
30 |
31 | namespace utils {
32 | namespace test {
33 | namespace color {
34 |
35 | std::string kRed{"\033[1;31m"};
36 | std::string kGreen{"\033[1;32m"};
37 | std::string kYellow{"\033[1;33m"};
38 | std::string kReset{"\033[0m"};
39 |
40 | } // namespace color
41 | } // namespace test
42 | } // namespace utils
43 |
44 | namespace lexer {
45 | namespace test {
46 |
47 | using Category = Token::Category;
48 | using TestData = std::pair>;
49 | using namespace utils::test;
50 |
51 | std::vector kData = {
52 | // All valid tokens:
53 | TestData{"true false if else then 0 succ pred iszero",
54 | {Token{Category::CONSTANT_TRUE, "true"},
55 | Token{Category::CONSTANT_FALSE, "false"},
56 | Token{Category::KEYWORD_IF, "if"},
57 | Token{Category::KEYWORD_ELSE, "else"},
58 | Token{Category::KEYWORD_THEN, "then"},
59 | Token{Category::CONSTANT_ZERO, "0"},
60 | Token{Category::KEYWORD_SUCC, "succ"},
61 | Token{Category::KEYWORD_PRED, "pred"},
62 | Token{Category::KEYWORD_ISZERO, "iszero"}}},
63 | // Invalid tokens:
64 | TestData{"x", {Token{Category::MARKER_ERROR, "x"}}},
65 | TestData{"1", {Token{Category::MARKER_ERROR, "1"}}},
66 | };
67 |
68 | void Run() {
69 | std::cout << color::kYellow << "[Lexer] Running " << kData.size()
70 | << " tests...\n"
71 | << color::kReset;
72 | int num_failed = 0;
73 |
74 | for (const auto& test : kData) {
75 | Lexer lexer{std::istringstream{test.first}};
76 |
77 | bool failed = false;
78 | auto actual_token = lexer.NextToken();
79 | auto expected_token_iter = std::begin(test.second);
80 |
81 | for (; actual_token.category != Token::Category::MARKER_END &&
82 | expected_token_iter != std::end(test.second);
83 | actual_token = lexer.NextToken(), ++expected_token_iter) {
84 | if (actual_token != *expected_token_iter) {
85 | std::cout << color::kRed << "Test failed:" << color::kReset
86 | << "\n";
87 |
88 | std::cout << " Input program: " << test.first << "\n";
89 |
90 | std::cout << color::kGreen
91 | << " Expected token: " << color::kReset
92 | << expected_token_iter->DebugString() << ", "
93 | << color::kRed << "actual token: " << color::kReset
94 | << actual_token.DebugString() << "\n";
95 | failed = true;
96 | break;
97 | }
98 | }
99 |
100 | if (!failed && (actual_token.category != Token::Category::MARKER_END ||
101 | expected_token_iter != std::end(test.second))) {
102 | std::cout << "Test failed:\n Input program: " << test.first
103 | << "\n Unexpected number of tokens.\n";
104 | failed = true;
105 | }
106 |
107 | if (failed) {
108 | ++num_failed;
109 | }
110 | }
111 |
112 | std::cout << color::kYellow << "Results: " << color::kReset
113 | << (kData.size() - num_failed) << " out of " << kData.size()
114 | << " tests passed.\n";
115 | }
116 | } // namespace test
117 | } // namespace lexer
118 |
119 | namespace parser {
120 | namespace test {
121 |
122 | using namespace utils::test;
123 | using Category = lexer::Token::Category;
124 |
125 | struct TestData {
126 | std::string input_program_;
127 | // The absense of an expected AST means that: for the test being specified,
128 | // a parse error is expected.
129 | std::optional expected_ast_;
130 | };
131 |
132 | std::vector kData{
133 | TestData{"0", Term(Category::CONSTANT_ZERO)},
134 | TestData{"true", Term(Category::CONSTANT_TRUE)},
135 | TestData{"false", Term(Category::CONSTANT_FALSE)},
136 | TestData{"if false then true else 0",
137 | Term(Category::KEYWORD_IF, {Term(Category::CONSTANT_FALSE),
138 | Term(Category::CONSTANT_TRUE),
139 | Term(Category::CONSTANT_ZERO)})},
140 | TestData{"if false then true else false",
141 | Term(Category::KEYWORD_IF, {Term(Category::CONSTANT_FALSE),
142 | Term(Category::CONSTANT_TRUE),
143 | Term(Category::CONSTANT_FALSE)})},
144 | TestData{
145 | "if false then true else succ 0",
146 | Term(Category::KEYWORD_IF,
147 | {Term(Category::CONSTANT_FALSE), Term(Category::CONSTANT_TRUE),
148 | Term(Category::KEYWORD_SUCC, {Term(Category::CONSTANT_ZERO)})})},
149 | TestData{
150 | "if false then true else succ succ 0",
151 | Term(Category::KEYWORD_IF,
152 | {Term(Category::CONSTANT_FALSE), Term(Category::CONSTANT_TRUE),
153 | Term(Category::KEYWORD_SUCC,
154 | {Term(Category::KEYWORD_SUCC,
155 | {Term(Category::CONSTANT_ZERO)})})})},
156 | TestData{
157 | "if false then true else succ succ succ 0",
158 | Term(Category::KEYWORD_IF,
159 | {Term(Category::CONSTANT_FALSE), Term(Category::CONSTANT_TRUE),
160 | Term(Category::KEYWORD_SUCC,
161 | {Term(Category::KEYWORD_SUCC,
162 | {Term(Category::KEYWORD_SUCC,
163 | {Term(Category::CONSTANT_ZERO)})})})})},
164 | TestData{
165 | "if succ 0 then succ 0 else true",
166 | Term(Category::KEYWORD_IF,
167 | {Term(Category::KEYWORD_SUCC, {Term(Category::CONSTANT_ZERO)}),
168 | Term(Category::KEYWORD_SUCC, {Term(Category::CONSTANT_ZERO)}),
169 | Term(Category::CONSTANT_TRUE)})},
170 | TestData{"if true then false else true",
171 | Term(Category::KEYWORD_IF, {Term(Category::CONSTANT_TRUE),
172 | Term(Category::CONSTANT_FALSE),
173 | Term(Category::CONSTANT_TRUE)})},
174 | TestData{"if true then succ 0 else 0",
175 | Term(Category::KEYWORD_IF, {Term(Category::CONSTANT_TRUE),
176 | Term(Category::KEYWORD_SUCC,
177 | {Term(Category::CONSTANT_ZERO)}),
178 | Term(Category::CONSTANT_ZERO)})},
179 | TestData{"if true then succ 0 else true",
180 | Term(Category::KEYWORD_IF, {Term(Category::CONSTANT_TRUE),
181 | Term(Category::KEYWORD_SUCC,
182 | {Term(Category::CONSTANT_ZERO)}),
183 | Term(Category::CONSTANT_TRUE)})},
184 | TestData{
185 | "if true then true else succ 0",
186 | Term(Category::KEYWORD_IF,
187 | {Term(Category::CONSTANT_TRUE), Term(Category::CONSTANT_TRUE),
188 | Term(Category::KEYWORD_SUCC, {Term(Category::CONSTANT_ZERO)})})},
189 |
190 | TestData{
191 | "if if true then false else true then true else false",
192 | Term(Category::KEYWORD_IF,
193 | {Term(Category::KEYWORD_IF, {Term(Category::CONSTANT_TRUE),
194 | Term(Category::CONSTANT_FALSE),
195 | Term(Category::CONSTANT_TRUE)}),
196 | Term(Category::CONSTANT_TRUE), Term(Category::CONSTANT_FALSE)})},
197 | TestData{"iszero 0",
198 | Term(Category::KEYWORD_ISZERO, {Term(Category::CONSTANT_ZERO)})},
199 | TestData{"iszero pred succ succ 0",
200 | Term(Category::KEYWORD_ISZERO,
201 | {Term(Category::KEYWORD_PRED,
202 | {Term(Category::KEYWORD_SUCC,
203 | {Term(Category::KEYWORD_SUCC,
204 | {Term(Category::CONSTANT_ZERO)})})})})},
205 | TestData{"pred pred 0", Term(Category::KEYWORD_PRED,
206 | {Term(Category::KEYWORD_PRED,
207 | {Term(Category::CONSTANT_ZERO)})})},
208 | TestData{"pred pred if true then true else false",
209 | Term(Category::KEYWORD_PRED,
210 | {Term(Category::KEYWORD_PRED,
211 | {Term(Category::KEYWORD_IF,
212 | {Term(Category::CONSTANT_TRUE),
213 | Term(Category::CONSTANT_TRUE),
214 | Term(Category::CONSTANT_FALSE)})})})},
215 | TestData{"pred succ 0", Term(Category::KEYWORD_PRED,
216 | {Term(Category::KEYWORD_SUCC,
217 | {Term(Category::CONSTANT_ZERO)})})},
218 | TestData{"pred succ if true then true else false",
219 | Term(Category::KEYWORD_PRED,
220 | {Term(Category::KEYWORD_SUCC,
221 | {Term(Category::KEYWORD_IF,
222 | {Term(Category::CONSTANT_TRUE),
223 | Term(Category::CONSTANT_TRUE),
224 | Term(Category::CONSTANT_FALSE)})})})},
225 | TestData{"pred succ pred 0",
226 | Term(Category::KEYWORD_PRED,
227 | {Term(Category::KEYWORD_SUCC,
228 | {Term(Category::KEYWORD_PRED,
229 | {Term(Category::CONSTANT_ZERO)})})})},
230 | TestData{"pred succ pred succ 0",
231 | Term(Category::KEYWORD_PRED,
232 | {Term(Category::KEYWORD_SUCC,
233 | {Term(Category::KEYWORD_PRED,
234 | {Term(Category::KEYWORD_SUCC,
235 | {Term(Category::CONSTANT_ZERO)})})})})},
236 | TestData{
237 | "succ if true then true else false",
238 | Term(Category::KEYWORD_SUCC,
239 | {Term(Category::KEYWORD_IF, {Term(Category::CONSTANT_TRUE),
240 | Term(Category::CONSTANT_TRUE),
241 | Term(Category::CONSTANT_FALSE)})})},
242 | TestData{"succ succ true", Term(Category::KEYWORD_SUCC,
243 | {Term(Category::KEYWORD_SUCC,
244 | {Term(Category::CONSTANT_TRUE)})})},
245 |
246 | // Expected parse errors.
247 | TestData{"succ"},
248 | TestData{"if else false"},
249 | TestData{"if if true then false else true then true else"},
250 | TestData{"if if true then false else true then true else false if"},
251 | TestData{"if then else succ pred"},
252 | TestData{"if then else succ pred 0 true false"},
253 | TestData{"if then else succ pred 0 true false test"},
254 | TestData{"if true"},
255 | TestData{"if true else false"},
256 | TestData{"if true then succ 1 else true"},
257 | TestData{"pred"},
258 | TestData{"pred pred"},
259 | TestData{"pred succ"},
260 | TestData{"pred succ 1"},
261 | TestData{"pred succ if true then true false"},
262 | TestData{"succ"},
263 | TestData{"succ 1"},
264 | TestData{"succ pred 0 pred"},
265 | TestData{"succ pred 0 pred 0"},
266 | TestData{"succ pred 0 presd"},
267 | TestData{"succ succ 1"},
268 | };
269 |
270 | void Run() {
271 | std::cout << color::kYellow << "[Parser] Running " << kData.size()
272 | << " tests...\n"
273 | << color::kReset;
274 | int num_failed = 0;
275 |
276 | for (const auto& test : kData) {
277 | Parser parser{std::istringstream{test.input_program_}};
278 | Term res;
279 |
280 | try {
281 | res = parser.ParseProgram();
282 |
283 | if (*test.expected_ast_ != res) {
284 | std::cout << color::kRed << "Test failed:" << color::kReset
285 | << "\n";
286 |
287 | std::cout << " Input program: " << test.input_program_ << "\n";
288 |
289 | std::cout << color::kGreen
290 | << " Expected AST: " << color::kReset << "\n"
291 | << test.expected_ast_->ASTString(4) << "\n";
292 |
293 | std::cout << color::kRed << " Actual AST: " << color::kReset
294 | << "\n"
295 | << res.ASTString(4) << "\n";
296 |
297 | ++num_failed;
298 | }
299 | } catch (std::exception& ex) {
300 | if (test.expected_ast_) {
301 | // Unexpected parse error.
302 | std::cout << color::kRed << "Test failed:" << color::kReset
303 | << "\n";
304 |
305 | std::cout << " Input program: " << test.input_program_ << "\n";
306 |
307 | std::cout << color::kGreen
308 | << " Expected AST: " << color::kReset << "\n"
309 | << test.expected_ast_->ASTString(4) << "\n";
310 |
311 | std::cout << color::kRed << " Parsing failed." << color::kReset
312 | << "\n";
313 |
314 | ++num_failed;
315 | }
316 |
317 | continue;
318 | }
319 |
320 | // Unexpected parse success.
321 | if (!test.expected_ast_) {
322 | std::cout << color::kRed << "Test failed:" << color::kReset << "\n";
323 |
324 | std::cout << " Input program: " << test.input_program_ << "\n";
325 |
326 | std::cout << color::kGreen << " Expected parsing error"
327 | << color::kReset << "\n";
328 |
329 | std::cout << color::kRed << " Parsed AST: " << color::kReset
330 | << "\n"
331 | << res.ASTString(4) << "\n";
332 |
333 | ++num_failed;
334 | }
335 | }
336 |
337 | std::cout << color::kYellow << "Results: " << color::kReset
338 | << (kData.size() - num_failed) << " out of " << kData.size()
339 | << " tests passed.\n";
340 | }
341 |
342 | } // namespace test
343 | } // namespace parser
344 |
345 | namespace interpreter {
346 | namespace test {
347 |
348 | using namespace utils::test;
349 | using namespace type_checker;
350 |
351 | struct TestData {
352 | std::string input_program_;
353 | std::pair expected_eval_result_;
354 | };
355 |
356 | std::vector kData{
357 | TestData{"0", {"0", Type::Nat}},
358 | TestData{"true", {"true", Type::Bool}},
359 | TestData{"false", {"false", Type::Bool}},
360 | TestData{"if false then true else 0", {"0", Type::Nat}},
361 | TestData{"if false then true else false", {"false", Type::Bool}},
362 | TestData{"if false then true else succ 0", {"1", Type::Nat}},
363 | TestData{"if false then true else succ succ 0", {"2", Type::Nat}},
364 | TestData{"if false then true else succ succ succ 0", {"3", Type::Nat}},
365 | TestData{"if succ 0 then succ 0 else true", {"", Type::IllTyped}},
366 | TestData{"if true then false else true", {"false", Type::Bool}},
367 | TestData{"if true then succ 0 else 0", {"1", Type::Nat}},
368 | TestData{"if true then succ 0 else true", {"1", Type::Nat}},
369 | TestData{"if true then true else succ 0", {"true", Type::Bool}},
370 | TestData{"if if true then false else true then true else false",
371 | {"false", Type::Bool}},
372 | TestData{"iszero 0", {"true", Type::Bool}},
373 | TestData{"iszero pred succ succ 0", {"false", Type::Bool}},
374 | TestData{"pred pred 0", {"0", Type::Nat}},
375 | TestData{"pred pred if true then true else false",
376 | {"", Type::IllTyped}},
377 | TestData{"pred succ 0", {"0", Type::Nat}},
378 | TestData{"pred succ if true then true else false",
379 | {"", Type::IllTyped}},
380 | TestData{"pred succ pred 0", {"0", Type::Nat}},
381 | TestData{"pred succ pred succ 0", {"0", Type::Nat}},
382 | TestData{"succ if true then true else false", {"", Type::IllTyped}},
383 | TestData{"succ succ true", {"", Type::IllTyped}},
384 | };
385 |
386 | void Run() {
387 | std::cout << color::kYellow << "[Interpreter] Running " << kData.size()
388 | << " tests...\n"
389 | << color::kReset;
390 | int num_failed = 0;
391 |
392 | for (const auto& test : kData) {
393 | Interpreter interpreter{};
394 | std::pair actual_eval_res;
395 |
396 | try {
397 | actual_eval_res = interpreter.Interpret(
398 | parser::Parser{std::istringstream{test.input_program_}}
399 | .ParseProgram());
400 | } catch (std::exception& ex) {
401 | std::cout << color::kRed << "Test failed:" << color::kReset << "\n";
402 |
403 | std::cout << " Input program: " << test.input_program_ << "\n";
404 |
405 | std::cout << color::kGreen
406 | << " Expected evaluation result: " << color::kReset
407 | << test.expected_eval_result_.first << ": "
408 | << test.expected_eval_result_.second << "\n";
409 |
410 | std::cout << color::kRed << " Parsing failed." << color::kReset
411 | << "\n";
412 |
413 | ++num_failed;
414 | continue;
415 | }
416 |
417 | if (actual_eval_res.first != test.expected_eval_result_.first ||
418 | actual_eval_res.second != test.expected_eval_result_.second) {
419 | std::cout << color::kRed << "Test failed:" << color::kReset << "\n";
420 |
421 | std::cout << " Input program: " << test.input_program_ << "\n";
422 |
423 | std::cout << color::kGreen
424 | << " Expected evaluation result: " << color::kReset
425 | << test.expected_eval_result_.first << ": "
426 | << test.expected_eval_result_.second << "\n";
427 |
428 | std::cout << color::kRed
429 | << " Actual evaluation result: " << color::kReset
430 | << actual_eval_res.first << ": " << actual_eval_res.second
431 | << "\n";
432 |
433 | ++num_failed;
434 | }
435 | }
436 |
437 | std::cout << color::kYellow << "Results: " << color::kReset
438 | << (kData.size() - num_failed) << " out of " << kData.size()
439 | << " tests passed.\n";
440 | }
441 | } // namespace test
442 | } // namespace interpreter
443 |
444 |
--------------------------------------------------------------------------------
/ch10_simplebool/README.md:
--------------------------------------------------------------------------------
1 | # Simply Typed Lamda Calculus
2 |
3 | ## Syntax
4 |
5 | ### Terms
6 |
7 | ```
8 | t ::=
9 | x
10 | l x:T. t
11 | t t
12 | true
13 | false
14 | if t then t else t
15 | ```
16 |
17 | ### Values
18 |
19 | ```
20 | v ::=
21 | x
22 | l x:T. t
23 | true
24 | false
25 | ```
26 |
27 | ### Types
28 |
29 | ```
30 | T ::=
31 | Bool
32 | T -> T
33 | ```
34 |
35 | ### Contexts
36 |
37 | ```
38 | Γ ::=
39 | Φ
40 | Γ, x:T
41 | ```
42 |
43 | ## Typing Rules
44 |
45 | TODO
46 |
47 | ## Evaluation Rules
48 |
49 | TODO
50 |
--------------------------------------------------------------------------------
/ch10_simplebool/interpreter.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "interpreter.hpp"
4 |
5 | int main(int argc, char* argv[]) {
6 | if (argc < 2) {
7 | std::cerr
8 | << "Error: expected input program as a command line argument.\n";
9 | return 1;
10 | }
11 |
12 | parser::Parser parser{std::istringstream{argv[1]}};
13 | type_checker::TypeChecker checker;
14 | auto program = parser.ParseProgram();
15 | std::cout << " " << program << ": " << checker.TypeOf(program) << "\n";
16 |
17 | interpreter::Interpreter interpreter;
18 | auto res = interpreter.Interpret(program);
19 | std::cout << "=> " << res.first << ": " << res.second << "\n";
20 |
21 | return 0;
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/ch10_simplebool/interpreter.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | namespace lexer {
13 | struct Token {
14 | enum class Category {
15 | VARIABLE,
16 | LAMBDA,
17 | LAMBDA_DOT,
18 | OPEN_PAREN,
19 | CLOSE_PAREN,
20 | COLON,
21 | ARROW,
22 |
23 | CONSTANT_TRUE,
24 | CONSTANT_FALSE,
25 |
26 | KEYWORD_BOOL,
27 | KEYWORD_IF,
28 | KEYWORD_THEN,
29 | KEYWORD_ELSE,
30 |
31 | MARKER_END,
32 | MARKER_INVALID
33 | };
34 |
35 | Token(Category category = Category::MARKER_INVALID, std::string text = "")
36 | : category_(category),
37 | text_(category == Category::VARIABLE ? text : "") {}
38 |
39 | bool operator==(const Token& other) const {
40 | return category_ == other.category_ && text_ == other.text_;
41 | }
42 |
43 | bool operator!=(const Token& other) const { return !(*this == other); }
44 |
45 | Category GetCategory() const { return category_; }
46 |
47 | std::string GetText() const { return text_; }
48 |
49 | private:
50 | Category category_ = Category::MARKER_INVALID;
51 | std::string text_;
52 | };
53 |
54 | std::ostream& operator<<(std::ostream& out, Token token);
55 |
56 | namespace {
57 | const std::string kLambdaInputSymbol = "l";
58 | const std::string kKeywordBool = "Bool";
59 | } // namespace
60 |
61 | class Lexer {
62 | public:
63 | Lexer(std::istringstream&& in) {
64 | std::istringstream iss(SurroundTokensBySpaces(std::move(in)));
65 | token_strings_ =
66 | std::vector(std::istream_iterator{iss},
67 | std::istream_iterator());
68 | }
69 |
70 | Token NextToken() {
71 | Token token;
72 |
73 | if (current_token_ == token_strings_.size()) {
74 | token = Token(Token::Category::MARKER_END);
75 | return token;
76 | }
77 |
78 | if (token_strings_[current_token_] == "true") {
79 | token = Token(Token::Category::CONSTANT_TRUE);
80 | } else if (token_strings_[current_token_] == "false") {
81 | token = Token(Token::Category::CONSTANT_FALSE);
82 | } else if (token_strings_[current_token_] == "if") {
83 | token = Token(Token::Category::KEYWORD_IF);
84 | } else if (token_strings_[current_token_] == "then") {
85 | token = Token(Token::Category::KEYWORD_THEN);
86 | } else if (token_strings_[current_token_] == "else") {
87 | token = Token(Token::Category::KEYWORD_ELSE);
88 | } else if (token_strings_[current_token_] == kLambdaInputSymbol) {
89 | token = Token(Token::Category::LAMBDA);
90 | } else if (token_strings_[current_token_] == kKeywordBool) {
91 | token = Token(Token::Category::KEYWORD_BOOL);
92 | } else if (token_strings_[current_token_] == "(") {
93 | token = Token(Token::Category::OPEN_PAREN);
94 | } else if (token_strings_[current_token_] == ")") {
95 | token = Token(Token::Category::CLOSE_PAREN);
96 | } else if (token_strings_[current_token_] == ".") {
97 | token = Token(Token::Category::LAMBDA_DOT);
98 | } else if (token_strings_[current_token_] == ":") {
99 | token = Token(Token::Category::COLON);
100 | } else if (token_strings_[current_token_] == "->") {
101 | token = Token(Token::Category::ARROW);
102 | } else if (IsVariableName(token_strings_[current_token_])) {
103 | token = Token(Token::Category::VARIABLE,
104 | token_strings_[current_token_]);
105 | }
106 |
107 | ++current_token_;
108 |
109 | return token;
110 | }
111 |
112 | void PutBackToken() {
113 | if (current_token_ > 0) {
114 | --current_token_;
115 | }
116 | }
117 |
118 | private:
119 | std::string SurroundTokensBySpaces(std::istringstream&& in) {
120 | std::ostringstream processed_stream;
121 | char c;
122 |
123 | while (in.get(c)) {
124 | // Check for one-character separators and surround them with spaces.
125 | if (c == ':' || c == '.' || c == '(' || c == ')') {
126 | processed_stream << " " << c << " ";
127 | } else if (c == '-') {
128 | // Check for the only two-character serparator '->' and surround
129 | // it with spaces.
130 | if (in.peek() == '>') {
131 | in.get(c);
132 | processed_stream << " -> ";
133 | } else {
134 | // Just write '-' and let the lexing error be
135 | // discovered later.
136 | processed_stream << " - ";
137 | }
138 | } else {
139 | processed_stream << c;
140 | }
141 | }
142 |
143 | return processed_stream.str();
144 | }
145 |
146 | bool IsVariableName(std::string token_text) {
147 | for (auto c : token_text) {
148 | if (!std::isalpha(c) && c != '_') {
149 | return false;
150 | }
151 | }
152 |
153 | return !token_text.empty();
154 | }
155 |
156 | private:
157 | std::vector token_strings_;
158 | int current_token_ = 0;
159 | };
160 |
161 | std::ostream& operator<<(std::ostream& out, Token token) {
162 | switch (token.GetCategory()) {
163 | case Token::Category::LAMBDA:
164 | out << "λ";
165 | break;
166 | case Token::Category::KEYWORD_BOOL:
167 | out << "";
168 | break;
169 | case Token::Category::VARIABLE:
170 | out << token.GetText();
171 | break;
172 | case Token::Category::LAMBDA_DOT:
173 | out << ".";
174 | break;
175 | case Token::Category::OPEN_PAREN:
176 | out << "(";
177 | break;
178 | case Token::Category::CLOSE_PAREN:
179 | out << ")";
180 | break;
181 | case Token::Category::COLON:
182 | out << ":";
183 | break;
184 | case Token::Category::ARROW:
185 | out << "->";
186 | break;
187 |
188 | case Token::Category::CONSTANT_TRUE:
189 | out << "";
190 | break;
191 | case Token::Category::CONSTANT_FALSE:
192 | out << "";
193 | break;
194 | case Token::Category::KEYWORD_IF:
195 | out << "";
196 | break;
197 | case Token::Category::KEYWORD_THEN:
198 | out << "";
199 | break;
200 | case Token::Category::KEYWORD_ELSE:
201 | out << "";
202 | break;
203 |
204 | case Token::Category::MARKER_END:
205 | out << "";
206 | break;
207 |
208 | case Token::Category::MARKER_INVALID:
209 | out << "";
210 | break;
211 |
212 | default:
213 | out << "";
214 | }
215 |
216 | return out;
217 | }
218 | } // namespace lexer
219 |
220 | namespace parser {
221 |
222 | class Type {
223 | friend std::ostream& operator<<(std::ostream&, const Type&);
224 |
225 | public:
226 | // TODO: For now, types are created over and over again. Instead, create
227 | // each type once and use its reference. For one thing, a Type shouldn't be
228 | // owned by a term of that Type.
229 |
230 | static Type IllTyped() {
231 | Type type;
232 | type.ill_typed_ = true;
233 | return type;
234 | }
235 |
236 | static Type SimpleBool() {
237 | Type type;
238 | type.simple_bool_ = true;
239 | return type;
240 | }
241 |
242 | static Type FunctionType(std::unique_ptr lhs,
243 | std::unique_ptr rhs) {
244 | Type type;
245 | type.lhs_ = std::move(lhs);
246 | type.rhs_ = std::move(rhs);
247 |
248 | return type;
249 | }
250 |
251 | Type() = default;
252 |
253 | Type(const Type&) = delete;
254 | Type& operator=(const Type&) = delete;
255 |
256 | Type(Type&&) = default;
257 | Type& operator=(Type&&) = default;
258 |
259 | ~Type() = default;
260 |
261 | Type Clone() const {
262 | if (IsIllTyped()) {
263 | return Type::IllTyped();
264 | }
265 |
266 | if (IsSimpleBool()) {
267 | return Type::SimpleBool();
268 | }
269 |
270 | return Type::FunctionType(std::make_unique(lhs_->Clone()),
271 | std::make_unique(rhs_->Clone()));
272 | }
273 |
274 | bool operator==(const Type& other) const {
275 | if (ill_typed_) {
276 | return other.ill_typed_;
277 | }
278 |
279 | if (simple_bool_) {
280 | return other.simple_bool_;
281 | }
282 |
283 | return lhs_ && rhs_ && other.lhs_ && other.rhs_ &&
284 | (*lhs_ == *other.lhs_) && (*rhs_ == *other.rhs_);
285 | }
286 |
287 | bool operator!=(const Type& other) const { return !(*this == other); }
288 |
289 | bool IsIllTyped() const { return ill_typed_; }
290 |
291 | bool IsSimpleBool() const { return simple_bool_; }
292 |
293 | bool IsFunction() const { return !ill_typed_ && !simple_bool_; }
294 |
295 | Type& FunctionLHS() const {
296 | if (!IsFunction()) {
297 | throw std::invalid_argument("Invalid function type.");
298 | }
299 |
300 | return *lhs_;
301 | }
302 |
303 | Type& FunctionRHS() const {
304 | if (!IsFunction()) {
305 | throw std::invalid_argument("Invalid function type.");
306 | }
307 |
308 | return *rhs_;
309 | }
310 |
311 | private:
312 | bool ill_typed_ = false;
313 |
314 | bool simple_bool_ = false;
315 |
316 | std::unique_ptr lhs_;
317 | std::unique_ptr rhs_;
318 | };
319 |
320 | std::ostream& operator<<(std::ostream& out, const Type& type) {
321 | if (type.IsSimpleBool()) {
322 | out << lexer::kKeywordBool;
323 | } else if (type.IsFunction()) {
324 | out << "(" << *type.lhs_ << " "
325 | << lexer::Token(lexer::Token::Category::ARROW) << " " << *type.rhs_
326 | << ")";
327 | } else {
328 | out << "Ⱦ";
329 | }
330 |
331 | return out;
332 | }
333 |
334 | class Term {
335 | friend std::ostream& operator<<(std::ostream&, const Term&);
336 |
337 | public:
338 | static Term Lambda(std::string arg_name, std::unique_ptr arg_type) {
339 | Term result;
340 | result.lambda_arg_name_ = arg_name;
341 | result.lambda_arg_type_ = std::move(arg_type);
342 | result.is_lambda_ = true;
343 |
344 | return result;
345 | }
346 |
347 | static Term Variable(std::string var_name, int de_bruijn_idx) {
348 | Term result;
349 | result.variable_name_ = var_name;
350 | result.de_bruijn_idx_ = de_bruijn_idx;
351 | result.is_variable_ = true;
352 |
353 | return result;
354 | }
355 |
356 | static Term Application(std::unique_ptr lhs,
357 | std::unique_ptr rhs) {
358 | Term result;
359 | result.is_application_ = true;
360 | result.application_lhs_ = std::move(lhs);
361 | result.application_rhs_ = std::move(rhs);
362 |
363 | return result;
364 | }
365 |
366 | static Term If() {
367 | Term result;
368 | result.is_if_ = true;
369 |
370 | return result;
371 | }
372 |
373 | static Term True() {
374 | Term result;
375 | result.is_true_ = true;
376 |
377 | return result;
378 | }
379 |
380 | static Term False() {
381 | Term result;
382 | result.is_false_ = true;
383 |
384 | return result;
385 | }
386 |
387 | Term() = default;
388 |
389 | Term(const Term&) = delete;
390 | Term& operator=(const Term&) = delete;
391 |
392 | Term(Term&&) = default;
393 | Term& operator=(Term&&) = default;
394 |
395 | ~Term() = default;
396 |
397 | bool IsLambda() const { return is_lambda_; }
398 |
399 | void MarkLambdaAsComplete() { is_complete_lambda_ = true; }
400 |
401 | bool IsVariable() const { return is_variable_; }
402 |
403 | bool IsApplication() const { return is_application_; }
404 |
405 | bool IsIf() const { return is_if_; }
406 |
407 | bool IsTrue() const { return is_true_; }
408 |
409 | bool IsFalse() const { return is_false_; }
410 |
411 | void MarkIfConditionAsComplete() { is_complete_if_condition_ = true; }
412 |
413 | void MarkIfThenAsComplete() { is_complete_if_then_ = true; }
414 |
415 | void MarkIfElseAsComplete() { is_complete_if_else_ = true; }
416 |
417 | bool IsInvalid() const {
418 | if (IsLambda()) {
419 | return lambda_arg_name_.empty() || !lambda_arg_type_ ||
420 | !lambda_body_;
421 | } else if (IsVariable()) {
422 | return variable_name_.empty();
423 | } else if (IsApplication()) {
424 | return !application_lhs_ || !application_rhs_;
425 | } else if (IsIf()) {
426 | return !if_condition_ || !if_then_ || !if_else_;
427 | } else if (IsTrue() || IsFalse()) {
428 | return false;
429 | }
430 |
431 | return true;
432 | }
433 |
434 | bool IsEmpty() const {
435 | return !IsLambda() && !IsVariable() && !IsApplication() && !IsIf();
436 | }
437 |
438 | Term& Combine(Term&& term) {
439 | if (term.IsInvalid()) {
440 | throw std::invalid_argument(
441 | "Term::Combine() received an invalid Term.");
442 | }
443 |
444 | if (IsLambda()) {
445 | if (lambda_body_) {
446 | // If the lambda body was completely parsed, then combining this
447 | // term and the argument term means applying this lambda to the
448 | // argument.
449 | if (is_complete_lambda_) {
450 | *this =
451 | Application(std::make_unique(std::move(*this)),
452 | std::make_unique(std::move(term)));
453 |
454 | is_lambda_ = false;
455 | lambda_body_ = nullptr;
456 | lambda_arg_name_ = "";
457 | is_complete_lambda_ = false;
458 | } else {
459 | lambda_body_->Combine(std::move(term));
460 | }
461 | } else {
462 | lambda_body_ = std::make_unique(std::move(term));
463 | }
464 | } else if (IsVariable()) {
465 | *this = Application(std::make_unique(std::move(*this)),
466 | std::make_unique(std::move(term)));
467 |
468 | is_variable_ = false;
469 | variable_name_ = "";
470 | } else if (IsApplication()) {
471 | *this = Application(std::make_unique(std::move(*this)),
472 | std::make_unique(std::move(term)));
473 | } else if (IsIf()) {
474 | if (!is_complete_if_condition_) {
475 | if (if_condition_) {
476 | if_condition_->Combine(std::move(term));
477 | } else {
478 | if_condition_ = std::make_unique(std::move(term));
479 | }
480 | } else if (!is_complete_if_then_) {
481 | if (if_then_) {
482 | if_then_->Combine(std::move(term));
483 | } else {
484 | if_then_ = std::make_unique(std::move(term));
485 | }
486 | } else {
487 | if (if_else_) {
488 | // If the lambda body was completely parsed, then combining
489 | // this term and the argument term means applying this
490 | // lambda to the argument.
491 | if (is_complete_if_else_) {
492 | *this = Application(
493 | std::make_unique(std::move(*this)),
494 | std::make_unique(std::move(term)));
495 |
496 | is_if_ = false;
497 | if_condition_ = nullptr;
498 | if_then_ = nullptr;
499 | if_else_ = nullptr;
500 | is_complete_if_condition_ = false;
501 | is_complete_if_then_ = false;
502 | is_complete_if_else_ = false;
503 | } else {
504 | if_else_->Combine(std::move(term));
505 | }
506 | } else {
507 | if_else_ = std::make_unique(std::move(term));
508 | }
509 | }
510 | } else if (IsTrue() || IsFalse()) {
511 | throw std::invalid_argument("Trying to combine with a constant.");
512 | } else {
513 | *this = std::move(term);
514 | }
515 |
516 | return *this;
517 | }
518 |
519 | /*
520 | * Shifts the de Bruijn indices of all free variables inside this Term up by
521 | * distance amount. For an example use, see Term::Substitute(int, Term&).
522 | */
523 | void Shift(int distance) {
524 | std::function walk = [&distance, &walk](
525 | int binding_context_size,
526 | Term& term) {
527 | if (term.IsInvalid()) {
528 | throw std::invalid_argument("Trying to shift an invalid term.");
529 | }
530 |
531 | if (term.IsVariable()) {
532 | if (term.de_bruijn_idx_ >= binding_context_size) {
533 | term.de_bruijn_idx_ += distance;
534 | }
535 | } else if (term.IsLambda()) {
536 | walk(binding_context_size + 1, *term.lambda_body_);
537 | } else if (term.IsApplication()) {
538 | walk(binding_context_size, *term.application_lhs_);
539 | walk(binding_context_size, *term.application_rhs_);
540 | }
541 | };
542 |
543 | walk(0, *this);
544 | }
545 |
546 | /**
547 | * Substitutes variable (that is, the de Brijun idex of a variable) with the
548 | * term sub.
549 | */
550 | void Substitute(int variable, Term& sub) {
551 | if (IsInvalid() || sub.IsInvalid()) {
552 | throw std::invalid_argument(
553 | "Trying to substitute using invalid terms.");
554 | }
555 |
556 | std::function walk = [&variable, &sub, &walk](
557 | int binding_context_size,
558 | Term& term) {
559 | if (term.IsVariable()) {
560 | // Adjust variable according to the current binding
561 | // depth before comparing term's index.
562 | if (term.de_bruijn_idx_ == variable + binding_context_size) {
563 | // Shift sub up by binding_context_size distance since sub
564 | // is now substituted in binding_context_size deep context.
565 | auto clone = sub.Clone();
566 | clone.Shift(binding_context_size);
567 | std::swap(term, clone);
568 | }
569 | } else if (term.IsLambda()) {
570 | walk(binding_context_size + 1, *term.lambda_body_);
571 | } else if (term.IsApplication()) {
572 | walk(binding_context_size, *term.application_lhs_);
573 | walk(binding_context_size, *term.application_rhs_);
574 | } else if (term.IsIf()) {
575 | walk(binding_context_size, *term.if_condition_);
576 | walk(binding_context_size, *term.if_then_);
577 | walk(binding_context_size, *term.if_else_);
578 | }
579 | };
580 |
581 | walk(0, *this);
582 | }
583 |
584 | Term& LambdaBody() const {
585 | if (!IsLambda()) {
586 | throw std::invalid_argument("Invalid Lambda term.");
587 | }
588 |
589 | return *lambda_body_;
590 | }
591 |
592 | std::string LambdaArgName() const {
593 | if (!IsLambda()) {
594 | throw std::invalid_argument("Invalid Lambda term.");
595 | }
596 |
597 | return lambda_arg_name_;
598 | }
599 |
600 | Type& LambdaArgType() const {
601 | if (!IsLambda()) {
602 | throw std::invalid_argument("Invalid Lambda term.");
603 | }
604 |
605 | return *lambda_arg_type_;
606 | }
607 |
608 | std::string VariableName() const {
609 | if (!IsVariable()) {
610 | throw std::invalid_argument("Invalid variable term.");
611 | }
612 |
613 | return variable_name_;
614 | }
615 |
616 | int VariableDeBruijnIdx() const {
617 | if (!IsVariable()) {
618 | throw std::invalid_argument("Invalid variable term.");
619 | }
620 |
621 | return de_bruijn_idx_;
622 | }
623 |
624 | Term& ApplicationLHS() const {
625 | if (!IsApplication()) {
626 | throw std::invalid_argument("Invalid application term.");
627 | }
628 |
629 | return *application_lhs_;
630 | }
631 |
632 | Term& ApplicationRHS() const {
633 | if (!IsApplication()) {
634 | throw std::invalid_argument("Invalid application term.");
635 | }
636 |
637 | return *application_rhs_;
638 | }
639 |
640 | Term& IfCondition() const {
641 | if (!IsIf()) {
642 | throw std::invalid_argument("Invalid if term.");
643 | }
644 |
645 | return *if_condition_;
646 | }
647 |
648 | Term& IfThen() const {
649 | if (!IsIf()) {
650 | throw std::invalid_argument("Invalid if term.");
651 | }
652 |
653 | return *if_then_;
654 | }
655 |
656 | Term& IfElse() const {
657 | if (!IsIf()) {
658 | throw std::invalid_argument("Invalid if term.");
659 | }
660 |
661 | return *if_else_;
662 | }
663 |
664 | bool operator==(const Term& other) const {
665 | if (IsLambda() && other.IsLambda()) {
666 | return LambdaArgType() == other.LambdaArgType() &&
667 | LambdaBody() == other.LambdaBody();
668 | }
669 |
670 | if (IsVariable() && other.IsVariable()) {
671 | return de_bruijn_idx_ == other.de_bruijn_idx_;
672 | }
673 |
674 | if (IsApplication() && other.IsApplication()) {
675 | return (ApplicationLHS() == other.ApplicationLHS()) &&
676 | (ApplicationRHS() == other.ApplicationRHS());
677 | }
678 |
679 | if (IsIf() && other.IsIf()) {
680 | return (IfCondition() == other.IfCondition()) &&
681 | (IfThen() == other.IfThen()) && (IfElse() == other.IfElse());
682 | }
683 |
684 | if (IsTrue() && other.IsTrue()) {
685 | return true;
686 | }
687 |
688 | if (IsFalse() && other.IsFalse()) {
689 | return true;
690 | }
691 |
692 | return false;
693 | }
694 |
695 | bool operator!=(const Term& other) const { return !(*this == other); }
696 |
697 | std::string ASTString(int indentation = 0) const {
698 | std::ostringstream out;
699 | std::string prefix = std::string(indentation, '-');
700 |
701 | if (IsLambda()) {
702 | out << prefix << "λ " << lambda_arg_name_ << ":"
703 | << *lambda_arg_type_ << "\n";
704 | out << lambda_body_->ASTString(indentation + 2);
705 | } else if (IsVariable()) {
706 | out << prefix << variable_name_ << "[" << de_bruijn_idx_ << "]";
707 | } else if (IsApplication()) {
708 | out << prefix << "<-\n";
709 | out << application_lhs_->ASTString(indentation + 2) << "\n";
710 | out << application_rhs_->ASTString(indentation + 2);
711 | } else if (IsIf()) {
712 | out << prefix << "if\n";
713 | out << if_condition_->ASTString(indentation + 2) << "\n";
714 | out << prefix << "then\n";
715 | out << if_then_->ASTString(indentation + 2) << "\n";
716 | out << prefix << "else\n";
717 | out << if_else_->ASTString(indentation + 2);
718 | } else if (IsTrue()) {
719 | out << prefix << "true";
720 | } else if (IsFalse()) {
721 | out << prefix << "false";
722 | }
723 |
724 | return out.str();
725 | }
726 |
727 | Term Clone() const {
728 | if (IsInvalid()) {
729 | throw std::logic_error("Trying to clone an invalid term.");
730 | }
731 |
732 | if (IsLambda()) {
733 | return std::move(
734 | Lambda(lambda_arg_name_,
735 | std::make_unique(lambda_arg_type_->Clone()))
736 | .Combine(lambda_body_->Clone()));
737 | } else if (IsVariable()) {
738 | return Variable(variable_name_, de_bruijn_idx_);
739 | } else if (IsApplication()) {
740 | return Application(
741 | std::make_unique(application_lhs_->Clone()),
742 | std::make_unique(application_rhs_->Clone()));
743 | } else if (IsTrue()) {
744 | return Term::True();
745 | } else if (IsFalse()) {
746 | return Term::False();
747 | }
748 |
749 | std::ostringstream error_ss;
750 | error_ss << "Couldn't clone term: " << *this;
751 | throw std::logic_error(error_ss.str());
752 | }
753 |
754 | bool is_complete_lambda_ = false;
755 |
756 | private:
757 | bool is_lambda_ = false;
758 | std::string lambda_arg_name_ = "";
759 | std::unique_ptr lambda_arg_type_{};
760 | std::unique_ptr lambda_body_{};
761 | // Marks whether parsing for the body of the lambda term is finished or not.
762 |
763 | bool is_variable_ = false;
764 | std::string variable_name_ = "";
765 | int de_bruijn_idx_ = -1;
766 |
767 | bool is_application_ = false;
768 | std::unique_ptr application_lhs_{};
769 | std::unique_ptr application_rhs_{};
770 |
771 | bool is_if_ = false;
772 | std::unique_ptr if_condition_{};
773 | std::unique_ptr if_then_{};
774 | std::unique_ptr if_else_{};
775 | bool is_complete_if_condition_ = false;
776 | bool is_complete_if_then_ = false;
777 | bool is_complete_if_else_ = false;
778 |
779 | bool is_true_ = false;
780 |
781 | bool is_false_ = false;
782 | };
783 |
784 | std::ostream& operator<<(std::ostream& out, const Term& term) {
785 | if (term.IsInvalid()) {
786 | out << "";
787 | } else if (term.IsVariable()) {
788 | out << term.variable_name_;
789 | } else if (term.IsLambda()) {
790 | out << "{l " << term.lambda_arg_name_ << " : " << *term.lambda_arg_type_
791 | << ". " << *term.lambda_body_ << "}";
792 | } else if (term.IsApplication()) {
793 | out << "(" << *term.application_lhs_ << " <- " << *term.application_rhs_
794 | << ")";
795 | } else if (term.IsIf()) {
796 | out << "if (" << *term.if_condition_ << ") then (" << *term.if_then_
797 | << ") else (" << *term.if_else_ << ")";
798 | } else if (term.IsTrue()) {
799 | out << "true";
800 | } else if (term.IsFalse()) {
801 | out << "false";
802 | } else {
803 | out << "";
804 | }
805 |
806 | return out;
807 | }
808 |
809 | class Parser {
810 | using Token = lexer::Token;
811 |
812 | public:
813 | Parser(std::istringstream&& in) : lexer_(std::move(in)) {}
814 |
815 | Term ParseProgram() {
816 | Token next_token;
817 | std::vector term_stack;
818 | term_stack.emplace_back(Term());
819 | int balance_parens = 0;
820 | // For each '(', records the size of term_stack when the '(' was parsed.
821 | // This is used later when the corresponding ')' is parsed to know how
822 | // many Terms from term_stack should be popped (i.e. their parsing is
823 | // know to be complete).
824 | std::vector stack_size_on_open_paren;
825 | // Contains a list of bound variables in order of binding. For example,
826 | // for a term λ x. λ y. x y, this list would eventually contains {"x" ,
827 | // "y"} in that order. This is used to assign de Bruijn indices/static
828 | // distances to bound variables (ref: tapl,§6.1).
829 | std::vector bound_variables;
830 |
831 | while ((next_token = lexer_.NextToken()).GetCategory() !=
832 | Token::Category::MARKER_END) {
833 | if (next_token.GetCategory() == Token::Category::LAMBDA) {
834 | auto lambda_arg = ParseLambdaArg();
835 | auto lambda_arg_name = lambda_arg.first;
836 | bound_variables.push_back(lambda_arg_name);
837 |
838 | // If the current stack top is empty, use its slot for the
839 | // lambda.
840 | if (term_stack.back().IsEmpty()) {
841 | term_stack.back() = Term::Lambda(
842 | lambda_arg_name,
843 | std::make_unique(std::move(lambda_arg.second)));
844 | } else {
845 | // Else, push a new term on the stack to start building the
846 | // lambda term.
847 | term_stack.emplace_back(Term::Lambda(
848 | lambda_arg_name,
849 | std::make_unique(std::move(lambda_arg.second))));
850 | }
851 | } else if (next_token.GetCategory() == Token::Category::VARIABLE) {
852 | auto bound_variable_it =
853 | std::find(std::begin(bound_variables),
854 | std::end(bound_variables), next_token.GetText());
855 | int de_bruijn_idx = -1;
856 |
857 | if (bound_variable_it != std::end(bound_variables)) {
858 | de_bruijn_idx = std::distance(bound_variable_it,
859 | std::end(bound_variables)) -
860 | 1;
861 | } else {
862 | // The naming context for free variables (ref: tapl,§6.1.2)
863 | // is chosen to be the ASCII code of a variable's name.
864 | //
865 | // NOTE: Only single-character variable names are currecntly
866 | // supported as free variables.
867 | if (next_token.GetText().length() != 1) {
868 | std::ostringstream error_ss;
869 | error_ss << "Unexpected token: " << next_token;
870 | throw std::invalid_argument(error_ss.str());
871 | }
872 |
873 | de_bruijn_idx =
874 | bound_variables.size() +
875 | (std::tolower(next_token.GetText()[0]) - 'a');
876 | }
877 |
878 | term_stack.back().Combine(
879 | Term::Variable(next_token.GetText(), de_bruijn_idx));
880 | } else if (next_token.GetCategory() ==
881 | Token::Category::KEYWORD_IF) {
882 | // If the current stack top is empty, use its slot for the
883 | // if condition.
884 | if (term_stack.back().IsEmpty()) {
885 | term_stack.back() = Term::If();
886 | } else {
887 | // Else, push a new term on the stack to start building the
888 | // if condition.
889 | term_stack.emplace_back(Term::If());
890 | }
891 |
892 | stack_size_on_open_paren.emplace_back(term_stack.size());
893 | term_stack.emplace_back(Term());
894 | ++balance_parens;
895 | } else if (next_token.GetCategory() ==
896 | Token::Category::KEYWORD_THEN) {
897 | while (!term_stack.empty() &&
898 | !stack_size_on_open_paren.empty() &&
899 | term_stack.size() > stack_size_on_open_paren.back()) {
900 | if (term_stack.back().IsLambda() &&
901 | !term_stack.back().is_complete_lambda_) {
902 | // Mark the λ as complete so that terms to its right
903 | // won't be combined to its body.
904 | term_stack.back().MarkLambdaAsComplete();
905 | // λ's variable is no longer part of the current binding
906 | // context, therefore pop it.
907 | bound_variables.pop_back();
908 | }
909 |
910 | if (term_stack.back().IsIf()) {
911 | term_stack.back().MarkIfElseAsComplete();
912 | }
913 |
914 | CombineStackTop(term_stack);
915 | }
916 |
917 | --balance_parens;
918 |
919 | if (!stack_size_on_open_paren.empty()) {
920 | stack_size_on_open_paren.pop_back();
921 | }
922 |
923 | if (!term_stack.back().IsIf()) {
924 | throw std::invalid_argument("Unexpected 'then'");
925 | }
926 |
927 | term_stack.back().MarkIfConditionAsComplete();
928 |
929 | stack_size_on_open_paren.emplace_back(term_stack.size());
930 | term_stack.emplace_back(Term());
931 | ++balance_parens;
932 | } else if (next_token.GetCategory() ==
933 | Token::Category::KEYWORD_ELSE) {
934 | while (!term_stack.empty() &&
935 | !stack_size_on_open_paren.empty() &&
936 | term_stack.size() > stack_size_on_open_paren.back()) {
937 | if (term_stack.back().IsLambda() &&
938 | !term_stack.back().is_complete_lambda_) {
939 | // Mark the λ as complete so that terms to its right
940 | // won't be combined to its body.
941 | term_stack.back().MarkLambdaAsComplete();
942 | // λ's variable is no longer part of the current binding
943 | // context, therefore pop it.
944 | bound_variables.pop_back();
945 | }
946 |
947 | if (term_stack.back().IsIf()) {
948 | term_stack.back().MarkIfElseAsComplete();
949 | }
950 |
951 | CombineStackTop(term_stack);
952 | }
953 |
954 | --balance_parens;
955 |
956 | if (!stack_size_on_open_paren.empty()) {
957 | stack_size_on_open_paren.pop_back();
958 | }
959 |
960 | if (!term_stack.back().IsIf()) {
961 | throw std::invalid_argument("Unexpected 'else'");
962 | }
963 |
964 | term_stack.back().MarkIfThenAsComplete();
965 | } else if (next_token.GetCategory() ==
966 | Token::Category::OPEN_PAREN) {
967 | stack_size_on_open_paren.emplace_back(term_stack.size());
968 | term_stack.emplace_back(Term());
969 | ++balance_parens;
970 | } else if (next_token.GetCategory() ==
971 | Token::Category::CLOSE_PAREN) {
972 | while (!term_stack.empty() &&
973 | !stack_size_on_open_paren.empty() &&
974 | term_stack.size() > stack_size_on_open_paren.back()) {
975 | if (term_stack.back().IsLambda() &&
976 | !term_stack.back().is_complete_lambda_) {
977 | // Mark the λ as complete so that terms to its right
978 | // won't be combined to its body.
979 | term_stack.back().MarkLambdaAsComplete();
980 | // λ's variable is no longer part of the current binding
981 | // context, therefore pop it.
982 | bound_variables.pop_back();
983 | }
984 |
985 | if (term_stack.back().IsIf()) {
986 | term_stack.back().MarkIfElseAsComplete();
987 | }
988 |
989 | CombineStackTop(term_stack);
990 | }
991 |
992 | --balance_parens;
993 |
994 | if (!stack_size_on_open_paren.empty()) {
995 | stack_size_on_open_paren.pop_back();
996 | }
997 | } else if (next_token.GetCategory() ==
998 | Token::Category::CONSTANT_TRUE) {
999 | term_stack.back().Combine(Term::True());
1000 |
1001 | } else if (next_token.GetCategory() ==
1002 | Token::Category::CONSTANT_FALSE) {
1003 | term_stack.back().Combine(Term::False());
1004 | } else {
1005 | std::ostringstream error_ss;
1006 | error_ss << "Unexpected token: " << next_token;
1007 | throw std::invalid_argument(error_ss.str());
1008 | }
1009 | }
1010 |
1011 | if (balance_parens != 0) {
1012 | throw std::invalid_argument(
1013 | "Invalid term: probably because a ( is not matched by a )");
1014 | }
1015 |
1016 | while (term_stack.size() > 1) {
1017 | CombineStackTop(term_stack);
1018 | }
1019 |
1020 | if (term_stack.back().IsInvalid()) {
1021 | throw std::invalid_argument("Invalid term.");
1022 | }
1023 |
1024 | return std::move(term_stack.back());
1025 | }
1026 |
1027 | void CombineStackTop(std::vector& term_stack) {
1028 | if (term_stack.size() < 2) {
1029 | throw std::invalid_argument(
1030 | "Invalid term: probably because a ( is not matched by a )");
1031 | }
1032 |
1033 | Term top = std::move(term_stack.back());
1034 | term_stack.pop_back();
1035 | term_stack.back().Combine(std::move(top));
1036 | }
1037 |
1038 | std::pair ParseLambdaArg() {
1039 | auto token = lexer_.NextToken();
1040 |
1041 | if (token.GetCategory() != Token::Category::VARIABLE) {
1042 | throw std::logic_error("Expected to parse a variable.");
1043 | }
1044 |
1045 | auto arg_name = token.GetText();
1046 | token = lexer_.NextToken();
1047 |
1048 | if (token.GetCategory() != Token::Category::COLON) {
1049 | throw std::logic_error("Expected to parse a ':'.");
1050 | }
1051 |
1052 | return {arg_name, std::move(ParseType())};
1053 | }
1054 |
1055 | Type ParseType() {
1056 | std::vector parts;
1057 | while (true) {
1058 | auto token = lexer_.NextToken();
1059 |
1060 | if (token.GetCategory() == Token::Category::KEYWORD_BOOL) {
1061 | parts.emplace_back(Type::SimpleBool());
1062 | } else if (token.GetCategory() == Token::Category::OPEN_PAREN) {
1063 | parts.emplace_back(ParseType());
1064 |
1065 | if (lexer_.NextToken().GetCategory() !=
1066 | Token::Category::CLOSE_PAREN) {
1067 | std::ostringstream error_ss;
1068 | error_ss << __LINE__ << ": Unexpected token: " << token;
1069 | throw std::logic_error(error_ss.str());
1070 | }
1071 | } else {
1072 | std::ostringstream error_ss;
1073 | error_ss << __LINE__ << ": Unexpected token: " << token;
1074 | throw std::logic_error(error_ss.str());
1075 | }
1076 |
1077 | token = lexer_.NextToken();
1078 |
1079 | if (token.GetCategory() == Token::Category::LAMBDA_DOT) {
1080 | break;
1081 | } else if (token.GetCategory() == Token::Category::CLOSE_PAREN) {
1082 | lexer_.PutBackToken();
1083 | break;
1084 | } else if (token.GetCategory() != Token::Category::ARROW) {
1085 | std::ostringstream error_ss;
1086 | error_ss << __LINE__ << ": Unexpected token: " << token;
1087 | throw std::logic_error(error_ss.str());
1088 | }
1089 | }
1090 |
1091 | for (int i = parts.size() - 2; i >= 0; --i) {
1092 | parts[i] = Type::FunctionType(
1093 | std::make_unique(std::move(parts[i])),
1094 | std::make_unique(std::move(parts[i + 1])));
1095 | }
1096 |
1097 | return std::move(parts[0]);
1098 | }
1099 |
1100 | Token ParseDot() {
1101 | auto token = lexer_.NextToken();
1102 |
1103 | return (token.GetCategory() == Token::Category::LAMBDA_DOT)
1104 | ? token
1105 | : throw std::logic_error("Expected to parse a dot.");
1106 | }
1107 |
1108 | private:
1109 | lexer::Lexer lexer_;
1110 | }; // namespace parser
1111 | } // namespace parser
1112 |
1113 | namespace type_checker {
1114 | using parser::Term;
1115 | using parser::Type;
1116 |
1117 | class TypeChecker {
1118 | using Context = std::deque>;
1119 |
1120 | public:
1121 | Type TypeOf(const Term& term) {
1122 | Context ctx;
1123 | return TypeOf(ctx, term);
1124 | }
1125 |
1126 | Type TypeOf(const Context& ctx, const Term& term) {
1127 | Type res = Type::IllTyped();
1128 |
1129 | if (term.IsTrue() || term.IsFalse()) {
1130 | res = Type::SimpleBool();
1131 | } else if (term.IsIf()) {
1132 | if (TypeOf(ctx, term.IfCondition()) == Type::SimpleBool()) {
1133 | Type then_type = TypeOf(ctx, term.IfThen());
1134 |
1135 | if (then_type == TypeOf(ctx, term.IfElse())) {
1136 | res = then_type.Clone();
1137 | }
1138 | }
1139 | } else if (term.IsLambda()) {
1140 | Context new_ctx =
1141 | AddBinding(ctx, term.LambdaArgName(), term.LambdaArgType());
1142 | Type return_type = TypeOf(new_ctx, term.LambdaBody());
1143 | res = Type::FunctionType(
1144 | std::make_unique(term.LambdaArgType().Clone()),
1145 | std::make_unique(return_type.Clone()));
1146 | } else if (term.IsApplication()) {
1147 | Type lhs_type = TypeOf(ctx, term.ApplicationLHS());
1148 | Type rhs_type = TypeOf(ctx, term.ApplicationRHS());
1149 |
1150 | if (lhs_type.IsFunction() && lhs_type.FunctionLHS() == rhs_type) {
1151 | res = lhs_type.FunctionRHS().Clone();
1152 | }
1153 | } else if (term.IsVariable()) {
1154 | int idx = term.VariableDeBruijnIdx();
1155 |
1156 | if (idx >= 0 && idx < ctx.size() &&
1157 | ctx[idx].first == term.VariableName()) {
1158 | res = ctx[idx].second->Clone();
1159 | }
1160 | }
1161 |
1162 | return res;
1163 | }
1164 |
1165 | private:
1166 | Context AddBinding(const Context& current_ctx, std::string var_name,
1167 | Type& type) {
1168 | Context new_ctx = current_ctx;
1169 | new_ctx.push_front({var_name, &type});
1170 |
1171 | return new_ctx;
1172 | }
1173 | };
1174 | } // namespace type_checker
1175 |
1176 | namespace interpreter {
1177 | class Interpreter {
1178 | using Term = parser::Term;
1179 |
1180 | public:
1181 | std::pair Interpret(Term& program) {
1182 | Eval(program);
1183 | type_checker::Type type = type_checker::TypeChecker().TypeOf(program);
1184 |
1185 | std::ostringstream ss;
1186 | ss << program;
1187 |
1188 | return {ss.str(), std::move(type)};
1189 | }
1190 |
1191 | private:
1192 | void Eval(Term& term) {
1193 | try {
1194 | Eval1(term);
1195 | Eval(term);
1196 | } catch (std::invalid_argument&) {
1197 | }
1198 | }
1199 |
1200 | void Eval1(Term& term) {
1201 | auto term_subst_top = [](Term& s, Term& t) {
1202 | // Adjust the free variables in s by increasing their static
1203 | // distances by 1. That's because s will now be embedded one level
1204 | // deeper in t (i.e. t's bound variable will be replaced by s).
1205 | s.Shift(1);
1206 | t.Substitute(0, s);
1207 | // Because of the substitution, one level of abstraction was peeled
1208 | // off. Account for that by decreasing the static distances of the
1209 | // free variables in t by 1.
1210 | t.Shift(-1);
1211 | // NOTE: For more details see: tapl,§6.3.
1212 | };
1213 |
1214 | if (term.IsApplication() && term.ApplicationLHS().IsLambda() &&
1215 | IsValue(term.ApplicationRHS())) {
1216 | term_subst_top(term.ApplicationRHS(),
1217 | term.ApplicationLHS().LambdaBody());
1218 | std::swap(term, term.ApplicationLHS().LambdaBody());
1219 | } else if (term.IsApplication() && IsValue(term.ApplicationLHS())) {
1220 | Eval1(term.ApplicationRHS());
1221 | } else if (term.IsApplication()) {
1222 | Eval1(term.ApplicationLHS());
1223 | } else if (term.IsIf()) {
1224 | if (term.IfCondition() == Term::True()) {
1225 | std::swap(term, term.IfThen());
1226 | } else if (term.IfCondition() == Term::False()) {
1227 | std::swap(term, term.IfElse());
1228 | } else {
1229 | Eval1(term.IfCondition());
1230 | }
1231 | } else {
1232 | throw std::invalid_argument("No applicable rule.");
1233 | }
1234 | }
1235 |
1236 | bool IsValue(const Term& term) {
1237 | return term.IsLambda() || term.IsVariable() || term.IsTrue() ||
1238 | term.IsFalse();
1239 | }
1240 | };
1241 | } // namespace interpreter
1242 |
--------------------------------------------------------------------------------
/ch11_fullsimple/README.md:
--------------------------------------------------------------------------------
1 | # Typed Lamda Calculus (with varios extensions)
2 |
3 | ## Syntax
4 |
5 | ### Terms
6 |
7 | ```
8 | t ::=
9 | x
10 | l x:T. t
11 | t t
12 | true
13 | false
14 | if t then t else t
15 | 0
16 | succ t
17 | pred t
18 | iszero t
19 | {l_i=t_i} for i in 1..n
20 | t.l
21 | ```
22 |
23 | ### Values
24 |
25 | ```
26 | v ::=
27 | x
28 | l x:T. t
29 | true
30 | false
31 | {l_i=v_i} for i in 1..n
32 | nv
33 |
34 | nv ::=
35 | 0
36 | succ nv
37 | ```
38 |
39 | ### Types
40 |
41 | ```
42 | T ::=
43 | Bool
44 | Nat
45 | {l_i:T_i} for i in 1..n
46 | T -> T
47 | ```
48 |
49 | ### Contexts
50 |
51 | ```
52 | Γ ::=
53 | Φ
54 | Γ, x:T
55 | ```
56 |
57 | ## Typing Rules
58 |
59 | TODO
60 |
61 | ## Evaluation Rules
62 |
63 | TODO
64 |
--------------------------------------------------------------------------------
/ch11_fullsimple/interpreter.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "interpreter.hpp"
4 |
5 | int main(int argc, char* argv[]) {
6 | if (argc < 2) {
7 | std::cerr
8 | << "Error: expected input program as a command line argument.\n";
9 | return 1;
10 | }
11 |
12 | parser::Parser parser{std::istringstream{argv[1]}};
13 | type_checker::TypeChecker checker;
14 | auto program = parser.ParseProgram();
15 | std::cout << " " << program << ": " << checker.TypeOf(program) << "\n";
16 |
17 | interpreter::Interpreter interpreter;
18 | auto res = interpreter.Interpret(program);
19 | std::cout << "=> " << res.first << ": " << res.second << "\n";
20 |
21 | return 0;
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/ch17_rcdjoinsub/README.md:
--------------------------------------------------------------------------------
1 | # Typed Lamda Calculus (with varios extensions)
2 |
3 | ## Syntax
4 |
5 | ### Terms
6 |
7 | ```
8 | t ::=
9 | x
10 | l x:T. t
11 | t t
12 | true
13 | false
14 | if t then t else t
15 | 0
16 | succ t
17 | pred t
18 | iszero t
19 | {l_i=t_i} for i in 1..n
20 | t.l
21 | ```
22 |
23 | ### Values
24 |
25 | ```
26 | v ::=
27 | x
28 | l x:T. t
29 | true
30 | false
31 | {l_i=v_i} for i in 1..n
32 | nv
33 |
34 | nv ::=
35 | 0
36 | succ nv
37 | ```
38 |
39 | ### Types
40 |
41 | ```
42 | T ::=
43 | Bool
44 | Nat
45 | {l_i:T_i} for i in 1..n
46 | T -> T
47 | ```
48 |
49 | ### Contexts
50 |
51 | ```
52 | Γ ::=
53 | Φ
54 | Γ, x:T
55 | ```
56 |
57 | ## Typing Rules
58 |
59 | TODO
60 |
61 | ## Evaluation Rules
62 |
63 | TODO
64 |
--------------------------------------------------------------------------------
/ch17_rcdjoinsub/interpreter.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "interpreter.hpp"
4 |
5 | int main(int argc, char* argv[]) {
6 | if (argc < 2) {
7 | std::cerr
8 | << "Error: expected input program as a command line argument.\n";
9 | return 1;
10 | }
11 |
12 | parser::Parser parser{std::istringstream{argv[1]}};
13 | type_checker::TypeChecker checker;
14 | auto program = parser.ParseProgram();
15 | std::cout << " " << program << ": " << checker.TypeOf(program) << "\n";
16 |
17 | interpreter::Interpreter interpreter;
18 | auto res = interpreter.Interpret(program);
19 | std::cout << "=> " << res.first << ": " << res.second << "\n";
20 |
21 | return 0;
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/ch18_fullref/README.md:
--------------------------------------------------------------------------------
1 | # Typed Lamda Calculus (with varios extensions)
2 |
3 | ## Syntax
4 |
5 | ### Terms
6 |
7 | ```
8 | t ::=
9 | x
10 | l x:T. t
11 | t t
12 | true
13 | false
14 | if t then t else t
15 | 0
16 | succ t
17 | pred t
18 | iszero t
19 | {l_i=t_i} for i in 1..n
20 | t.l
21 | let x = t in t
22 | ref t
23 | !t
24 | t := t
25 | t; t
26 | fix t
27 | l
28 | ```
29 |
30 | ### Values
31 |
32 | ```
33 | v ::=
34 | x
35 | l x:T. t
36 | true
37 | false
38 | {l_i=v_i} for i in 1..n
39 | nv
40 | l
41 |
42 | nv ::=
43 | 0
44 | succ nv
45 | ```
46 |
47 | ### Types
48 |
49 | ```
50 | T ::=
51 | Bool
52 | Nat
53 | {l_i:T_i} for i in 1..n
54 | T -> T
55 | Ref T
56 | Source T
57 | Sink T
58 | ```
59 |
60 | ### Contexts
61 |
62 | ```
63 | Γ ::=
64 | Φ
65 | Γ, x:T
66 | ```
67 |
68 | ## Typing Rules
69 |
70 | TODO
71 |
72 | ## Evaluation Rules
73 |
74 | TODO
75 |
--------------------------------------------------------------------------------
/ch18_fullref/examples/01_object_generators.ml:
--------------------------------------------------------------------------------
1 | (* Basic counter object generator: *)
2 | newCounter = l _:Unit. let x = ref succ 0 in {get = l _:Unit. !x, inc = l _:Unit. x := succ(!x)}
3 |
4 | (* Generate an object and play with it: *)
5 | c1 = newCounter unit
6 | c1.inc unit
7 | (c1.inc unit; c1.inc unit; c1.get unit)
8 |
9 | (* Generate another object and play with it: *)
10 | c2 = newCounter unit
11 | c2.get unit
12 |
13 | (* Increments counter objects 3 times in a raw: *)
14 | inc3 = l c:{get:Unit -> Nat, inc:Unit -> Unit}. (c.inc unit; c.inc unit; c.inc unit)
15 |
16 | (* Apply inc3 to counter objects: *)
17 | (inc3 c1; c1.get unit)
18 |
19 | inc3 c2
20 | c2.get unit
21 |
22 | (* An reset counter object whose type is a sub-type of objects generated by newCounter: *)
23 | newResetCounter = (l _:Unit. (let x = ref succ 0 in {get = l _:Unit. !x, inc = l _:Unit. x := succ(!x), reset = l _:Unit. x := succ 0}))
24 |
25 | (* Generate reset counter object and play with it: *)
26 | rc1 = newResetCounter unit
27 | (inc3 rc1; rc1.reset unit; inc3 rc1; rc1.get unit)
28 |
29 | (* Group instance variable(s) is a record of reference cells: *)
30 | c = let r = {x = ref succ 0} in {get = l _:Unit. !(r.x), inc = l _:Unit. r.x := succ(!r.x)}
31 |
--------------------------------------------------------------------------------
/ch18_fullref/examples/02_classes.ml:
--------------------------------------------------------------------------------
1 | (*
2 | * A simple counter class:
3 | * class operations - get, succ.
4 | *)
5 | counterClass = l r:{x:Ref Nat}. {get = l _:Unit. !(r.x), inc = l _:Unit. r.x := succ(!r.x)}
6 | (* object generator: *)
7 | newCounter = l _:Unit. let r = {x = ref succ 0} in counterClass r
8 |
9 | (*
10 | * A reset counter class:
11 | * inherits implementation of get and inc operations from counterClass
12 | * add a new operation - reset
13 | *)
14 | resetCounterClass = l r:{x:Ref Nat}. let super = counterClass r in {get = super.get, inc = super.inc, reset = l _:Unit. r.x := succ 0}
15 | (* object generator: *)
16 | newResetCounter = l _:Unit. let r = {x = ref succ 0} in resetCounterClass r
17 |
18 | (* Another class that inherits from newResetCounter and adds a dec operation: *)
19 | decResetCounterClass = l r:{x:Ref Nat}. let super = resetCounterClass r in {get = super.get, inc = super.inc, reset = super.reset, dec = l _:Unit. r.x := pred(!r.x)}
20 | (* object generator: *)
21 | newDecResetCounter = l _:Unit. let r = {x = ref succ 0} in decResetCounterClass r
22 |
23 | rc = newResetCounter unit
24 | rc.inc unit; rc.inc unit; rc.get unit
25 |
26 | drs = newDecResetCounter unit
27 | drs.inc unit; drs.dec unit; drs.get unit
28 |
29 | (*
30 | * A child of resetCounterClass that add a backup operation and maitains an
31 | * extended object representation (by adding a new field to the record of
32 | * reference cells):
33 | *)
34 | backupCounterClass = l r:{x:Ref Nat, b:Ref Nat}. let super = resetCounterClass r in {get = super.get, inc = super.inc, reset = l _:Unit. r.x := (!r.b), backup = l _:Unit. r.b := (!r.x)}
35 | newBackupCounter = l _:Unit. let r = {x = ref succ 0, b = ref succ 0} in backupCounterClass r
36 |
37 | bc = newBackupCounter unit
38 | bc.get unit
39 | bc.backup unit; bc.inc unit; bc.get unit
40 | bc.reset unit; bc.get unit
41 |
42 | (* A child of backupCounterClass that calls super class's operations: *)
43 | funnyBackupCounterClass = l r:{x:Ref Nat, b:Ref Nat}. let super = backupCounterClass r in {get = super.get, inc = l _:Unit. (super.backup unit; super.inc unit), reset = super.reset, backup = super.backup}
44 | newFunnyBackupCounter = l _:Unit. let r = {x = ref succ 0, b = ref succ 0} in funnyBackupCounterClass r
45 |
--------------------------------------------------------------------------------
/ch18_fullref/examples/03_self.ml:
--------------------------------------------------------------------------------
1 | (* A simple counter class where member methods are mutually recursive: *)
2 | setCounterClass = l r:{x:Ref Nat}. fix (l self:{get:Unit->Nat, set:Nat->Unit, inc:Unit->Unit}. {get=l _:Unit. !(r.x), set=l i:Nat. r.x := i, inc=l _:Unit. self.set (succ (self.get unit))})
3 |
4 | newSetCounter = l _:Unit. let r = {x = ref succ 0} in setCounterClass r
5 |
6 | sc = newSetCounter unit
7 | sc.inc unit; sc.get unit
8 | (sc.set (succ succ succ succ 0)); sc.get unit
9 |
--------------------------------------------------------------------------------
/ch18_fullref/examples/04_self_late_binding.ml:
--------------------------------------------------------------------------------
1 | (*
2 | * This example uses late binding to enable super classes to call child classes (see tapl sec. 18.10).
3 | * It also uses thunks (see tapl, sec. 18.11) to work around divergence if fix's argument is used too early.
4 | * TODO sudy these examples carefully to make sure you understand the consequences of late binding and thunks.
5 | *)
6 | setCounterClass = l r:{x:Ref Nat}. l self:Unit->{get:Unit->Nat, set:Nat->Unit, inc:Unit->Unit}. l _:Unit. {get=(l _:Unit. !(r.x)), set=(l i:Nat. r.x:=i), inc=(l _:Unit. (self unit).set (succ((self unit).get unit)))}
7 |
8 | newSetCounter = l _:Unit. let r = {x=ref succ 0} in (fix (setCounterClass r)) unit
9 |
10 | sc = newSetCounter unit
11 | (sc.set (succ succ succ 0)); sc.get unit
12 |
13 | instrCounterClass = l r:{x:Ref Nat,a:Ref Nat}. l self:Unit->{get:Unit->Nat,set:Nat->Unit,inc:Unit->Unit,accesses:Unit->Nat}. l _:Unit. let super = setCounterClass r self unit in {get=super.get, set=l i:Nat. ((r.a:=succ(!(r.a))); (super.set i)), inc=super.inc, accesses=l _:Unit. !(r.a)}
14 |
15 | newInstrCounter = l _:Unit. let r = {x=ref succ 0, a= ref 0} in (fix (instrCounterClass r)) unit
16 |
17 | ic = newInstrCounter unit
18 |
--------------------------------------------------------------------------------
/ch18_fullref/examples/05_self_late_binding_ref.ml:
--------------------------------------------------------------------------------
1 | (*
2 | * This example avoids re-computing the method table for an object every time a
3 | * method on that object is called (like the fix approach). It does so by
4 | * allocating a cell to hold a dummy method table for each new object and pass
5 | * a reference to that cell to the class constructor.
6 | *
7 | * To support inheritence, Source references are used in order to pass a child
8 | * class' refernce (i.e. a reference to its method table) to the a parent class
9 | * constructor.
10 | *)
11 | setCounterClass = l r:{x:Ref Nat}. l self: Source {get:Unit->Nat, set:Nat->Unit, inc:Unit->Unit}. {get=l _:Unit. !(r.x), set=l i:Nat. r.x := i, inc=l _:Unit. (!self).set (succ ((!self).get unit))}
12 |
13 | dummySetCounter = {get=l _:Unit. 0, set=l i:Nat. unit, inc=l _:Unit. unit}
14 |
15 | newSetCounter = l _:Unit. let r = {x=ref succ 0} in let cAux = ref dummySetCounter in ((cAux := (setCounterClass r cAux)); !cAux)
16 |
17 | sc = newSetCounter unit
18 | (sc.set (succ succ succ 0)); sc.get unit
19 |
20 | instrCounterClass = l r:{x:Ref Nat, a:Ref Nat}. l self:Source {get:Unit->Nat, set:Nat->Unit, inc:Unit->Unit, accesses:Unit->Nat}. let super = setCounterClass r self in {get=super.get, set=l i:Nat. ((r.a := succ(!(r.a))); super.set i), inc=super.inc, accesses=l _:Unit. !(r.a)}
21 |
22 | dummyInstrCounter = {get=l _:Unit. 0, set=l i:Nat. unit, inc=l _:Unit. unit, accesses=l _:Unit. 0}
23 |
24 | newInstrCounter = l _:Unit. let r = {x=ref succ 0, a=ref 0} in let cAux = ref dummyInstrCounter in ((cAux := (instrCounterClass r cAux)); !cAux)
25 |
26 | ic = newInstrCounter unit
27 | (ic.set (succ succ succ 0)); ic.get unit
28 | ic.accesses unit
29 |
--------------------------------------------------------------------------------
/ch18_fullref/examples/example_run.log:
--------------------------------------------------------------------------------
1 | // An example run of fix that logs the intermediate evaluation steps.
2 |
3 | >> (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}}) <- succ succ succ succ 0: Bool
4 |
5 | Eval: (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}}) <- succ succ succ succ 0
6 |
7 | Eval: (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}})
8 |
9 | Eval: fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- succ succ succ succ 0
10 |
11 | Eval: fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}}
12 |
13 | Eval: {l x : Nat. if iszero x then true else if iszero (pred x) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred x)))} <- succ succ succ succ 0
14 |
15 | Eval: if iszero succ succ succ succ 0 then true else if iszero (pred succ succ succ succ 0) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ succ
16 | succ 0)))
17 |
18 | Eval: iszero succ succ succ succ 0
19 |
20 | Eval: if false then true else if iszero (pred succ succ succ succ 0) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ succ succ 0)))
21 |
22 | Eval: if iszero (pred succ succ succ succ 0) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ succ succ 0)))
23 |
24 | Eval: iszero (pred succ succ succ succ 0)
25 |
26 | Eval: (pred succ succ succ succ 0)
27 |
28 | Eval: if iszero pred succ succ succ succ 0 then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ succ succ 0)))
29 |
30 | Eval: iszero pred succ succ succ succ 0
31 |
32 | Eval: pred succ succ succ succ 0
33 |
34 | Eval: if iszero succ succ succ 0 then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ succ succ 0)))
35 |
36 | Eval: iszero succ succ succ 0
37 |
38 | Eval: if false then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ succ succ 0)))
39 |
40 | Eval: (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ succ succ 0)))
41 |
42 | Eval: fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ succ succ 0))
43 |
44 | Eval: fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}}
45 |
46 | Eval: {l x : Nat. if iszero x then true else if iszero (pred x) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred x)))} <- (pred (pred succ succ succ succ 0)
47 | )
48 |
49 | Eval: (pred (pred succ succ succ succ 0))
50 |
51 | Eval: {l x : Nat. if iszero x then true else if iszero (pred x) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred x)))} <- pred (pred succ succ succ succ 0)
52 |
53 | Eval: pred (pred succ succ succ succ 0)
54 |
55 | Eval: (pred succ succ succ succ 0)
56 |
57 | Eval: {l x : Nat. if iszero x then true else if iszero (pred x) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred x)))} <- pred pred succ succ succ succ 0
58 |
59 | Eval: pred pred succ succ succ succ 0
60 |
61 | Eval: pred succ succ succ succ 0
62 |
63 | Eval: {l x : Nat. if iszero x then true else if iszero (pred x) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred x)))} <- pred succ succ succ 0
64 |
65 | Eval: pred succ succ succ 0
66 |
67 | Eval: {l x : Nat. if iszero x then true else if iszero (pred x) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred x)))} <- succ succ 0
68 |
69 | Eval: if iszero succ succ 0 then true else if iszero (pred succ succ 0) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ 0)))
70 |
71 | Eval: iszero succ succ 0
72 |
73 | Eval: if false then true else if iszero (pred succ succ 0) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ 0)))
74 |
75 | Eval: if iszero (pred succ succ 0) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ 0)))
76 |
77 | Eval: iszero (pred succ succ 0)
78 |
79 | Eval: (pred succ succ 0)
80 |
81 | Eval: if iszero pred succ succ 0 then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ 0)))
82 |
83 | Eval: iszero pred succ succ 0
84 |
85 | Eval: pred succ succ 0
86 |
87 | Eval: if iszero succ 0 then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ 0)))
88 |
89 | Eval: iszero succ 0
90 |
91 | Eval: if false then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ 0)))
92 |
93 | Eval: (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ 0)))
94 |
95 | Eval: fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred succ succ 0))
96 |
97 | Eval: fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}}
98 |
99 | Eval: {l x : Nat. if iszero x then true else if iszero (pred x) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred x)))} <- (pred (pred succ succ 0))
100 |
101 | Eval: (pred (pred succ succ 0))
102 |
103 | Eval: {l x : Nat. if iszero x then true else if iszero (pred x) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred x)))} <- pred (pred succ succ 0)
104 |
105 | Eval: pred (pred succ succ 0)
106 |
107 | Eval: (pred succ succ 0)
108 |
109 | Eval: {l x : Nat. if iszero x then true else if iszero (pred x) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred x)))} <- pred pred succ succ 0
110 |
111 | Eval: pred pred succ succ 0
112 |
113 | Eval: pred succ succ 0
114 |
115 | Eval: {l x : Nat. if iszero x then true else if iszero (pred x) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred x)))} <- pred succ 0
116 |
117 | Eval: pred succ 0
118 |
119 | Eval: {l x : Nat. if iszero x then true else if iszero (pred x) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred x)))} <- 0
120 |
121 | Eval: if iszero 0 then true else if iszero (pred 0) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred 0)))
122 |
123 | Eval: iszero 0
124 |
125 | Eval: if true then true else if iszero (pred 0) then false else (fix {l ie : (Nat -> Bool). {l x : Nat. if iszero x then true else if iszero (pred x) then false else (ie <- (pred (pred x)))}} <- (pred (pred 0)))
126 |
127 | Eval: true
128 |
129 | => true: Bool
130 |
--------------------------------------------------------------------------------
/ch18_fullref/interpreter.cpp:
--------------------------------------------------------------------------------
1 | #include "interpreter.hpp"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | void EvaluateProgram(char* input) {
10 | parser::Parser parser{std::istringstream{input}};
11 | type_checker::TypeChecker checker;
12 | auto program = parser.ParseStatement();
13 | std::cout << " " << program << ": "
14 | << checker.TypeOf(runtime::NamedStatementStore(), program)
15 | << "\n";
16 |
17 | interpreter::Interpreter interpreter;
18 | auto res = interpreter.Interpret(program);
19 | std::cout << "=> " << res.first << ": " << res.second << "\n";
20 | }
21 |
22 | int main(int argc, char* argv[]) {
23 | if (argc == 1) {
24 | constexpr int line_size = 512;
25 | char line[line_size];
26 | interpreter::Interpreter interpreter;
27 |
28 | while (true) {
29 | try {
30 | std::cout << ">> ";
31 | std::cin.getline(&line[0], line_size);
32 | parser::Parser statement_parser(std::istringstream{line});
33 | auto statement_ast = statement_parser.ParseStatement();
34 | auto res = interpreter.Interpret(statement_ast);
35 | std::cout << "=> " << res.first << ": " << res.second << "\n";
36 | } catch (const std::exception& ex) {
37 | std::cerr << "Error: " << ex.what() << "\n";
38 | }
39 | }
40 | } else if (argc == 2) {
41 | EvaluateProgram(argv[1]);
42 | } else {
43 | // Print usage.
44 | }
45 |
46 | return 0;
47 | }
48 |
--------------------------------------------------------------------------------