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