├── .gitignore ├── Makefile ├── Readme.md ├── src ├── TinyClojure.cpp ├── TinyClojure.h └── tool.cpp ├── tests ├── repl.clj └── trip.clj └── xcode └── TinyClojure.xcodeproj ├── .gitignore └── project.pbxproj /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.o 3 | tclj 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=c++ -std=gnu++11 -stdlib=libc++ -O3 2 | 3 | all: tclj 4 | 5 | tclj: src/tool.o src/TinyClojure.o 6 | $(CC) src/*.o -o tclj 7 | 8 | src/tool.o: src/tool.cpp src/TinyClojure.h 9 | $(CC) -c src/tool.cpp -o src/tool.o 10 | 11 | src/TinyClojure.o: src/TinyClojure.cpp src/TinyClojure.h 12 | $(CC) -c src/TinyClojure.cpp -o src/TinyClojure.o 13 | 14 | test: triptest 15 | 16 | triptest: tclj 17 | ./tclj tests/trip.clj 18 | 19 | clean: 20 | rm -f src/*.o tclj 21 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # TinyClojure 2 | 3 | ### What is TinyClojure 4 | 5 | TinyClojure is a project to build a small, easily embeddable version of Clojure/ClojureScript in portable C++. In many ways it is my attempt to create a Clojure equivalent of [TinyScheme](http://tinyscheme.sourceforge.net/home.html). 6 | 7 | I started this project because I was looking for an embeddable Clojure for [Lisping](http://slidetocode.com/), an iPad Lisp development environment I develop. Having used the excellent [TinyScheme](http://tinyscheme.sourceforge.net/home.html) for the Scheme interpreter in Lisping I wanted looking for a clojure version that is just as easily embeddable. [ClojureC](https://github.com/schani/clojurec) is good, but the build process is complex, and there are external library dependencies. These are not insurmountable, but they were sufficient to discourage me from using it. 8 | 9 | The focus with TinyClojure's development is to make it the easiest way to embed Clojure within any application. Tiny Clojure consists of one header file, one source file, no external dependencies, and the extension and embedding interface is as simple as it is possible to be. All forms are subclasses of the ExtensionFunction class, whether they are builtin control statements like (if ), (cond ), (do ) or custom extension functions. This structure keeps TinyClojure's core minimal, so development is easy and contained. 10 | 11 | A warning, these are very early days for TinyClojure, the core of the interpreter works well and I have implemented the most basic functions for TinyClojure to function as a language (simple io, simple arithmetic, the most basic control structures). I am now working on filling out Clojure's Core API. Feel free to use it for whatever you would to, but do not rely on it. 12 | 13 | Obviously, this is an open source project and if you want to get involved, fork it and get going. You can get in touch with me at . The real priority for now is to fill out the standard library so that TinyClojure is usable as a language, then I would like to work on more complex issues such as threading. 14 | 15 | ### How do I embed TinyClojure in my code? 16 | 17 | Easily. 18 | 19 | tinyclojure::TinyClojure interpreter; 20 | tinyclojure::Object* code = interpreter.parse("(+ 1 2)"); 21 | tinyclojure::Object* result = interpreter.eval(code); 22 | std::cout << result->integerValue(); 23 | 24 | All objects are garbage collected by the interpreter itself, so there is no need to delete them. 25 | 26 | 27 | ### Exactly what language are you targeting? 28 | 29 | Ostensibly Clojure, I would say ClojureScript, but when I last looked, ClojureScript lacked the (eval ) form, and for me, LISP without (eval ) is no LISP at all. Right now, I'm looking to implement the Clojure language itself (without namespaces, imports or any of the Java interface), and its core library in [the Clojure.Core API](http://richhickey.github.com/clojure/clojure.core-api.html). 30 | 31 | ### TODO 32 | 33 | This project is very young, with a very vague and incomplete TODO list. 34 | 35 | ###### Immediate tasks 36 | 37 | 38 | ###### Forms to implement 39 | 40 | * recurse structures 41 | * map 42 | * many more, import and prioritise the Clojure.core AOI 43 | 44 | ###### Minor 45 | 46 | * fix the bugs with lists in stringRepresentation() 47 | * sort :else case in a (cond ) statement 48 | * swap all return NULLs for return nil object 49 | * shouldn't need to reset the interpreter when an extension function is added 50 | * work on the command line interface 51 | * correct the (print ) form to 52 | * parse \n, \r, \t, etc. correctly in strings 53 | * variable length argument lists to fn (the &) 54 | 55 | ###### Major 56 | 57 | * implement all datastructures (the various hashmaps, etc.) 58 | * full numeric stack. Only cater for integers and floats right now, need fractions 59 | * Test suite. This is sadly lacking right now. test.clj is the beginnings of my testing 60 | * refactoring. C++ is not my "first language" in the programming world, so any refactors to make it more idiomatic would be appreciated. 61 | * Garbage collector. Right now there is no garbage collector to speak of. Objects collect until the interpreter destructs, then they are deleted. 62 | * Parser rewrite. I converted the parser from the tolerant parser used in Lisping. It is neither elegant, nor an appropriate design. I would like to replace it with something more elegant once this interpreter is up and running. 63 | * Implement all the Clojure.Core functions. 64 | * Better error reporting 65 | * Macros 66 | 67 | ###### Oneday 68 | 69 | * A more efficient structure. TinyClojure converts code into Clojure data, then recursively evalutes it. This eats stack space and it is not efficient either in terms of speed or memory. A bytecode structure would increase the size of the source code somewhat, but it would greatly increase efficiency. 70 | 71 | ### Coding conventions 72 | 73 | Roughly, my coding conventions are 74 | 75 | * Keep everything in the "tinyclojure" namespace please. 76 | * Datatypes start with a capital, member variables with an underscore. 77 | * Indent by four spaces, no tabs. 78 | * No macros (I'm talking about the C++ not the Lisp of course). 79 | * Document all interfaces with Doxygen style comments. 80 | * Add each feature to trip.clj (in as diabolic a manner as you please) 81 | * All variable and method names must be descriptive. No one letter variables please. Comment code when necessary, but if you can clarify the operation of your code through the method and variables names instead, that is better. 82 | * Git 83 | * If possible no broken commits, it is a disaster when git bisecting. 84 | 85 | Less defined principles are 86 | 87 | * Order of precedence 88 | * Hackability. No extension language is at all useful unless it is easy to read, fix and extend. 89 | * Ease of embedding. One .h, one .cpp and four lines of code should be all you need to embed this in your code. 90 | * Small compiled size. I'm not aiming to match TinyScheme's minute footprint, but noone likes big bulky software. 91 | * Startup speed. In general I'm not worried about speed, but an interpreter cannot be regarded as lightweight if takes a long time to construct and initialise. 92 | * Execution speed. I'm willing to relax the small size rule a little for large performance gains, but this interpreter is only meant for small scripts. Execution speed just isn't that important. 93 | * If you want to add functionality that violates the golden rule, then as long as it can be turned on and off with an #ifdef then that is fine. 94 | 95 | 96 | ### Hacking TinyClojure 97 | 98 | Tiny Clojure is designed to be as hackable as possible, and all interfaces are documented with Doxygen to make this as accessible as possible. However here are a few bullet points to help you before you dive into the source. 99 | 100 | * `Object` is the fundamental dynamic type in TinyClojure. All code, data and functions (whether closure or builtins) are instances of this type. The convention is that when creating any object, it must be registered with the garbage collector. 101 | * Parsing and Evaluation are handled by The `TinyClojure` object. Pass a string to the parse function and it will return the parsed source code as a data structure. To execute this code, call eval on that data. Internally there is both a scoped and an unscoped eval function. Scoped eval is a wrapper around unscoped eval that creates a new scope before evaluating the code. By default use this as it will 102 | * Interpreter scope objects represent the current "scope", these are simply wrappers around a dictionary of defined names and values 103 | * subclasses of ExtensionFunction are responsible for all builtin forms, and they are the mechanism for extending the language. To add a form, create a subclass of ExtensionFunction and register it with the TinyClojure object 104 | * The number class is a type wrapping Clojure's "numeric tower" 105 | 106 | ### Contact 107 | 108 | If you want to contact me, email me at slidetocode at gmail dot com 109 | 110 | 111 | ### License 112 | 113 | Tiny clojure is licensed under the standard MIT license so that it can be used anywhere, for anything, as long as you keep the copyright notice. Nevertheless, pull requests would be greatly appreciated if you improve TinyClojure. -------------------------------------------------------------------------------- /src/TinyClojure.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 Duncan Steele 2 | // http://slidetocode.com 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining 5 | // a copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be 13 | // included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | // 23 | // TinyClojure.cpp 24 | // TinyClojure 25 | // 26 | // Created by Duncan Steele on 20/10/2012. 27 | // 28 | 29 | #include "TinyClojure.h" 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | namespace tinyclojure { 37 | 38 | #pragma mark - 39 | #pragma mark Number 40 | 41 | Number::Number(double val) { 42 | setFloating(val); 43 | } 44 | 45 | Number::Number(int val) { 46 | setInteger(val); 47 | } 48 | 49 | Number::Number() { 50 | setInteger(0); 51 | } 52 | 53 | Number::Number(Number* oldNum) { 54 | _mode = oldNum->_mode; 55 | _value = oldNum->_value; 56 | } 57 | 58 | double Number::floatingValue() const { 59 | switch (_mode) { 60 | case kNumberModeFloating: 61 | return _value.floating; 62 | break; 63 | 64 | case kNumberModeInteger: 65 | return (double)_value.integer; 66 | break; 67 | } 68 | } 69 | 70 | Number Number::floatingVersion() const { 71 | return Number(floatingValue()); 72 | } 73 | 74 | int Number::integerValue() const { 75 | switch (_mode) { 76 | case kNumberModeInteger: 77 | return _value.integer; 78 | break; 79 | 80 | case kNumberModeFloating: 81 | return (int)std::round(_value.floating); 82 | break; 83 | } 84 | } 85 | 86 | Number Number::integerVersion() const { 87 | return Number(floatingValue()); 88 | } 89 | 90 | Number Number::operator+(const Number& rhs) const { 91 | Number ilhs,irhs; 92 | equivalentModes(*this, rhs, ilhs, irhs); 93 | 94 | switch (ilhs._mode) { 95 | case kNumberModeFloating: 96 | return Number(ilhs._value.floating + irhs._value.floating); 97 | break; 98 | 99 | case kNumberModeInteger: 100 | return Number(ilhs._value.integer + irhs._value.integer); 101 | break; 102 | } 103 | } 104 | 105 | Number Number::operator*(const Number& rhs) const { 106 | Number ilhs,irhs; 107 | equivalentModes(*this, rhs, ilhs, irhs); 108 | 109 | switch (ilhs._mode) { 110 | case kNumberModeFloating: 111 | return Number(ilhs._value.floating * irhs._value.floating); 112 | break; 113 | 114 | case kNumberModeInteger: 115 | return Number(ilhs._value.integer * irhs._value.integer); 116 | break; 117 | } 118 | } 119 | 120 | Number Number::operator/(const Number& rhs) const { 121 | return Number(floatingValue() / rhs.floatingValue()); 122 | } 123 | 124 | Number Number::operator-(const Number& rhs) const { 125 | Number ilhs,irhs; 126 | equivalentModes(*this, rhs, ilhs, irhs); 127 | 128 | switch (ilhs._mode) { 129 | case kNumberModeFloating: 130 | return Number(ilhs._value.floating - irhs._value.floating); 131 | break; 132 | 133 | case kNumberModeInteger: 134 | return Number(ilhs._value.integer - irhs._value.integer); 135 | break; 136 | } 137 | } 138 | 139 | void Number::equivalentModes(const Number& originalLHS, const Number& originalRHS, Number& newLHS, Number& newRHS) { 140 | if (originalLHS._mode==originalRHS._mode) { 141 | newLHS=originalLHS; 142 | newRHS=originalRHS; 143 | } else { 144 | newLHS = originalLHS.floatingVersion(); 145 | newRHS = originalRHS.floatingVersion(); 146 | } 147 | } 148 | 149 | void Number::setFloating(double val) { 150 | _value.floating = val; 151 | _mode = kNumberModeFloating; 152 | } 153 | 154 | void Number::setInteger(int val) { 155 | _value.integer = val; 156 | _mode = kNumberModeInteger; 157 | } 158 | 159 | std::string Number::stringRepresentation() const { 160 | std::stringstream stringBuilder; 161 | 162 | switch (_mode) { 163 | case kNumberModeFloating: 164 | stringBuilder << _value.floating; 165 | break; 166 | 167 | case kNumberModeInteger: 168 | stringBuilder << _value.integer; 169 | break; 170 | } 171 | 172 | return stringBuilder.str(); 173 | } 174 | 175 | bool Number::operator==(const Number& rhs) const { 176 | Number ilhs,irhs; 177 | equivalentModes(*this, rhs, ilhs, irhs); 178 | 179 | switch (ilhs._mode) { 180 | case kNumberModeFloating: 181 | return ilhs._value.floating == irhs._value.floating; 182 | break; 183 | 184 | case kNumberModeInteger: 185 | return ilhs._value.integer == irhs._value.integer; 186 | break; 187 | } 188 | } 189 | 190 | bool Number::operator<(const Number& rhs) const { 191 | Number ilhs,irhs; 192 | equivalentModes(*this, rhs, ilhs, irhs); 193 | 194 | switch (ilhs._mode) { 195 | case kNumberModeFloating: 196 | return ilhs._value.floating < irhs._value.floating; 197 | break; 198 | 199 | case kNumberModeInteger: 200 | return ilhs._value.integer < irhs._value.integer; 201 | break; 202 | } 203 | } 204 | 205 | bool Number::operator>(const Number& rhs) const { 206 | Number ilhs,irhs; 207 | equivalentModes(*this, rhs, ilhs, irhs); 208 | 209 | switch (ilhs._mode) { 210 | case kNumberModeFloating: 211 | return ilhs._value.floating > irhs._value.floating; 212 | break; 213 | 214 | case kNumberModeInteger: 215 | return ilhs._value.integer > irhs._value.integer; 216 | break; 217 | } 218 | } 219 | 220 | bool Number::operator<=(const Number& rhs) const { 221 | Number ilhs,irhs; 222 | equivalentModes(*this, rhs, ilhs, irhs); 223 | 224 | switch (ilhs._mode) { 225 | case kNumberModeFloating: 226 | return ilhs._value.floating <= irhs._value.floating; 227 | break; 228 | 229 | case kNumberModeInteger: 230 | return ilhs._value.integer <= irhs._value.integer; 231 | break; 232 | } 233 | } 234 | 235 | bool Number::operator>=(const Number& rhs) const { 236 | Number ilhs,irhs; 237 | equivalentModes(*this, rhs, ilhs, irhs); 238 | 239 | switch (ilhs._mode) { 240 | case kNumberModeFloating: 241 | return ilhs._value.floating >= irhs._value.floating; 242 | break; 243 | 244 | case kNumberModeInteger: 245 | return ilhs._value.integer >= irhs._value.integer; 246 | break; 247 | } 248 | } 249 | 250 | bool Number::operator!=(const Number& rhs) const { 251 | return !operator==(rhs); 252 | } 253 | 254 | Number::NumberMode Number::getMode() const { 255 | return _mode; 256 | } 257 | 258 | void Number::roundUp() { 259 | if (_mode == kNumberModeFloating) { 260 | setInteger(std::ceil(_value.floating)); 261 | } 262 | } 263 | 264 | void Number::roundDown() { 265 | if (_mode == kNumberModeFloating) { 266 | setInteger(std::floor(_value.floating)); 267 | } 268 | } 269 | 270 | #pragma mark - ExtensionFunction 271 | 272 | void ExtensionFunction::garbageCollector(GarbageCollector *gc_long, GarbageCollector *gc_short) { 273 | _gc_long = gc_long; 274 | _gc_short = gc_short; 275 | } 276 | 277 | void ExtensionFunction::evaluator(TinyClojure *evaluator) { 278 | _evaluator = evaluator; 279 | } 280 | 281 | void ExtensionFunction::setIOProxy(IOProxy *ioProxy) { 282 | _ioProxy = ioProxy; 283 | } 284 | 285 | bool ExtensionFunction::validateArgumentTypes(std::vector& typeArray) { 286 | if (_typeArray.size()==0) 287 | return true; 288 | 289 | if (_typeArray.size() != typeArray.size()) 290 | return false; 291 | 292 | for (int typeIndex = 0; typeIndex < _typeArray.size(); ++typeIndex) { 293 | if (typeArray[typeIndex] != _typeArray[typeIndex]) { 294 | return false; 295 | } 296 | } 297 | 298 | return true; 299 | } 300 | 301 | int ExtensionFunction::requiredNumberOfArguments() { 302 | if (_typeArray.size()) { 303 | return (int)_typeArray.size(); 304 | } 305 | 306 | return -1; 307 | } 308 | 309 | void ExtensionFunction::setup() { 310 | _typeArray.clear(); 311 | fillTypeArray(); 312 | } 313 | 314 | #pragma mark - 315 | #pragma mark Standard Library 316 | 317 | namespace core { 318 | /// function for adding a list of numbers 319 | class Arithmetic : public ExtensionFunction { 320 | virtual Number numberOperation(Number lhs, Number rhs) = 0; 321 | 322 | int minimumNumberOfArguments() { 323 | return 1; 324 | } 325 | 326 | Object* execute(ObjectList arguments, InterpreterScope* interpreterState) { 327 | bool first = true; 328 | 329 | Number current; 330 | 331 | for (int argumentIndex = 0; argumentIndex < arguments.size(); ++argumentIndex) { 332 | Object *evaluatedArgument = arguments[argumentIndex]; 333 | if (evaluatedArgument->type() != Object::kObjectTypeNumber) { 334 | throw Error("Arithmetic functions require all arguments to be numbers"); 335 | } 336 | 337 | if (first) { 338 | current = evaluatedArgument->numberValue(); 339 | first = false; 340 | } else { 341 | current = numberOperation(current, evaluatedArgument->numberValue()); 342 | } 343 | } 344 | 345 | return _gc_short->registerObject(new Object(current)); 346 | } 347 | }; 348 | 349 | class Plus : public Arithmetic { 350 | std::string functionName() { 351 | return std::string("+"); 352 | } 353 | 354 | virtual Number numberOperation(Number lhs, Number rhs) { 355 | return lhs + rhs; 356 | } 357 | }; 358 | 359 | class Minus : public Arithmetic { 360 | std::string functionName() { 361 | return "-"; 362 | } 363 | 364 | virtual Number numberOperation(Number lhs, Number rhs) { 365 | return lhs - rhs; 366 | } 367 | }; 368 | 369 | class Multiply : public Arithmetic { 370 | std::string functionName() { 371 | return "*"; 372 | } 373 | 374 | virtual Number numberOperation(Number lhs, Number rhs) { 375 | return lhs * rhs; 376 | } 377 | }; 378 | 379 | class Divide : public Arithmetic { 380 | std::string functionName() { 381 | return "/"; 382 | } 383 | 384 | virtual Number numberOperation(Number lhs, Number rhs) { 385 | return lhs / rhs; 386 | } 387 | }; 388 | 389 | class If : public ExtensionFunction { 390 | std::string functionName() { 391 | return "if"; 392 | } 393 | 394 | int minimumNumberOfArguments() { 395 | return 2; 396 | } 397 | 398 | int maximumNumberOfArguments() { 399 | return 3; 400 | } 401 | 402 | bool preEvaluateArguments() { 403 | return false; 404 | } 405 | 406 | Object *execute(ObjectList arguments, InterpreterScope* interpreterState) { 407 | Object *condition = arguments[0], 408 | *trueBranch = arguments[1], 409 | *falseBranch = NULL; 410 | 411 | if (arguments.size()==3) { 412 | falseBranch = arguments[2]; 413 | } else { 414 | falseBranch = _gc_short->registerObject(new Object()); 415 | } 416 | 417 | Object *evaluatedCondition = _evaluator->scopedEval(interpreterState, condition); 418 | if (evaluatedCondition->coerceBoolean()) { 419 | return _evaluator->scopedEval(interpreterState, trueBranch); 420 | } else { 421 | return _evaluator->scopedEval(interpreterState, falseBranch); 422 | } 423 | } 424 | }; 425 | 426 | class Equality : public ExtensionFunction { 427 | std::string functionName() { 428 | return "="; 429 | } 430 | 431 | int minimumNumberOfArguments() { 432 | return 1; 433 | } 434 | 435 | bool preEvaluateArguments() { 436 | return false; 437 | } 438 | 439 | Object *execute(ObjectList arguments, InterpreterScope* interpreterState) { 440 | Object *lhs = _evaluator->scopedEval(interpreterState, arguments[0]); 441 | 442 | for (int argumentIndex = 1; argumentIndex < arguments.size(); ++argumentIndex) { 443 | Object *rhs = _evaluator->scopedEval(interpreterState, arguments[argumentIndex]); 444 | 445 | if (*lhs!=*rhs) { 446 | return _gc_short->registerObject(new Object(false)); 447 | } 448 | } 449 | 450 | return _gc_short->registerObject(new Object(true)); 451 | } 452 | }; 453 | 454 | class NotEqual : public ExtensionFunction { 455 | std::string functionName() { 456 | return "not="; 457 | } 458 | 459 | int minimumNumberOfArguments() { 460 | return 1; 461 | } 462 | 463 | bool preEvaluateArguments() { 464 | return false; 465 | } 466 | 467 | Object *execute(ObjectList arguments, InterpreterScope* interpreterState) { 468 | Object *lhs = _evaluator->scopedEval(interpreterState, arguments[0]); 469 | 470 | for (int argumentIndex = 1; argumentIndex < arguments.size(); ++argumentIndex) { 471 | Object *rhs = _evaluator->scopedEval(interpreterState, arguments[argumentIndex]); 472 | 473 | if (*lhs==*rhs) { 474 | return _gc_short->registerObject(new Object(false)); 475 | } 476 | } 477 | 478 | return _gc_short->registerObject(new Object(true)); 479 | } 480 | }; 481 | 482 | class NumericInequality : public ExtensionFunction { 483 | Object *execute(ObjectList arguments, InterpreterScope* interpreterState) { 484 | ObjectList evaluatedArguments; 485 | for (int argumentIndex=0; argumentIndexscopedEval(interpreterState, arguments[argumentIndex])); 487 | 488 | if (evaluatedArguments.back()->type() != Object::kObjectTypeNumber) { 489 | std::stringstream stringBuilder; 490 | 491 | stringBuilder << "Arguments to " 492 | << functionName() 493 | << " must all be numeric"; 494 | 495 | throw Error(stringBuilder.str()); 496 | } 497 | } 498 | 499 | Object *lhs = _evaluator->scopedEval(interpreterState, arguments[0]); 500 | 501 | for (int argumentIndex = 1; argumentIndex < arguments.size(); ++argumentIndex) { 502 | Object *rhs = _evaluator->scopedEval(interpreterState, arguments[argumentIndex]); 503 | 504 | if (!comparison(lhs->numberValue(), rhs->numberValue())) { 505 | return _gc_short->registerObject(new Object(false)); 506 | } 507 | } 508 | 509 | return _gc_short->registerObject(new Object(true)); 510 | } 511 | 512 | bool preEvaluateArguments() { 513 | return false; 514 | } 515 | 516 | protected: 517 | virtual bool comparison(Number lhs, Number rhs) = 0; 518 | }; 519 | 520 | class LessThan : public NumericInequality { 521 | std::string functionName() { 522 | return "<"; 523 | } 524 | 525 | bool comparison(Number lhs, Number rhs) { 526 | return lhs < rhs; 527 | } 528 | }; 529 | 530 | class GreaterThan : public NumericInequality { 531 | std::string functionName() { 532 | return ">"; 533 | } 534 | 535 | bool comparison(Number lhs, Number rhs) { 536 | return lhs > rhs; 537 | } 538 | }; 539 | 540 | class LessThanOrEqual : public NumericInequality { 541 | std::string functionName() { 542 | return "<="; 543 | } 544 | 545 | bool comparison(Number lhs, Number rhs) { 546 | return lhs <= rhs; 547 | } 548 | }; 549 | 550 | class GreaterThanOrEqual : public NumericInequality { 551 | std::string functionName() { 552 | return ">="; 553 | } 554 | 555 | bool comparison(Number lhs, Number rhs) { 556 | return lhs >= rhs; 557 | } 558 | }; 559 | 560 | class Vector : public ExtensionFunction { 561 | std::string functionName() { 562 | return "vector"; 563 | } 564 | 565 | Object* execute(ObjectList arguments, InterpreterScope *interpreterState) { 566 | return _gc_short->registerObject(new Object(arguments)); 567 | } 568 | }; 569 | 570 | class List : public ExtensionFunction { 571 | std::string functionName() { 572 | return "list"; 573 | } 574 | 575 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 576 | Object *null = _gc_short->registerObject(new Object()); 577 | 578 | if (arguments.size()==0) { 579 | return _gc_short->registerObject(new Object(null,null)); 580 | } else if (arguments.size()==1) { 581 | return _gc_short->registerObject(new Object(arguments[0],null)); 582 | } else { 583 | Object *rhs = null; 584 | 585 | for (long argumentIndex = arguments.size()-1; argumentIndex >= 0; --argumentIndex) { 586 | Object *lhs = arguments[argumentIndex]; 587 | 588 | rhs = _gc_short->registerObject(new Object(lhs, rhs)); 589 | } 590 | 591 | return rhs; 592 | } 593 | } 594 | }; 595 | 596 | class Cons : public ExtensionFunction { 597 | std::string functionName() { 598 | return "cons"; 599 | } 600 | 601 | int requiredNumberOfArguments() { 602 | return 2; 603 | } 604 | 605 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 606 | return _gc_short->registerObject(new Object(arguments[0], arguments[1])); 607 | } 608 | }; 609 | 610 | class Print : public ExtensionFunction { 611 | std::string functionName() { 612 | return "print"; 613 | } 614 | 615 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 616 | for (int argumentIndex=0; argumentIndexstringValue(); 618 | 619 | _ioProxy->writeOut(thisArgument); 620 | 621 | // Only print out a space as long as this is not the last argument to print 622 | if (argumentIndex != (arguments.size()-1)) { 623 | _ioProxy->writeOut(" "); 624 | } 625 | } 626 | 627 | return _gc_short->registerObject(new Object()); 628 | } 629 | }; 630 | 631 | 632 | class Println : public ExtensionFunction { 633 | std::string functionName() { 634 | return "println"; 635 | } 636 | 637 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 638 | for (int argumentIndex=0; argumentIndexstringValue(); 640 | 641 | _ioProxy->writeOut(thisArgument); 642 | 643 | // Only print out a space as long as this is not the last argument to print 644 | if (argumentIndex != (arguments.size()-1)) { 645 | _ioProxy->writeOut(" "); 646 | } 647 | } 648 | 649 | _ioProxy->writeOut("\n"); 650 | 651 | return _gc_short->registerObject(new Object()); 652 | } 653 | 654 | }; 655 | 656 | class PrintStr : public ExtensionFunction { 657 | std::string functionName() { 658 | return "print-str"; 659 | } 660 | 661 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 662 | 663 | std::string stringToPrint; 664 | 665 | for (int argumentIndex=0; argumentIndexstringValue(); 667 | 668 | stringToPrint.append(thisArgument); 669 | 670 | // Only add a space as long as this is not the last argument to append 671 | if (argumentIndex != (arguments.size()-1)) { 672 | stringToPrint.append(" "); 673 | } 674 | } 675 | 676 | return _gc_short->registerObject(new Object(stringToPrint)); 677 | } 678 | 679 | }; 680 | 681 | class PrintlnStr : public ExtensionFunction { 682 | std::string functionName() { 683 | return "println-str"; 684 | } 685 | 686 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 687 | 688 | std::string stringToPrint; 689 | 690 | for (int argumentIndex=0; argumentIndexstringValue(); 692 | 693 | stringToPrint.append(thisArgument); 694 | 695 | // Only add a space as long as this is not the last argument to append 696 | if (argumentIndex != (arguments.size()-1)) { 697 | stringToPrint.append(" "); 698 | } 699 | } 700 | 701 | stringToPrint.append("\n"); 702 | 703 | return _gc_short->registerObject(new Object(stringToPrint)); 704 | } 705 | 706 | }; 707 | 708 | class Str : public ExtensionFunction { 709 | std::string functionName() { 710 | return "str"; 711 | } 712 | 713 | Object *execute(ObjectList arguments, InterpreterScope *interpreterScope) { 714 | 715 | std::string result; 716 | 717 | // no arguments or only nil argument, return empty string 718 | if (arguments.size() == 0 || (arguments.size() == 1 && arguments[0]->type() == Object::kObjectTypeNil)) { 719 | result = ""; 720 | } else if (arguments.size() == 1) { 721 | // one argument return the string representation of the input 722 | result.append(arguments[0]->stringValue()); 723 | } else { 724 | // more than one argument concatenate the strings and return them 725 | for (int argumentIndex = 0; argumentIndexstringValue()); 727 | } 728 | } 729 | 730 | return _gc_short->registerObject(new Object(result)); 731 | } 732 | 733 | }; 734 | 735 | class Count : public ExtensionFunction { 736 | std::string functionName() { 737 | return "count"; 738 | } 739 | 740 | int requiredNumberOfArguments() { 741 | return 1; 742 | } 743 | 744 | bool validateArgumentTypes(std::vector& typeArray) { 745 | 746 | if (typeArray[0] == Object::kObjectTypeNil || typeArray[0] == Object::kObjectTypeString || typeArray[0] == Object::kObjectTypeCons) { 747 | return true; 748 | } else { 749 | return false; 750 | } 751 | } 752 | 753 | Object *execute(ObjectList arguments, InterpreterScope *interpreterScope) { 754 | 755 | int result; 756 | 757 | switch (arguments[0]->type()) { 758 | case Object::kObjectTypeNil: 759 | result = 0; 760 | break; 761 | case Object::kObjectTypeString: 762 | result = arguments[0]->stringValue().length(); 763 | break; 764 | case Object::kObjectTypeVector: 765 | result = arguments[0]->vectorValue().size(); 766 | break; 767 | default: 768 | break; 769 | } 770 | 771 | return _gc_short->registerObject(new Object(result)); 772 | 773 | } 774 | }; 775 | 776 | class Compare : public ExtensionFunction { 777 | std::string functionName() { 778 | return "compare"; 779 | } 780 | 781 | int requiredNumberOfArguments() { 782 | return 2; 783 | } 784 | 785 | bool validateArgumentTypes(std::vector& typeArray) { 786 | 787 | if (typeArray[0] == Object::kObjectTypeNil || typeArray[0] == Object::kObjectTypeString || typeArray[0] == Object::kObjectTypeCons) { 788 | return true; 789 | } else { 790 | return false; 791 | } 792 | } 793 | 794 | Object *execute(ObjectList arguments, InterpreterScope *interpreterScope) { 795 | 796 | if ((arguments[0]->type() == Object::kObjectTypeString && arguments[1]->type() == Object::kObjectTypeVector) || (arguments[0]->type() == Object::kObjectTypeVector && arguments[1]->type() == Object::kObjectTypeString)) { 797 | std::stringstream stringbuilder; 798 | 799 | stringbuilder << "You may not compare a string and a vector"; 800 | 801 | throw Error(stringbuilder.str()); 802 | } 803 | 804 | int result; 805 | 806 | 807 | if (arguments[0]->type() == Object::kObjectTypeNil) { 808 | // If the first is nil 809 | 810 | if (arguments[1]->type() == Object::kObjectTypeNil) { 811 | // and the second we know they are the same 812 | result = 0; 813 | } else { 814 | // If not nil then it is automatically lower than the rhs 815 | result = -1; 816 | } 817 | 818 | } else if (arguments[1]->type() == Object::kObjectTypeNil) { 819 | 820 | // Lhs is automatically greater than rhs as lhs is not nil and rhs is nil 821 | result = 1; 822 | 823 | } else { 824 | 825 | if (arguments[0]->type() == Object::kObjectTypeString || arguments[0]->type() == Object::kObjectTypeString) { 826 | // Handle comparing strings 827 | result = arguments[0]->stringValue().compare(arguments[1]->stringValue()); 828 | 829 | } else { 830 | result = arguments[0]->vectorValue().size() - arguments[1]->vectorValue().size(); 831 | } 832 | } 833 | 834 | return _gc_short->registerObject(new Object(result)); 835 | } 836 | 837 | }; 838 | 839 | class Subs : public ExtensionFunction { 840 | std::string functionName() { 841 | return "subs"; 842 | } 843 | 844 | int minimumNumberOfArguments() { 845 | return 2; 846 | } 847 | 848 | int maximumNumberOfArguments() { 849 | return 3; 850 | } 851 | 852 | bool validateArgumentTypes(std::vector& typeArray) { 853 | 854 | if (typeArray[0] == Object::kObjectTypeString && typeArray[1] == Object::kObjectTypeNumber) { 855 | 856 | if (typeArray.size() == 3) { 857 | if (typeArray[2] == Object::kObjectTypeNumber) { 858 | return true; 859 | } else { 860 | return false; 861 | } 862 | } else { 863 | return true; 864 | } 865 | } else { 866 | return false; 867 | } 868 | } 869 | 870 | Object *execute(ObjectList arguments, InterpreterScope *interpreterScope) { 871 | 872 | std::string result; 873 | 874 | if (arguments.size() == 3) { 875 | result = arguments[0]->stringValue().substr(arguments[1]->numberValue().integerValue(), (arguments[2]->numberValue().integerValue()-arguments[1]->numberValue().integerValue())); 876 | } else { 877 | result = arguments[0]->stringValue().substr(arguments[1]->numberValue().integerValue(), std::string::npos); 878 | } 879 | 880 | return _gc_short->registerObject(new Object(result)); 881 | } 882 | 883 | }; 884 | 885 | class Quot : public ExtensionFunction { 886 | std::string functionName() { 887 | return "quot"; 888 | } 889 | 890 | int requiredNumberOfArguments() { 891 | return 2; 892 | } 893 | 894 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 895 | // Divide the two numbers passed as arguments 896 | Number result = arguments[0]->numberValue() / arguments[1]->numberValue(); 897 | 898 | if (result.floatingValue() >= 0) { 899 | // Round down to the nearest integer 900 | result.roundDown(); 901 | } else { 902 | // Round up to the nearest integer 903 | result.roundUp(); 904 | } 905 | 906 | return _gc_short->registerObject(new Object(result.integerValue())); 907 | } 908 | }; 909 | 910 | class Rem : public ExtensionFunction { 911 | std::string functionName() { 912 | return "rem"; 913 | } 914 | 915 | int requiredNumberOfArguments() { 916 | return 2; 917 | } 918 | 919 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 920 | // Divide the two numbers passed as arguments 921 | Number result = arguments[0]->numberValue() / arguments[1]->numberValue(); 922 | 923 | if (result.floatingValue() >= 0) { 924 | // Round down to the nearest integer 925 | result.roundDown(); 926 | } else { 927 | // Round up to the nearest integer 928 | result.roundUp(); 929 | } 930 | 931 | Number remainder = arguments[0]->numberValue() - (result * arguments[1]->numberValue()); 932 | 933 | return _gc_short->registerObject(new Object(remainder)); 934 | } 935 | }; 936 | 937 | class Inc : public ExtensionFunction { 938 | std::string functionName() { 939 | return "inc"; 940 | } 941 | 942 | int requiredNumberOfArguments() { 943 | return 1; 944 | } 945 | 946 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 947 | 948 | Number result = arguments[0]->numberValue() + Number(1); 949 | 950 | return _gc_short->registerObject(new Object(result)); 951 | 952 | } 953 | }; 954 | 955 | class Dec : public ExtensionFunction { 956 | std::string functionName() { 957 | return "dec"; 958 | } 959 | 960 | int requiredNumberOfArguments() { 961 | return 1; 962 | } 963 | 964 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 965 | 966 | Number result = arguments[0]->numberValue() - Number(1); 967 | 968 | return _gc_short->registerObject(new Object(result)); 969 | 970 | } 971 | }; 972 | 973 | class Max : public ExtensionFunction { 974 | std::string functionName() { 975 | return "max"; 976 | } 977 | 978 | int minimumNumberOfArguments() { 979 | return 1; 980 | } 981 | 982 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 983 | 984 | Number maxVal = arguments[0]->numberValue(); 985 | 986 | for (int i=0; inumberValue() > maxVal) { 989 | maxVal = arguments[i]->numberValue(); 990 | } 991 | } 992 | 993 | return _gc_short->registerObject(new Object(maxVal)); 994 | } 995 | }; 996 | 997 | class Min : public ExtensionFunction { 998 | std::string functionName() { 999 | return "min"; 1000 | } 1001 | 1002 | int minimumNumberOfArguments() { 1003 | return 1; 1004 | } 1005 | 1006 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1007 | 1008 | Number minVal = arguments[0]->numberValue(); 1009 | 1010 | for (int i=0; inumberValue() < minVal) { 1013 | minVal = arguments[i]->numberValue(); 1014 | } 1015 | } 1016 | 1017 | return _gc_short->registerObject(new Object(minVal)); 1018 | 1019 | } 1020 | }; 1021 | 1022 | class LoadFile : public ExtensionFunction { 1023 | std::string functionName() { 1024 | return "load-file"; 1025 | } 1026 | 1027 | int requiredNumberOfArguments() { 1028 | return 1; 1029 | } 1030 | 1031 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1032 | 1033 | std::ifstream myFile(arguments[0]->stringValue()); 1034 | 1035 | // Error handling if unable to open the file 1036 | if (myFile.fail()) { 1037 | std::cout << "Failed to open " << arguments[0]->stringValue() << std::endl; 1038 | } 1039 | 1040 | std::string myLine; 1041 | 1042 | 1043 | while (std::getline(myFile, myLine)) { 1044 | 1045 | try { 1046 | Object *code = _evaluator->parse(myLine); 1047 | 1048 | if (code) { 1049 | if (code->type() != tinyclojure::Object::kObjectTypeNil) { 1050 | Object *resultObject = _evaluator->scopedEval(interpreterState, code); 1051 | std::cout << resultObject->stringRepresentation() << std::endl; 1052 | } 1053 | } 1054 | 1055 | } catch (tinyclojure::Error error) { 1056 | std::cout << error.position << ": " << error.message << std::endl << std::endl; 1057 | } 1058 | 1059 | } 1060 | return _gc_short->registerObject(new Object()); 1061 | } 1062 | }; 1063 | 1064 | // Hanldes only one set per string 1065 | class LoadString : public ExtensionFunction { 1066 | std::string functionName() { 1067 | return "load-string"; 1068 | } 1069 | 1070 | int requiredNumberOfArguments() { 1071 | return 1; 1072 | } 1073 | 1074 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1075 | 1076 | try { 1077 | Object *code = _evaluator->parse(arguments[0]->stringValue()); 1078 | 1079 | if (code) { 1080 | if (code->type() != tinyclojure::Object::kObjectTypeNil) { 1081 | return _gc_short->registerObject(_evaluator->scopedEval(interpreterState, code)); 1082 | } 1083 | } 1084 | 1085 | } catch (tinyclojure::Error error) { 1086 | std::cout << error.position << ": " << error.message << std::endl << std::endl; 1087 | } 1088 | 1089 | return _gc_short->registerObject(new Object()); 1090 | } 1091 | }; 1092 | 1093 | class Spit : public ExtensionFunction { 1094 | std::string functionName() { 1095 | return "spit"; 1096 | } 1097 | 1098 | int minimumNumberOfArguments() { 1099 | return 2; 1100 | } 1101 | 1102 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1103 | 1104 | std::ofstream myFile(arguments[0]->stringValue()); 1105 | myFile << arguments[1]->stringValue(); 1106 | 1107 | return _gc_short->registerObject(new Object()); 1108 | } 1109 | }; 1110 | 1111 | class Slurp : public ExtensionFunction { 1112 | std::string functionName() { 1113 | return "slurp"; 1114 | } 1115 | 1116 | int minimumNumberOfArguments() { 1117 | return 1; 1118 | } 1119 | 1120 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1121 | 1122 | std::ifstream myFile(arguments[0]->stringValue()); 1123 | std::string myLine; 1124 | std::string result; 1125 | 1126 | while (std::getline(myFile, myLine)) { 1127 | result += myLine; 1128 | } 1129 | 1130 | return _gc_short->registerObject(new Object(result)); 1131 | } 1132 | }; 1133 | 1134 | class Mod : public ExtensionFunction { 1135 | std::string functionName() { 1136 | return "mod"; 1137 | } 1138 | 1139 | int requiredNumberOfArguments() { 1140 | return 2; 1141 | } 1142 | 1143 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1144 | 1145 | // Divide the two numbers passed as arguments 1146 | Number result = arguments[0]->numberValue() / arguments[1]->numberValue(); 1147 | 1148 | if (result.floatingValue() >= 0) { 1149 | // Round down to the nearest integer 1150 | result.roundDown(); 1151 | } else { 1152 | // Round up to the nearest integer 1153 | result.roundUp(); 1154 | } 1155 | 1156 | Number remainder = arguments[0]->numberValue() - (result * arguments[1]->numberValue()); 1157 | 1158 | return _gc_short->registerObject(new Object(remainder)); 1159 | 1160 | } 1161 | }; 1162 | 1163 | // Ns-unmap function from Clojure core library 1164 | // Currently, does not take namespace as an argument (only the symbol to be undefined), as only the core library is implemented 1165 | // In the future if new namespaces are added, this function will need to be redone 1166 | class Nsunmap : public ExtensionFunction { 1167 | std::string functionName() { 1168 | return "ns-unmap"; 1169 | } 1170 | 1171 | int requiredNumberOfArguments() { 1172 | return 1; 1173 | } 1174 | 1175 | bool preEvaluateArguments() { 1176 | return false; 1177 | } 1178 | 1179 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1180 | 1181 | // Erases the symbol from the symbol table 1182 | // Returns the corresponding object for that symbol (for deletion in the garbage collector) 1183 | Object *ret = interpreterState->removeSymbol(arguments[0]->stringValue()); 1184 | 1185 | // Delete the returned object from the garbage collector 1186 | _gc_long->deleteObject(ret); 1187 | 1188 | return _gc_short->registerObject(new Object()); 1189 | } 1190 | }; 1191 | 1192 | class Def : public ExtensionFunction { 1193 | std::string functionName() { 1194 | return "def"; 1195 | } 1196 | 1197 | int requiredNumberOfArguments() { 1198 | return 2; 1199 | } 1200 | 1201 | bool preEvaluateArguments() { 1202 | return false; 1203 | } 1204 | 1205 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1206 | Object *symbol = _gc_long->registerObject(new Object(arguments[0], _gc_long)), 1207 | *value = _gc_long->registerObject(new Object(_evaluator->scopedEval(interpreterState, arguments[1]), _gc_long)); 1208 | 1209 | if (symbol->type()!=Object::kObjectTypeSymbol) { 1210 | throw Error("first argument to def must be a symbol"); 1211 | } 1212 | 1213 | interpreterState->setSymbolInScope(symbol->stringValue(), value); 1214 | 1215 | return _gc_short->registerObject(new Object()); 1216 | } 1217 | }; 1218 | 1219 | class Do : public ExtensionFunction { 1220 | std::string functionName() { 1221 | return "do"; 1222 | } 1223 | 1224 | bool preEvaluateArguments() { 1225 | return false; 1226 | } 1227 | 1228 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1229 | InterpreterScope aScope(interpreterState); 1230 | 1231 | Object *retValue = _gc_short->registerObject(new Object()); 1232 | 1233 | for (int argumentIndex = 0; argumentIndex < arguments.size(); ++argumentIndex) { 1234 | retValue = _evaluator->unscopedEval(&aScope, arguments[argumentIndex]); 1235 | } 1236 | 1237 | return retValue; 1238 | } 1239 | }; 1240 | 1241 | class Closure : public ExtensionFunction { 1242 | public: 1243 | bool preEvaluateArguments() { 1244 | return false; 1245 | } 1246 | 1247 | Object* captureState(Object *object, InterpreterScope *interpreterState) { 1248 | switch (object->type()) { 1249 | case Object::kObjectTypeBoolean: 1250 | case Object::kObjectTypeNil: 1251 | case Object::kObjectTypeNumber: 1252 | case Object::kObjectTypeString: 1253 | case Object::kObjectTypeBuiltinFunction: 1254 | case Object::kObjectTypeClosure: 1255 | return object; 1256 | break; 1257 | 1258 | case Object::kObjectTypeCons: { 1259 | Object *left = captureState(object->consValueLeft(), interpreterState), 1260 | *right = captureState(object->consValueRight(), interpreterState); 1261 | 1262 | return _gc_short->registerObject(new Object(left, right)); 1263 | } break; 1264 | 1265 | case Object::kObjectTypeVector: { 1266 | std::vector newVector; 1267 | 1268 | for (int vectorIndex = 0; vectorIndex < object->vectorValue().size(); ++vectorIndex) { 1269 | newVector.push_back(captureState(object->vectorValue()[vectorIndex], interpreterState)); 1270 | } 1271 | 1272 | return _gc_short->registerObject(new Object(newVector)); 1273 | } break; 1274 | 1275 | case Object::kObjectTypeSymbol: { 1276 | Object *lookedUpValue = interpreterState->lookupSymbol(object->stringValue()); 1277 | 1278 | if (lookedUpValue) { 1279 | return lookedUpValue; 1280 | } else { 1281 | return object; 1282 | } 1283 | } break; 1284 | } 1285 | } 1286 | 1287 | void constructArgumentList(Object *arglist, ObjectList& argumentSymbols) { 1288 | // construct the argument list 1289 | ObjectList parameterSymbols; 1290 | bool validArgumentList = false; 1291 | if (arglist->buildList(parameterSymbols)) { 1292 | if (parameterSymbols.size()) { 1293 | if (parameterSymbols[0]->type()==Object::kObjectTypeSymbol) { 1294 | if (parameterSymbols[0]->stringValue()=="vector") { 1295 | validArgumentList = true; 1296 | } 1297 | } else if (parameterSymbols[0]->type()==Object::kObjectTypeBuiltinFunction) { 1298 | if (parameterSymbols[0]->functionValueExtensionFunction()->functionName() == "vector") { 1299 | validArgumentList = true; 1300 | } 1301 | } 1302 | 1303 | } 1304 | } 1305 | 1306 | if (validArgumentList) { 1307 | // grab each individual argument following the "vector symbol" 1308 | for (int argumentIndex=1; argumentIndex < parameterSymbols.size(); ++argumentIndex) { 1309 | if (parameterSymbols[argumentIndex]->type() == Object::kObjectTypeSymbol) { 1310 | argumentSymbols.push_back(parameterSymbols[argumentIndex]); 1311 | } else { 1312 | validArgumentList = false; 1313 | } 1314 | } 1315 | } else { 1316 | throw Error("Could not build argument list"); 1317 | } 1318 | } 1319 | }; 1320 | 1321 | class Defn : public Closure { 1322 | public: 1323 | std::string functionName() { 1324 | return "defn"; 1325 | } 1326 | 1327 | int minimumNumberOfArguments() { 1328 | return 3; 1329 | } 1330 | 1331 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1332 | // symbol 1333 | Object *symbol = _gc_long->registerObject(new Object(arguments[0], _gc_long)), 1334 | *arglist = arguments[1]; 1335 | 1336 | if (symbol->type()!=Object::kObjectTypeSymbol) { 1337 | throw Error("first argument to defn must be a symbol"); 1338 | } 1339 | 1340 | // construct an argument list 1341 | ObjectList argumentSymbols; 1342 | constructArgumentList(arglist, argumentSymbols); 1343 | 1344 | // remove the initial argument and symbol, just leaving the function body 1345 | arguments.erase(arguments.begin()); 1346 | arguments.erase(arguments.begin()); 1347 | 1348 | // capture the arguments 1349 | ObjectList capturedArguments; 1350 | capturedArguments.push_back(_gc_short->registerObject(new Object("do", true))); 1351 | for (int argumentIndex = 0; argumentIndex < arguments.size(); ++argumentIndex) { 1352 | capturedArguments.push_back(captureState(arguments[argumentIndex], interpreterState)); 1353 | } 1354 | 1355 | Object *lambda = _gc_short->registerObject(new Object(_evaluator->listObject(capturedArguments), argumentSymbols)); 1356 | Object* lambda_long = _gc_long->registerObject(new Object(lambda, _gc_long)); 1357 | 1358 | interpreterState->setSymbolInScope(symbol->stringValue(), lambda_long); 1359 | 1360 | return _gc_short->registerObject(new Object()); 1361 | } 1362 | }; 1363 | 1364 | class Defmacro : public Closure { 1365 | std::string functionName() { 1366 | return "defmacro"; 1367 | } 1368 | 1369 | int minimumNumberOfArguments() { 1370 | return 3; 1371 | } 1372 | 1373 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1374 | 1375 | // Differentiate between symbols and arg list 1376 | Object *symbol = _gc_long->registerObject(new Object(arguments[0], _gc_long)), *argList = arguments[1]; 1377 | 1378 | if (symbol->type()!=Object::kObjectTypeSymbol) { 1379 | throw Error("first argument to defmacro must be a symbol"); 1380 | } 1381 | 1382 | // construct an argument list 1383 | ObjectList argumentSymbols; 1384 | constructArgumentList(argList, argumentSymbols); 1385 | 1386 | // remove the initial argument and symbol, just leaving the function body 1387 | arguments.erase(arguments.begin()); 1388 | arguments.erase(arguments.begin()); 1389 | 1390 | // capture the arguments 1391 | ObjectList capturedArguments; 1392 | capturedArguments.push_back(_gc_short->registerObject(new Object("do", true))); 1393 | for (int argumentIndex = 0; argumentIndex < arguments.size(); ++argumentIndex) { 1394 | capturedArguments.push_back(captureState(arguments[argumentIndex], interpreterState)); 1395 | } 1396 | 1397 | Object *lambda = _gc_short->registerObject(new Object(_evaluator->listObject(capturedArguments), argumentSymbols, true)); 1398 | Object* lambda_long = _gc_long->registerObject(new Object(lambda, _gc_long)); 1399 | 1400 | interpreterState->setSymbolInScope(symbol->stringValue(), lambda_long); 1401 | 1402 | return _gc_short->registerObject(new Object()); 1403 | } 1404 | 1405 | }; 1406 | 1407 | class Fn : public Closure { 1408 | std::string functionName() { 1409 | return "fn"; 1410 | } 1411 | 1412 | int minimumNumberOfArguments() { 1413 | return 2; 1414 | } 1415 | 1416 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1417 | // construct an argument list 1418 | ObjectList argumentSymbols; 1419 | constructArgumentList(arguments[0], argumentSymbols); 1420 | 1421 | // remove the initial argument, just leaving the function body 1422 | arguments.erase(arguments.begin()); 1423 | 1424 | // capture the arguments 1425 | ObjectList capturedArguments; 1426 | capturedArguments.push_back(_gc_short->registerObject(new Object("do", true))); 1427 | for (int argumentIndex = 0; argumentIndex < arguments.size(); ++argumentIndex) { 1428 | capturedArguments.push_back(captureState(arguments[argumentIndex], interpreterState)); 1429 | } 1430 | 1431 | return _gc_long->registerObject(new Object(_gc_short->registerObject(new Object(_evaluator->listObject(capturedArguments), argumentSymbols)), _gc_long)); 1432 | } 1433 | }; 1434 | 1435 | class ConsFunction : public ExtensionFunction { 1436 | int requiredNumberOfArguments() { 1437 | return 1; 1438 | } 1439 | 1440 | virtual Object* operation(Object *consObject) = 0; 1441 | 1442 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1443 | if (arguments[0]->type() != Object::kObjectTypeCons) { 1444 | std::stringstream stringBuilder; 1445 | 1446 | stringBuilder << "arguments to " 1447 | << functionName() 1448 | << "must be of cons type"; 1449 | 1450 | throw Error(stringBuilder.str()); 1451 | } 1452 | 1453 | return operation(arguments[0]); 1454 | } 1455 | }; 1456 | 1457 | class First : public ConsFunction { 1458 | std::string functionName() { 1459 | return "first"; 1460 | } 1461 | 1462 | Object *operation(Object *consObject) { 1463 | return consObject->consValueLeft(); 1464 | } 1465 | }; 1466 | 1467 | class Rest : public ConsFunction { 1468 | std::string functionName() { 1469 | return "rest"; 1470 | } 1471 | 1472 | Object *operation(Object *consObject) { 1473 | return consObject->consValueRight(); 1474 | } 1475 | }; 1476 | 1477 | class ReadString : public ExtensionFunction { 1478 | std::string functionName() { 1479 | return "read-string"; 1480 | } 1481 | 1482 | void fillTypeArray() { 1483 | _typeArray.push_back(Object::kObjectTypeString); 1484 | } 1485 | 1486 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1487 | return _evaluator->parse(arguments[0]->stringValue()); 1488 | } 1489 | }; 1490 | 1491 | class Eval : public ExtensionFunction { 1492 | std::string functionName() { 1493 | return "eval"; 1494 | } 1495 | 1496 | int requiredNumberOfArguments() { 1497 | return 1; 1498 | } 1499 | 1500 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1501 | return _evaluator->scopedEval(interpreterState, arguments[0]); 1502 | } 1503 | }; 1504 | 1505 | class ReadLine : public ExtensionFunction { 1506 | std::string functionName() { 1507 | return "read-line"; 1508 | } 1509 | 1510 | int requiredNumberOfArguments() { 1511 | return 0; 1512 | } 1513 | 1514 | Object *execute(ObjectList arguments, InterpreterScope *interpreterState) { 1515 | return _gc_short->registerObject(new Object(_ioProxy->readLine())); 1516 | } 1517 | }; 1518 | 1519 | class Cond : public ExtensionFunction { 1520 | std::string functionName() { 1521 | return "cond"; 1522 | } 1523 | 1524 | bool preEvaluateArguments() { 1525 | return false; 1526 | } 1527 | 1528 | Object* execute(ObjectList arguments, InterpreterScope* interpreterState) { 1529 | if (arguments.size() % 2 != 0) { 1530 | throw Error("The cond form requires an even number of arguemnts"); 1531 | } 1532 | 1533 | for (int firstArgumentIndex = 0; firstArgumentIndex < arguments.size(); firstArgumentIndex += 2) { 1534 | Object *firstArgument = arguments[firstArgumentIndex], 1535 | *secondArgument = arguments[firstArgumentIndex+1]; // we known arguments has even length 1536 | 1537 | if (_evaluator->scopedEval(interpreterState, firstArgument)->coerceBoolean()) { 1538 | return _evaluator->scopedEval(interpreterState, secondArgument); 1539 | } 1540 | } 1541 | 1542 | return _gc_short->registerObject(new Object()); 1543 | }; 1544 | }; 1545 | 1546 | class Let : public ExtensionFunction { 1547 | std::string functionName() { 1548 | return "let"; 1549 | } 1550 | 1551 | bool preEvaluateArguments() { 1552 | return false; 1553 | } 1554 | 1555 | int minimumNumberOfArguments() { 1556 | return 1; 1557 | } 1558 | 1559 | Object* execute(ObjectList arguments, InterpreterScope *interpreterState) { 1560 | InterpreterScope letScope(interpreterState); 1561 | 1562 | ObjectList bindings; 1563 | 1564 | // initial checks 1565 | if (!arguments[0]->buildList(bindings)) { 1566 | throw Error("First argument to let statement must be a vector of bindings"); 1567 | } 1568 | 1569 | if (bindings[0]->type() != Object::kObjectTypeSymbol) { 1570 | throw Error("First argument to let statement must be a vector of bindings"); 1571 | } 1572 | 1573 | if (bindings[0]->stringValue() != std::string("vector")) { 1574 | throw Error("First argument to let statement must be a vector of bindings"); 1575 | } 1576 | 1577 | if (!bindings.size()) { 1578 | throw Error("First argument to let statement must be a vector of bindings"); 1579 | } 1580 | 1581 | if (bindings.size() % 2 != 1) { 1582 | throw Error("First argument of let statement must consist of variables and values"); 1583 | } 1584 | 1585 | // set bindings 1586 | for (int bindingIndex = 1; bindingIndex < bindings.size(); bindingIndex += 2) { 1587 | Object *bindingSymbol = bindings[bindingIndex], 1588 | *bindingValue = bindings[bindingIndex+1], 1589 | *evaluatedBindingValue = _evaluator->scopedEval(&letScope, bindingValue); 1590 | 1591 | if (bindingSymbol->type() != Object::kObjectTypeSymbol) { 1592 | throw Error("Let bindings should consist of symbol/value pairs"); 1593 | } 1594 | 1595 | Object* newBindVal = _gc_long->registerObject(new Object(evaluatedBindingValue, _gc_long)); 1596 | 1597 | letScope.setSymbolInScope(bindingSymbol->stringValue(), newBindVal); 1598 | } 1599 | 1600 | // now evaluate the arguments in turn 1601 | Object *retValue = _gc_short->registerObject(new Object()); 1602 | 1603 | for (int argumentIndex = 1; argumentIndex < arguments.size(); ++argumentIndex) { 1604 | retValue = _evaluator->unscopedEval(&letScope, arguments[argumentIndex]); 1605 | } 1606 | 1607 | return retValue; 1608 | } 1609 | }; 1610 | 1611 | class Nth : public ExtensionFunction { 1612 | std::string functionName() { 1613 | return "nth"; 1614 | } 1615 | 1616 | int minimumNumberOfArguments() { 1617 | return 2; 1618 | } 1619 | 1620 | int maximumNumberOfArguments() { 1621 | return 3; 1622 | } 1623 | 1624 | Object* execute(ObjectList arguments, InterpreterScope *interpreterState) { 1625 | Object *collection = arguments[0], 1626 | *indexValue = arguments[1], 1627 | *defaultValue = NULL; 1628 | 1629 | if (arguments.size()==3) { 1630 | defaultValue = arguments[2]; 1631 | } 1632 | 1633 | if (indexValue->type()!=Object::kObjectTypeNumber) { 1634 | throw Error("second argument to nth must be an index"); 1635 | } 1636 | 1637 | const int index = indexValue->numberValue().integerValue(); 1638 | 1639 | ObjectList convertedList; 1640 | if (!collection->buildList(convertedList)) { 1641 | throw Error("first argument to nth must be a collection"); 1642 | } 1643 | 1644 | if (index < convertedList.size()) { 1645 | if (index >= 0) { 1646 | return convertedList[index]; 1647 | } else { 1648 | throw Error("index to nth is < 0"); 1649 | } 1650 | } else { 1651 | // out of bounds. this is an exception if no default value is supplied 1652 | if (defaultValue) { 1653 | return defaultValue; 1654 | } else { 1655 | throw Error("index to nth is out of bounds"); 1656 | } 1657 | } 1658 | } 1659 | }; 1660 | } 1661 | 1662 | #pragma mark - 1663 | #pragma mark InterpreterScope 1664 | 1665 | Object* InterpreterScope::lookupSymbolInScope(std::string symbolName) { 1666 | std::map::iterator it = _symbolTable.find(symbolName); 1667 | 1668 | if (it == _symbolTable.end()) { 1669 | return NULL; 1670 | } else { 1671 | return it->second; 1672 | } 1673 | } 1674 | 1675 | void InterpreterScope::setSymbolInScope(std::string symbolName, Object *functionValue) { 1676 | _symbolTable[symbolName] = functionValue; 1677 | } 1678 | 1679 | Object* InterpreterScope::lookupSymbol(std::string symbolName) { 1680 | Object *ret = lookupSymbolInScope(symbolName); 1681 | if (ret) { 1682 | return ret; 1683 | } else { 1684 | if (_parentScope) { 1685 | return _parentScope->lookupSymbol(symbolName); 1686 | } else { 1687 | return NULL; 1688 | } 1689 | } 1690 | } 1691 | 1692 | Object* InterpreterScope::removeSymbolInScope(std::string symbolName) { 1693 | Object* ret = lookupSymbolInScope(symbolName); 1694 | _symbolTable.erase(symbolName); 1695 | return ret; 1696 | } 1697 | 1698 | Object* InterpreterScope::removeSymbol(std::string symbolName) { 1699 | Object* ret = removeSymbolInScope(symbolName); 1700 | 1701 | if (ret) { 1702 | return ret; 1703 | } else { 1704 | if (_parentScope) { 1705 | return _parentScope->removeSymbol(symbolName); 1706 | } else { 1707 | return NULL; 1708 | } 1709 | } 1710 | } 1711 | 1712 | 1713 | #pragma mark - 1714 | #pragma mark Object 1715 | 1716 | Object::Object() { 1717 | _type = kObjectTypeNil; 1718 | } 1719 | 1720 | Object::Object(std::string stringVal, bool symbol) { 1721 | if (symbol) { 1722 | _type = kObjectTypeSymbol; 1723 | _contents.stringValue = new std::string(stringVal); 1724 | } else { 1725 | _type = kObjectTypeString; 1726 | _contents.stringValue = new std::string(stringVal); 1727 | } 1728 | } 1729 | 1730 | Object::Object(Object *code, ObjectList arguments) { 1731 | _type = kObjectTypeClosure; 1732 | _contents.functionValue.objectPointer = code; 1733 | _contents.functionValue.argumentSymbols = new ObjectList(arguments); 1734 | _contents.functionValue.macro = false; 1735 | } 1736 | 1737 | Object::Object(Object *code, ObjectList arguments, bool macro) { 1738 | _type = kObjectTypeClosure; 1739 | _contents.functionValue.objectPointer = code; 1740 | _contents.functionValue.argumentSymbols = new ObjectList(arguments); 1741 | _contents.functionValue.macro = macro; 1742 | } 1743 | 1744 | Object::Object(ExtensionFunction *function) { 1745 | _type = kObjectTypeBuiltinFunction; 1746 | _contents.builtinFunctionValue.extensionFunctionPointer = function; 1747 | } 1748 | 1749 | // Creates a deep copy of an object 1750 | // Does not have the ability to clone built in functions 1751 | Object::Object(Object* oldObj, GarbageCollector* gc) { 1752 | 1753 | _type = oldObj->_type; 1754 | 1755 | switch (_type) { 1756 | 1757 | case kObjectTypeSymbol: 1758 | _contents.stringValue = new std::string(oldObj->stringValue()); 1759 | break; 1760 | 1761 | case kObjectTypeString: 1762 | _contents.stringValue = new std::string(oldObj->stringValue()); 1763 | break; 1764 | 1765 | case kObjectTypeVector: 1766 | _contents.vectorPointer = new ObjectList(); 1767 | 1768 | for(unsigned i = 0; i < oldObj->_contents.vectorPointer->size(); ++i) 1769 | _contents.vectorPointer->push_back(gc->registerObject(new Object(oldObj->_contents.vectorPointer->at(i), gc))); 1770 | break; 1771 | 1772 | case kObjectTypeClosure: 1773 | _contents.functionValue.objectPointer = gc->registerObject(new Object(oldObj->_contents.functionValue.objectPointer, gc)); 1774 | 1775 | _contents.functionValue.argumentSymbols = new ObjectList(); 1776 | 1777 | for(unsigned i = 0; i < oldObj->_contents.functionValue.argumentSymbols->size(); ++i) 1778 | _contents.functionValue.argumentSymbols->push_back(gc->registerObject(new Object(oldObj->_contents.functionValue.argumentSymbols->at(i), gc))); 1779 | 1780 | _contents.functionValue.macro = oldObj->_contents.functionValue.macro; 1781 | break; 1782 | 1783 | case kObjectTypeNumber: 1784 | _contents.numberPointer = new Number(_contents.numberPointer); 1785 | break; 1786 | 1787 | case kObjectTypeCons: 1788 | _contents.consValue.left = gc->registerObject(new Object(oldObj->consValueLeft(), gc)); 1789 | _contents.consValue.right = gc->registerObject(new Object(oldObj->consValueRight(), gc)); 1790 | break; 1791 | 1792 | // Does not deep copy built in functions 1793 | case kObjectTypeBuiltinFunction: 1794 | _contents.builtinFunctionValue = oldObj->_contents.builtinFunctionValue; 1795 | break; 1796 | 1797 | case kObjectTypeBoolean: 1798 | _contents.booleanValue = _contents.booleanValue; 1799 | break; 1800 | 1801 | case kObjectTypeNil: 1802 | break; 1803 | } 1804 | } 1805 | 1806 | Object::~Object() { 1807 | switch (_type) { 1808 | case kObjectTypeSymbol: 1809 | case kObjectTypeString: 1810 | delete _contents.stringValue; 1811 | break; 1812 | 1813 | case kObjectTypeVector: 1814 | delete _contents.vectorPointer; 1815 | break; 1816 | 1817 | case kObjectTypeClosure: 1818 | // leave the Objects to the gc 1819 | delete _contents.functionValue.argumentSymbols; 1820 | break; 1821 | 1822 | case kObjectTypeNumber: 1823 | delete _contents.numberPointer; 1824 | break; 1825 | 1826 | case kObjectTypeCons: 1827 | // it isn't our business deleting "unused" objects, that is for the GC 1828 | case kObjectTypeBuiltinFunction: 1829 | case kObjectTypeBoolean: 1830 | case kObjectTypeNil: 1831 | // nothing need be done 1832 | break; 1833 | } 1834 | } 1835 | 1836 | bool Object::operator==(const Object& rhs) { 1837 | if (type() != rhs.type()) { 1838 | return false; 1839 | } 1840 | 1841 | switch (_type) { 1842 | case kObjectTypeBoolean: 1843 | return _contents.booleanValue == rhs._contents.booleanValue; 1844 | break; 1845 | 1846 | case kObjectTypeNumber: 1847 | return *_contents.numberPointer == *rhs._contents.numberPointer; 1848 | break; 1849 | 1850 | case kObjectTypeNil: 1851 | return true; 1852 | break; 1853 | 1854 | case kObjectTypeVector: 1855 | if (_contents.vectorPointer->size() == rhs._contents.vectorPointer->size()) { 1856 | for (int elementIndex=0; elementIndex < _contents.vectorPointer->size(); ++elementIndex) { 1857 | if (*(_contents.vectorPointer->at(elementIndex)) != *(rhs._contents.vectorPointer->at(elementIndex))) { 1858 | return false; 1859 | } 1860 | 1861 | } 1862 | return true; 1863 | } else { 1864 | return false; 1865 | } 1866 | break; 1867 | 1868 | case kObjectTypeBuiltinFunction: 1869 | return _contents.builtinFunctionValue.extensionFunctionPointer->functionName() == rhs._contents.builtinFunctionValue.extensionFunctionPointer->functionName(); 1870 | break; 1871 | 1872 | case kObjectTypeClosure: 1873 | return *_contents.functionValue.objectPointer == *rhs._contents.functionValue.objectPointer; 1874 | break; 1875 | 1876 | case kObjectTypeSymbol: 1877 | case kObjectTypeString: 1878 | return *_contents.stringValue == *rhs._contents.stringValue; 1879 | break; 1880 | 1881 | case kObjectTypeCons: 1882 | // recursively evaluate ==, NB lhs equality should return more quickly than rhs equality 1883 | if (*_contents.consValue.left != *rhs._contents.consValue.left) { 1884 | return false; 1885 | } 1886 | return *_contents.consValue.right == *rhs._contents.consValue.right; 1887 | break; 1888 | } 1889 | } 1890 | 1891 | bool Object::operator!=(const Object& rhs) { 1892 | return !operator==(rhs); 1893 | } 1894 | 1895 | Object* Object::functionValueCode() { 1896 | return _contents.functionValue.objectPointer; 1897 | } 1898 | 1899 | ExtensionFunction* Object::functionValueExtensionFunction() { 1900 | return _contents.builtinFunctionValue.extensionFunctionPointer; 1901 | } 1902 | 1903 | ObjectList Object::functionValueParameters() { 1904 | return *_contents.functionValue.argumentSymbols; 1905 | } 1906 | 1907 | bool Object::isMacro() { 1908 | return _contents.functionValue.macro; 1909 | } 1910 | 1911 | Object* Object::consValueLeft() { 1912 | return _contents.consValue.left; 1913 | } 1914 | 1915 | Object* Object::consValueRight() { 1916 | return _contents.consValue.right; 1917 | } 1918 | 1919 | Object::Object(Number numberValue) { 1920 | _type = kObjectTypeNumber; 1921 | _contents.numberPointer = new Number(numberValue); 1922 | } 1923 | 1924 | Object::Object(bool boolValue) { 1925 | _type = kObjectTypeBoolean; 1926 | _contents.booleanValue = boolValue; 1927 | } 1928 | 1929 | Object::Object(int val) { 1930 | _type = kObjectTypeNumber; 1931 | _contents.numberPointer = new Number(val); 1932 | } 1933 | 1934 | Object::Object(double val) { 1935 | _type = kObjectTypeNumber; 1936 | _contents.numberPointer = new Number(val); 1937 | } 1938 | 1939 | Object::Object(Object *left, Object *right) { 1940 | _type = kObjectTypeCons; 1941 | _contents.consValue.left = left; 1942 | _contents.consValue.right = right; 1943 | } 1944 | 1945 | Object::Object(ObjectList objects) { 1946 | _type = kObjectTypeVector; 1947 | _contents.vectorPointer = new ObjectList(objects); 1948 | } 1949 | 1950 | std::string Object::stringValue(bool expandList) { 1951 | std::stringstream stringBuilder; 1952 | 1953 | switch (_type) { 1954 | case kObjectTypeBuiltinFunction: 1955 | stringBuilder << "<<functionName() 1957 | << ">>>"; 1958 | break; 1959 | 1960 | case kObjectTypeClosure: 1961 | stringBuilder << "<<stringRepresentation() 1963 | << ">>>"; 1964 | break; 1965 | 1966 | case kObjectTypeString: 1967 | stringBuilder << *_contents.stringValue; 1968 | break; 1969 | 1970 | case kObjectTypeNumber: 1971 | stringBuilder << _contents.numberPointer->stringRepresentation(); 1972 | break; 1973 | 1974 | case kObjectTypeVector: 1975 | stringBuilder << "["; 1976 | for (int elementIndex = 0; elementIndex < vectorValue().size(); ++elementIndex) { 1977 | if (elementIndex) { 1978 | stringBuilder << " "; 1979 | } 1980 | stringBuilder << _contents.vectorPointer->at(elementIndex)->stringRepresentation(); 1981 | } 1982 | stringBuilder << "]"; 1983 | break; 1984 | 1985 | case kObjectTypeBoolean: 1986 | if (_contents.booleanValue) { 1987 | stringBuilder << "true"; 1988 | } else { 1989 | stringBuilder << "false"; 1990 | } 1991 | break; 1992 | 1993 | case kObjectTypeCons: { 1994 | ObjectList elements; 1995 | 1996 | if (buildList(elements) && expandList) { 1997 | stringBuilder << "`("; 1998 | for (int listIndex = 0; listIndex < elements.size(); ++listIndex) { 1999 | stringBuilder << elements[listIndex]->stringRepresentation(false); 2000 | 2001 | if (listIndex < elements.size() - 1) { 2002 | stringBuilder << " "; 2003 | } 2004 | } 2005 | stringBuilder << ")"; 2006 | } else { 2007 | stringBuilder << "(cons " 2008 | << _contents.consValue.left->stringRepresentation() 2009 | << " " 2010 | << _contents.consValue.right->stringRepresentation() 2011 | << ")"; 2012 | } 2013 | } 2014 | break; 2015 | 2016 | case kObjectTypeNil: 2017 | stringBuilder << "nil"; 2018 | break; 2019 | 2020 | case kObjectTypeSymbol: 2021 | stringBuilder << *_contents.stringValue; 2022 | break; 2023 | } 2024 | 2025 | return stringBuilder.str(); 2026 | } 2027 | 2028 | ObjectList Object::vectorValue() { 2029 | return *_contents.vectorPointer; 2030 | } 2031 | 2032 | Number Object::numberValue() { 2033 | return *_contents.numberPointer; 2034 | } 2035 | 2036 | bool Object::booleanValue() { 2037 | return _contents.booleanValue; 2038 | } 2039 | 2040 | bool Object::coerceBoolean() { 2041 | switch (_type) { 2042 | case kObjectTypeNumber: 2043 | return numberValue()!=Number(0); 2044 | break; 2045 | 2046 | case kObjectTypeBoolean: 2047 | return booleanValue(); 2048 | break; 2049 | 2050 | case kObjectTypeNil: 2051 | return false; 2052 | break; 2053 | 2054 | default: 2055 | // any object other than nil should be considered true 2056 | return true; 2057 | break; 2058 | } 2059 | } 2060 | 2061 | std::string Object::stringRepresentation(bool expandList) { 2062 | std::stringstream stringBuilder; 2063 | 2064 | switch (_type) { 2065 | case kObjectTypeBuiltinFunction: 2066 | stringBuilder << "<<functionName() 2068 | << ">>>"; 2069 | break; 2070 | 2071 | case kObjectTypeClosure: 2072 | stringBuilder << "<<stringRepresentation() 2074 | << ">>>"; 2075 | break; 2076 | 2077 | case kObjectTypeString: 2078 | stringBuilder << '"' << *_contents.stringValue << '"'; 2079 | break; 2080 | 2081 | case kObjectTypeNumber: 2082 | stringBuilder << _contents.numberPointer->stringRepresentation(); 2083 | break; 2084 | 2085 | case kObjectTypeVector: 2086 | stringBuilder << "["; 2087 | for (int elementIndex=0; elementIndexat(elementIndex)->stringRepresentation(); 2092 | } 2093 | stringBuilder << "]"; 2094 | break; 2095 | 2096 | case kObjectTypeBoolean: 2097 | if (_contents.booleanValue) { 2098 | stringBuilder << "true"; 2099 | } else { 2100 | stringBuilder << "false"; 2101 | } 2102 | break; 2103 | 2104 | case kObjectTypeCons: { 2105 | ObjectList elements; 2106 | 2107 | if (buildList(elements) && expandList) { 2108 | stringBuilder << "`("; 2109 | for (int listIndex = 0; listIndex < elements.size(); ++listIndex) { 2110 | stringBuilder << elements[listIndex]->stringRepresentation(false); 2111 | 2112 | if (listIndex < elements.size()-1) { 2113 | stringBuilder << " "; 2114 | } 2115 | } 2116 | stringBuilder << ")"; 2117 | } else { 2118 | stringBuilder << "(cons " 2119 | << _contents.consValue.left->stringRepresentation() 2120 | << " " 2121 | << _contents.consValue.right->stringRepresentation() 2122 | << ")"; 2123 | } 2124 | } break; 2125 | 2126 | case kObjectTypeNil: 2127 | stringBuilder << "nil"; 2128 | break; 2129 | 2130 | case kObjectTypeSymbol: 2131 | stringBuilder << *_contents.stringValue; 2132 | break; 2133 | } 2134 | 2135 | return stringBuilder.str(); 2136 | } 2137 | 2138 | bool Object::isIterable() { 2139 | if (_type == kObjectTypeVector) { 2140 | return false; 2141 | } 2142 | 2143 | if (isList()) { 2144 | return true; 2145 | } 2146 | 2147 | return false; 2148 | } 2149 | 2150 | bool Object::isList() { 2151 | Object *currentObject = this; 2152 | 2153 | while (currentObject->_type == kObjectTypeCons) { 2154 | if (currentObject->_contents.consValue.right->_type == kObjectTypeCons) { 2155 | // this is a cons with a cons as its right value, continue the list 2156 | currentObject = currentObject->_contents.consValue.right; 2157 | } else if (currentObject->_contents.consValue.right->_type == kObjectTypeNil) { 2158 | // a nil terminator for the list 2159 | return true; 2160 | } else { 2161 | // this isn't a list 2162 | return false; 2163 | } 2164 | } 2165 | 2166 | return false; 2167 | } 2168 | 2169 | bool Object::buildList(ObjectList& results) { 2170 | if (_type == kObjectTypeVector) { 2171 | results = *_contents.vectorPointer; 2172 | 2173 | return true; 2174 | } else { 2175 | Object *currentObject = this; 2176 | 2177 | results.clear(); 2178 | 2179 | while (currentObject->_type == kObjectTypeCons) { 2180 | if (currentObject->_contents.consValue.right->_type == kObjectTypeCons) { 2181 | // this is a cons with a cons as its right value, continue the list 2182 | results.push_back(currentObject->_contents.consValue.left); 2183 | currentObject = currentObject->_contents.consValue.right; 2184 | } else if (currentObject->_contents.consValue.right->_type == kObjectTypeNil) { 2185 | // a nil terminator for the list 2186 | results.push_back(currentObject->_contents.consValue.left); 2187 | return true; 2188 | } else { 2189 | // this isn't a list 2190 | return false; 2191 | } 2192 | } 2193 | } 2194 | 2195 | return false; 2196 | } 2197 | 2198 | #pragma mark - 2199 | #pragma mark TinyClojure 2200 | 2201 | Object* TinyClojure::listObject(ObjectList list) { 2202 | if (list.size()) { 2203 | if (list.size()==1) { 2204 | // end a list with a nil sentinel 2205 | Object *nilObject = _gc_short->registerObject(new Object()); 2206 | return _gc_short->registerObject(new Object(list[0], nilObject)); 2207 | } else { 2208 | Object *left = list[0]; 2209 | list.erase(list.begin()); 2210 | return _gc_short->registerObject(new Object(left, listObject(list))); 2211 | } 2212 | } else { 2213 | // clojure's empty lists seem to be (cons nil nil) 2214 | Object *nilObject = _gc_short->registerObject(new Object()); 2215 | return _gc_short->registerObject(new Object(nilObject, nilObject)); 2216 | } 2217 | } 2218 | 2219 | 2220 | TinyClojure::TinyClojure() { 2221 | _ioProxy = new IOProxy(); 2222 | _gc_long = new GarbageCollector(); 2223 | _gc_short = new GarbageCollector(); 2224 | _newlineSet = std::string("\n\r"); 2225 | 2226 | for (char excludeChar = 1; excludeChar<32; ++excludeChar) { 2227 | _excludeSet.append(&excludeChar, 1); 2228 | } 2229 | _excludeSet.append("\"()[]{}';` "); 2230 | 2231 | _numberSet = std::string("0123456789"); 2232 | 2233 | _baseScope = NULL; 2234 | 2235 | loadExtensionFunctions(); 2236 | 2237 | // this will initialise the root scope 2238 | resetInterpreter(); 2239 | } 2240 | 2241 | TinyClojure::~TinyClojure() { 2242 | for (std::vector::iterator it = _extensionFunctions.begin(); it != _extensionFunctions.end(); ++it) { 2243 | delete *it; 2244 | } 2245 | 2246 | delete _baseScope; 2247 | delete _ioProxy; 2248 | delete _gc_long; 2249 | delete _gc_short; 2250 | } 2251 | 2252 | void TinyClojure::addExtensionFunction(ExtensionFunction *function) { 2253 | internalAddExtensionFunction(function); 2254 | resetInterpreter(); 2255 | } 2256 | 2257 | void TinyClojure::internalAddExtensionFunction(ExtensionFunction *function) { 2258 | function->evaluator(this); 2259 | function->garbageCollector(_gc_long, _gc_short); 2260 | function->setIOProxy(_ioProxy); 2261 | function->setup(); 2262 | 2263 | _extensionFunctions.push_back(function); 2264 | } 2265 | 2266 | void TinyClojure::resetInterpreter() { 2267 | if (_baseScope) { 2268 | delete _baseScope; 2269 | } 2270 | 2271 | _baseScope = new InterpreterScope(); 2272 | 2273 | for (int functionIndex = 0; functionIndex < _extensionFunctions.size(); ++functionIndex) { 2274 | ExtensionFunction *aFunction = _extensionFunctions[functionIndex]; 2275 | _baseScope->setSymbolInScope(aFunction->functionName(), 2276 | _gc_long->registerObject(new Object(aFunction))); 2277 | } 2278 | } 2279 | 2280 | void TinyClojure::loadExtensionFunctions() { 2281 | internalAddExtensionFunction(new core::Plus()); 2282 | internalAddExtensionFunction(new core::Minus()); 2283 | internalAddExtensionFunction(new core::Multiply()); 2284 | internalAddExtensionFunction(new core::Divide()); 2285 | internalAddExtensionFunction(new core::If()); 2286 | internalAddExtensionFunction(new core::Equality()); 2287 | internalAddExtensionFunction(new core::NotEqual()); 2288 | internalAddExtensionFunction(new core::Cons()); 2289 | internalAddExtensionFunction(new core::List()); 2290 | internalAddExtensionFunction(new core::LessThan); 2291 | internalAddExtensionFunction(new core::LessThanOrEqual); 2292 | internalAddExtensionFunction(new core::GreaterThan); 2293 | internalAddExtensionFunction(new core::GreaterThanOrEqual); 2294 | internalAddExtensionFunction(new core::Print()); 2295 | internalAddExtensionFunction(new core::Println()); 2296 | internalAddExtensionFunction(new core::PrintStr()); 2297 | internalAddExtensionFunction(new core::PrintlnStr()); 2298 | internalAddExtensionFunction(new core::Str()); 2299 | internalAddExtensionFunction(new core::Count()); 2300 | internalAddExtensionFunction(new core::Compare()); 2301 | internalAddExtensionFunction(new core::Subs()); 2302 | internalAddExtensionFunction(new core::Quot()); 2303 | internalAddExtensionFunction(new core::Rem()); 2304 | internalAddExtensionFunction(new core::Mod()); 2305 | internalAddExtensionFunction(new core::Inc()); 2306 | internalAddExtensionFunction(new core::Dec()); 2307 | internalAddExtensionFunction(new core::Max()); 2308 | internalAddExtensionFunction(new core::Min()); 2309 | internalAddExtensionFunction(new core::LoadFile()); 2310 | internalAddExtensionFunction(new core::LoadString()); 2311 | internalAddExtensionFunction(new core::Spit()); 2312 | internalAddExtensionFunction(new core::Slurp()); 2313 | internalAddExtensionFunction(new core::Nsunmap()); 2314 | internalAddExtensionFunction(new core::Def); 2315 | internalAddExtensionFunction(new core::Do); 2316 | internalAddExtensionFunction(new core::Vector); 2317 | internalAddExtensionFunction(new core::Fn); 2318 | internalAddExtensionFunction(new core::Defn); 2319 | internalAddExtensionFunction(new core::First); 2320 | internalAddExtensionFunction(new core::Rest); 2321 | internalAddExtensionFunction(new core::ReadString); 2322 | internalAddExtensionFunction(new core::Eval); 2323 | internalAddExtensionFunction(new core::ReadLine); 2324 | internalAddExtensionFunction(new core::Cond); 2325 | internalAddExtensionFunction(new core::Let); 2326 | internalAddExtensionFunction(new core::Nth); 2327 | internalAddExtensionFunction(new core::Defmacro); 2328 | } 2329 | 2330 | #pragma mark parser 2331 | 2332 | Object* TinyClojure::parse(std::string startText) { 2333 | ParserState parseState(startText); 2334 | return recursiveParse(parseState); 2335 | } 2336 | 2337 | void TinyClojure::parseAll(std::string codeText, ObjectList& expressions) { 2338 | ParserState parseState(codeText); 2339 | 2340 | while (parseState.charactersLeft()) { 2341 | Object *code = recursiveParse(parseState); 2342 | if (code) { 2343 | expressions.push_back(code); 2344 | } 2345 | } 2346 | } 2347 | 2348 | /** 2349 | * TODO rewrite the entire parser 2350 | * 2351 | * it is a poor translation of Lisping's objc parser, 2352 | * which is a highly tolerant parser, not appropriate for an interpreter 2353 | */ 2354 | Object* TinyClojure::recursiveParse(ParserState& parseState) { 2355 | parseState.skipSeparators(); 2356 | 2357 | if (parseState.position >= parseState.parserString.length()) { 2358 | // there is nothing here return NULL 2359 | return _gc_short->registerObject(new Object()); 2360 | } 2361 | 2362 | const int startPosition = parseState.position; 2363 | 2364 | char startChar = parseState.currentChar(), 2365 | peekChar = parseState.peekChar(); 2366 | 2367 | enum { 2368 | sexpTypeNormal, 2369 | sexpTypeLambdaShorthand, 2370 | sexpTypeListLiteral, 2371 | sexpTypeHashSet, 2372 | } sexpType = sexpTypeNormal; 2373 | 2374 | enum { 2375 | stringTypeNormal, 2376 | stringTypeRegex, 2377 | } stringType = stringTypeNormal; 2378 | 2379 | if (startChar == '#' && peekChar == '"') { 2380 | stringType = stringTypeRegex; 2381 | startChar = peekChar; 2382 | parseState.position++; 2383 | } 2384 | 2385 | if (startChar == '#' && peekChar == '(') { 2386 | sexpType = sexpTypeLambdaShorthand; 2387 | startChar = peekChar; 2388 | parseState.position++; 2389 | } else if (startChar == '#' && peekChar == '{') { 2390 | sexpType = sexpTypeHashSet; 2391 | startChar = peekChar; 2392 | parseState.position++; 2393 | } else if ((startChar == '\'' || startChar == '`') && (peekChar == '(' || peekChar == '[' || peekChar == '{')) { 2394 | sexpType = sexpTypeListLiteral; 2395 | startChar = peekChar; 2396 | parseState.position++; 2397 | } 2398 | 2399 | if (startChar == '"' || stringType == stringTypeRegex) { 2400 | // start collecting a string 2401 | std::string stringbuf; 2402 | 2403 | bool escapeNextChar = false; 2404 | 2405 | // ignore the intial string char 2406 | ++parseState.position; 2407 | 2408 | while (parseState.charactersLeft()) { 2409 | const char currentChar = parseState.currentChar(); 2410 | ++parseState.position; 2411 | 2412 | if (escapeNextChar) { 2413 | char adjustedChar = currentChar; 2414 | 2415 | switch (currentChar) { 2416 | case 'n': 2417 | adjustedChar = 10; 2418 | break; 2419 | 2420 | case 'r': 2421 | adjustedChar = 13; 2422 | break; 2423 | 2424 | case 't': 2425 | adjustedChar = 9; 2426 | break; 2427 | } 2428 | 2429 | stringbuf.append(&adjustedChar, 1); 2430 | 2431 | escapeNextChar = false; 2432 | } else { 2433 | if (currentChar == '\\') { 2434 | escapeNextChar = true; 2435 | } else { 2436 | if (currentChar=='"' && stringbuf.length()) { 2437 | // TODO do something in the case of regexes 2438 | if (stringType == stringTypeRegex) { 2439 | std::cerr << "Not dealing with regexes" << std::endl; 2440 | } 2441 | 2442 | // end of the string 2443 | Object *element = new Object(stringbuf); 2444 | _gc_short->registerObject(element); 2445 | return element; 2446 | } 2447 | stringbuf.append(¤tChar, 1); 2448 | } 2449 | } 2450 | } 2451 | 2452 | Error error(parseState, std::string("Ran out of characters when parsing a string")); 2453 | throw error; 2454 | } else if (startChar == '(' || sexpType == sexpTypeHashSet) { 2455 | char closeChar = ')'; 2456 | if (sexpType == sexpTypeHashSet) closeChar = '}'; 2457 | 2458 | /* 2459 | * start an S-Expression 2460 | * 2461 | * We are looking for 2462 | * * Open parenthesis (found) 2463 | * * terms (recurse this function) 2464 | * * Close parenthesis 2465 | */ 2466 | ObjectList elements; 2467 | 2468 | // advance from the parenthesis 2469 | ++parseState.position; 2470 | 2471 | while (true) { 2472 | // move on 2473 | parseState.skipSeparators(); 2474 | 2475 | if (!parseState.charactersLeft()) { 2476 | // nothing left, return the s expression so far 2477 | break; 2478 | } 2479 | 2480 | if (parseState.currentChar()==closeChar) { 2481 | // advance past the ) and end the S expression 2482 | ++parseState.position; 2483 | 2484 | break; 2485 | } 2486 | 2487 | Object *element = recursiveParse(parseState); 2488 | if (element) { 2489 | elements.push_back(element); 2490 | } else { 2491 | // nothing left, return the s expression so far 2492 | Error error(parseState, "Ran out of characters when building an S Expression"); 2493 | throw error; 2494 | break; 2495 | } 2496 | } 2497 | 2498 | switch (sexpType) { 2499 | case sexpTypeNormal: 2500 | return listObject(elements); 2501 | break; 2502 | 2503 | case sexpTypeListLiteral: 2504 | elements.insert(elements.begin(), _gc_short->registerObject(new Object(std::string("list"), true))); 2505 | return listObject(elements); 2506 | break; 2507 | 2508 | case sexpTypeLambdaShorthand: 2509 | _ioProxy->writeErr("lambda shorthand unimplemented"); 2510 | return NULL; 2511 | break; 2512 | 2513 | case sexpTypeHashSet: 2514 | elements.insert(elements.begin(), _gc_short->registerObject(new Object(std::string("hash-set", true)))); 2515 | return listObject(elements); 2516 | break; 2517 | } 2518 | 2519 | return NULL; 2520 | } else if (startChar == '[') { 2521 | /* 2522 | * start a Clojure vector 2523 | * 2524 | * We are looking for 2525 | * * Open parenthesis (found) 2526 | * * terms (recurse this function) 2527 | * * Close parenthesis 2528 | */ 2529 | ObjectList elements; 2530 | 2531 | // advance from the parenthesis 2532 | ++parseState.position; 2533 | 2534 | while (true) { 2535 | // move on 2536 | parseState.skipSeparators(); 2537 | 2538 | if (!parseState.charactersLeft()) { 2539 | Error error(parseState, "Ran out of characters when building a vector"); 2540 | throw error; 2541 | } 2542 | 2543 | if (parseState.currentChar()==']') { 2544 | // advance past the ] and end the vector 2545 | ++parseState.position; 2546 | 2547 | // insert the vector identifier at the beginning 2548 | elements.insert(elements.begin(), _gc_short->registerObject(new Object("vector", true))); 2549 | 2550 | return listObject(elements); 2551 | } 2552 | 2553 | Object *element = recursiveParse(parseState); 2554 | if (element) { 2555 | elements.push_back(element); 2556 | } else { 2557 | Error error(parseState, "Ran out of characters when building a vector"); 2558 | throw error; 2559 | } 2560 | } 2561 | } else if (startChar == '{') { 2562 | /* 2563 | * start a Clojure map 2564 | * 2565 | * We are looking for 2566 | * * Open parenthesis (found) 2567 | * * terms (recurse this function) 2568 | * * Close parenthesis 2569 | */ 2570 | 2571 | ObjectList elements; 2572 | 2573 | // advance beyond the parenthesis 2574 | ++parseState.position; 2575 | 2576 | while (true) { 2577 | parseState.skipSeparators(); 2578 | 2579 | if (!parseState.charactersLeft()) { 2580 | Error error(parseState, "Ran out of characters when building a map"); 2581 | throw error; 2582 | } 2583 | 2584 | if (parseState.currentChar()=='}') { 2585 | // advance past the ] and end the vector 2586 | ++parseState.position; 2587 | 2588 | return listObject(elements); 2589 | } 2590 | 2591 | Object *element = recursiveParse(parseState); 2592 | if (element) { 2593 | elements.push_back(element); 2594 | } else { 2595 | Error error(parseState, "Ran out of characters when building a map"); 2596 | throw error; 2597 | } 2598 | } 2599 | } else if (startChar == ';') { 2600 | /// start a comment, run until the end of the line 2601 | std::string comment; 2602 | 2603 | while (parseState.charactersLeft()) { 2604 | const char currentChar = parseState.currentChar(); 2605 | 2606 | bool commentChar = false; 2607 | 2608 | if (_newlineSet.find(currentChar) == _newlineSet.npos) { 2609 | comment.append(¤tChar, 1); 2610 | commentChar = true; 2611 | ++parseState.position; 2612 | } 2613 | 2614 | if (!commentChar || !parseState.charactersLeft()) { 2615 | // end of the comment, ignore it 2616 | return recursiveParse(parseState); 2617 | } 2618 | } 2619 | } else if (startChar == '`' || startChar == '\'') { 2620 | // start collecting a symbol 2621 | // TODO no real difference between symbols, identifiers and numbers 2622 | std::string symbol; 2623 | 2624 | while (parseState.charactersLeft()) { 2625 | const char currentChar = parseState.currentChar(), 2626 | peekChar = parseState.peekChar(), 2627 | peekPeekChar = parseState.peekPeekChar(); 2628 | 2629 | bool identifierChar = false; 2630 | 2631 | if(_excludeSet.find(currentChar) == _excludeSet.npos 2632 | || (symbol.length() == 0)) { 2633 | symbol.append(¤tChar, 1); 2634 | identifierChar = false; 2635 | ++parseState.position; 2636 | } 2637 | 2638 | if (!identifierChar 2639 | || !parseState.charactersLeft() 2640 | || (peekChar=='#' && peekPeekChar=='"')) { 2641 | // this is a literal symbol xxx, translate to (quote xxx) and push that 2642 | std::vector els; 2643 | els.push_back(_gc_short->registerObject(new Object("quote", true))); 2644 | els.push_back(_gc_short->registerObject(new Object(symbol, true))); 2645 | return listObject(els); 2646 | } 2647 | } 2648 | } else if (_excludeSet.find(startChar) == _excludeSet.npos) { 2649 | // start an identifier 2650 | std::string identifier; 2651 | 2652 | while (parseState.charactersLeft()) { 2653 | const char currentChar = parseState.currentChar(), 2654 | peekChar = parseState.peekChar(), 2655 | peekPeekChar = parseState.peekPeekChar(); 2656 | 2657 | bool identifierChar = false; 2658 | 2659 | if (_excludeSet.find(currentChar) == _excludeSet.npos 2660 | || ((startChar=='#') && (currentChar==';') && (parseState.position == startPosition+1))) { // case to deal with inline comments 2661 | identifier.append(¤tChar, 1); 2662 | identifierChar = true; 2663 | ++parseState.position; 2664 | } 2665 | 2666 | if (!identifierChar 2667 | || !parseState.charactersLeft() 2668 | || (peekChar=='#' && peekPeekChar=='"')) { 2669 | if (identifier.find("#;")==0) { 2670 | // inline comment element 2671 | return recursiveParse(parseState); 2672 | } else { 2673 | // check for known symbol names 2674 | if (identifier == "true") { 2675 | return _gc_short->registerObject(new Object(true)); 2676 | } else if (identifier == "false") { 2677 | return _gc_short->registerObject(new Object(false)); 2678 | } else if (identifier == "nil") { 2679 | return _gc_short->registerObject(new Object()); 2680 | } 2681 | 2682 | int numberBaseIndex = 0; 2683 | if (identifier[numberBaseIndex] == '-') { 2684 | numberBaseIndex = 1; 2685 | } 2686 | 2687 | bool isInteger = true; 2688 | for (int identifierIndex = numberBaseIndex; identifierIndex < identifier.size(); ++identifierIndex) { 2689 | if (_numberSet.find(identifier[identifierIndex]) == _numberSet.npos) { 2690 | isInteger = false; 2691 | break; 2692 | } 2693 | } 2694 | if (identifier.length() == numberBaseIndex) { 2695 | // this can happen with a pure - sign 2696 | isInteger = false; 2697 | } 2698 | 2699 | bool isFloat = true, 2700 | pointFound = false; 2701 | for (int identifierIndex = numberBaseIndex; identifierIndex < identifier.size(); ++identifierIndex) { 2702 | if (identifier[identifierIndex]=='.') { 2703 | if (pointFound) { 2704 | isFloat = false; 2705 | break; 2706 | } else { 2707 | pointFound = true; 2708 | } 2709 | } else if (_numberSet.find(identifier[identifierIndex]) == _numberSet.npos) { 2710 | isInteger = false; 2711 | break; 2712 | } 2713 | } 2714 | if (!pointFound) { 2715 | isFloat = false; 2716 | } 2717 | 2718 | if (isInteger) { 2719 | return _gc_short->registerObject(new Object(atoi(identifier.c_str()))); 2720 | } else if (isFloat) { 2721 | return _gc_short->registerObject(new Object(atof(identifier.c_str()))); 2722 | } else { 2723 | return _gc_short->registerObject(new Object(identifier, true)); 2724 | } 2725 | } 2726 | } 2727 | } 2728 | } 2729 | 2730 | return NULL; 2731 | } 2732 | 2733 | #pragma mark evaluator 2734 | 2735 | Object* TinyClojure::scopedEval(InterpreterScope *interpreterState, Object *code) { 2736 | InterpreterScope newScope(interpreterState); 2737 | 2738 | return unscopedEval(interpreterState, code); 2739 | } 2740 | 2741 | Object* TinyClojure::unscopedEval(InterpreterScope *interpreterState, Object *code) { 2742 | switch (code->type()) { 2743 | case Object::kObjectTypeNil: 2744 | case Object::kObjectTypeNumber: 2745 | case Object::kObjectTypeString: 2746 | case Object::kObjectTypeBoolean: 2747 | case Object::kObjectTypeBuiltinFunction: 2748 | case Object::kObjectTypeClosure: 2749 | return code; 2750 | break; 2751 | 2752 | case Object::kObjectTypeVector: { 2753 | ObjectList elements; 2754 | 2755 | for (int elementIndex=0; elementIndexvectorValue().size(); ++elementIndex) { 2756 | elements[elementIndex] = scopedEval(interpreterState, code->vectorValue()[elementIndex]); 2757 | } 2758 | 2759 | return _gc_short->registerObject(new Object(elements)); 2760 | } break; 2761 | 2762 | case Object::kObjectTypeSymbol: { 2763 | 2764 | Object *symbolValue = interpreterState->lookupSymbol(code->stringValue()); 2765 | 2766 | if (symbolValue) { 2767 | 2768 | // Checks if the symbol still needs to be evaluated (i.e. Macros need to be evaluated at a different stage) 2769 | if (symbolValue->type() == Object::kObjectTypeCons) { 2770 | if (symbolValue->consValueLeft()->stringValue() == "macroEval") { 2771 | 2772 | if (symbolValue->consValueRight()->type() == Object::kObjectTypeCons) { 2773 | Object *temp = scopedEval(interpreterState, symbolValue->consValueRight()); 2774 | return scopedEval(interpreterState, temp); 2775 | } else { 2776 | return symbolValue->consValueRight(); 2777 | } 2778 | } 2779 | } 2780 | 2781 | return symbolValue; 2782 | } else { 2783 | std::stringstream stringBuilder; 2784 | stringBuilder << "I do not understand the symbol " << code->stringValue(); 2785 | throw Error(stringBuilder.str()); 2786 | } 2787 | } break; 2788 | 2789 | case Object::kObjectTypeCons: 2790 | if (code->isList()) { 2791 | ObjectList arguments; 2792 | code->buildList(arguments); 2793 | 2794 | Object *identifierObject = scopedEval(interpreterState, arguments[0]); 2795 | 2796 | // remove the identifier so that arguments contains just the arguments to the function 2797 | arguments.erase(arguments.begin()); 2798 | 2799 | if (identifierObject->type()==Object::kObjectTypeBuiltinFunction) { 2800 | ExtensionFunction *function = identifierObject->functionValueExtensionFunction(); 2801 | 2802 | int minArgs = function->minimumNumberOfArguments(), 2803 | maxArgs = function->maximumNumberOfArguments(); 2804 | 2805 | if (minArgs>=0) { 2806 | if (arguments.size() < minArgs) { 2807 | std::stringstream stringBuilder; 2808 | stringBuilder << "Function " 2809 | << function->functionName() 2810 | << " requires at least " 2811 | << minArgs 2812 | << " arguments" 2813 | << std::endl; 2814 | 2815 | throw Error(stringBuilder.str()); 2816 | } 2817 | } 2818 | 2819 | if (maxArgs>=0) { 2820 | if (arguments.size() > maxArgs) { 2821 | std::stringstream stringBuilder; 2822 | stringBuilder << "Function " 2823 | << function->functionName() 2824 | << " requires no more than " 2825 | << maxArgs 2826 | << " arguments" 2827 | << std::endl; 2828 | 2829 | throw Error(stringBuilder.str()); 2830 | } 2831 | } 2832 | 2833 | std::vector types; 2834 | for (int parameterIndex=0; parameterIndextype()); 2836 | } 2837 | 2838 | if (!function->validateArgumentTypes(types)) { 2839 | std::stringstream stringBuilder; 2840 | stringBuilder << "Function " 2841 | << function->functionName() 2842 | << "'s type signature does not match that which is passed" 2843 | << std::endl; 2844 | 2845 | throw Error(stringBuilder.str()); 2846 | } 2847 | 2848 | ObjectList preparedArguments; 2849 | if (function->preEvaluateArguments()) { 2850 | for (int argumentIndex=0; argumentIndexexecute(preparedArguments, interpreterState); 2858 | if (result==NULL) { 2859 | result = _gc_short->registerObject(new Object()); 2860 | } 2861 | 2862 | return result; 2863 | } else if (identifierObject->type() == Object::kObjectTypeClosure) { 2864 | 2865 | if (identifierObject->functionValueParameters().size() != arguments.size()) { 2866 | std::stringstream stringBuilder; 2867 | stringBuilder << "Function requires " 2868 | << identifierObject->functionValueParameters().size() 2869 | << " argument(s)" 2870 | << std::endl; 2871 | 2872 | throw Error(stringBuilder.str()); 2873 | } 2874 | 2875 | // build a new scope containing the passed arguments 2876 | InterpreterScope functionScope(interpreterState); 2877 | 2878 | if (identifierObject->isMacro()) { 2879 | 2880 | // is a macro 2881 | for (int parameterIndex = 0; parameterIndex < identifierObject->functionValueParameters().size(); ++parameterIndex) { 2882 | std::string macroEval = "macroEval"; 2883 | 2884 | Object* testObj = _gc_short->registerObject(new Object(_gc_short->registerObject(new Object(macroEval)), parse(arguments[parameterIndex]->stringValue()))); 2885 | Object* newTestObj = _gc_long->registerObject(new Object(testObj, _gc_long)); 2886 | functionScope.setSymbolInScope(identifierObject->functionValueParameters()[parameterIndex]->stringValue(), newTestObj); 2887 | } 2888 | 2889 | } else { 2890 | 2891 | // not a macro, normal function 2892 | for (int parameterIndex = 0; parameterIndex < identifierObject->functionValueParameters().size(); ++parameterIndex) { 2893 | functionScope.setSymbolInScope(identifierObject->functionValueParameters()[parameterIndex]->stringValue(), _gc_long->registerObject(new Object(scopedEval(interpreterState, arguments[parameterIndex]), _gc_long))); 2894 | } 2895 | } 2896 | 2897 | return scopedEval(&functionScope, identifierObject->functionValueCode()); 2898 | 2899 | } else { 2900 | throw Error("An executable S Expression must begin with a function object"); 2901 | } 2902 | } else { 2903 | // I could be wrong, but I don't think this case makes any sense, most likely we got here cos of a flaw in buildList 2904 | throw Error("An executable S Expression was not understood"); 2905 | } 2906 | break; 2907 | } 2908 | 2909 | return NULL; 2910 | } 2911 | 2912 | Object* TinyClojure::eval(Object* code) { 2913 | Object *ret = unscopedEval(_baseScope, code); 2914 | 2915 | if (ret==NULL) { 2916 | ret = _gc_short->registerObject(new Object()); 2917 | } 2918 | 2919 | return ret; 2920 | } 2921 | 2922 | void TinyClojure::CollectGarbage() { 2923 | _gc_short->collectGarbage(); 2924 | } 2925 | 2926 | #pragma mark - 2927 | #pragma mark Garbage Collector 2928 | 2929 | Object* GarbageCollector::registerObject(Object* object) { 2930 | _objects.insert(object); 2931 | return object; 2932 | } 2933 | 2934 | void GarbageCollector::deleteObject(Object* object) { 2935 | // Get object from _objects data structure 2936 | std::set::iterator objToDelete = _objects.find(object); 2937 | // Erase from data structure 2938 | _objects.erase(object); 2939 | // Deallocate the memory dedicated to this object 2940 | delete *objToDelete; 2941 | } 2942 | 2943 | GarbageCollector::GarbageCollector() { 2944 | } 2945 | 2946 | GarbageCollector::~GarbageCollector() { 2947 | for (std::set::iterator it = _objects.begin(); it != _objects.end(); ++it) 2948 | delete *it; 2949 | } 2950 | 2951 | Object* GarbageCollector::retainRootObject(Object *object) { 2952 | return object; 2953 | } 2954 | 2955 | Object* GarbageCollector::releaseRootObject(Object *object) { 2956 | return object; 2957 | } 2958 | 2959 | void GarbageCollector::collectGarbage() { 2960 | for (std::set::iterator it = _objects.begin(); it != _objects.end(); ++it) { 2961 | delete *it; 2962 | } 2963 | _objects.clear(); 2964 | } 2965 | 2966 | #pragma mark - 2967 | #pragma mark ParserState 2968 | 2969 | ParserState::ParserState(std::string& stringin) : parserString(stringin) { 2970 | position = 0; 2971 | } 2972 | 2973 | bool ParserState::charactersLeft() { 2974 | return position < parserString.length(); 2975 | } 2976 | 2977 | int ParserState::skipNewLinesAndWhitespace() { 2978 | return skipCharactersInString(" \n\r\t"); 2979 | } 2980 | 2981 | int ParserState::skipSeparators() { 2982 | return skipCharactersInString(" \t,\n\r"); 2983 | } 2984 | 2985 | char ParserState::currentChar() { 2986 | return parserString[position]; 2987 | } 2988 | 2989 | int ParserState::skipCharactersInString(std::string skipSet) { 2990 | int numberOfSkippedCharacters = 0; 2991 | 2992 | while (charactersLeft()) { 2993 | bool currentCharInSet = false; 2994 | 2995 | for (int skipSetIndex = 0; skipSetIndex < skipSet.length(); ++skipSetIndex) { 2996 | if (parserString[position] == skipSet[skipSetIndex]) { 2997 | currentCharInSet = true; 2998 | break; 2999 | } 3000 | } 3001 | 3002 | if (currentCharInSet) { 3003 | ++position; 3004 | ++numberOfSkippedCharacters; 3005 | } else { 3006 | break; 3007 | } 3008 | 3009 | ++numberOfSkippedCharacters; 3010 | } 3011 | 3012 | return numberOfSkippedCharacters; 3013 | } 3014 | 3015 | char ParserState::peekChar() { 3016 | if (position < parserString.length()-1) { 3017 | return parserString[position+1]; 3018 | } 3019 | 3020 | return 0; 3021 | } 3022 | 3023 | char ParserState::peekPeekChar() { 3024 | if (position < parserString.length()-2) { 3025 | return parserString[position+2]; 3026 | } 3027 | 3028 | return 0; 3029 | } 3030 | 3031 | #pragma mark - 3032 | #pragma mark IOProxy 3033 | 3034 | void IOProxy::writeOut(std::string stringout) { 3035 | std::cout << stringout; 3036 | } 3037 | 3038 | void IOProxy::writeErr(std::string stringout) { 3039 | std::cerr << stringout; 3040 | } 3041 | 3042 | std::string IOProxy::readLine() { 3043 | std::string input; 3044 | std::getline(std::cin, input, '\n'); 3045 | return input; 3046 | } 3047 | } -------------------------------------------------------------------------------- /src/TinyClojure.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 Duncan Steele 2 | // http://slidetocode.com 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining 5 | // a copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be 13 | // included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | // 23 | // TinyClojure.h 24 | // TinyClojure 25 | // 26 | // Created by Duncan Steele on 20/10/2012. 27 | // 28 | 29 | #ifndef __TinyClojure__TinyClojure__ 30 | #define __TinyClojure__TinyClojure__ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | namespace tinyclojure { 43 | /** 44 | * A fancy number class to implement Clojure's numeric tower 45 | */ 46 | class Number { 47 | public: 48 | typedef enum { 49 | kNumberModeInteger, 50 | kNumberModeFloating, 51 | } NumberMode; 52 | 53 | Number(double val); 54 | Number(int val); 55 | Number(); 56 | Number(Number* oldNum); 57 | double floatingValue() const; 58 | Number floatingVersion() const; 59 | int integerValue() const; 60 | Number integerVersion() const; 61 | Number operator+(const Number& rhs) const; 62 | Number operator*(const Number& rhs) const; 63 | Number operator/(const Number& rhs) const; 64 | Number operator-(const Number& rhs) const; 65 | Number::NumberMode getMode() const; 66 | void roundUp(); 67 | void roundDown(); 68 | 69 | bool operator==(const Number& rhs) const; 70 | bool operator!=(const Number& rhs) const; 71 | bool operator<(const Number& rhs) const; 72 | bool operator>(const Number& rhs) const; 73 | bool operator<=(const Number& rhs) const; 74 | bool operator>=(const Number& rhs) const; 75 | 76 | 77 | /// return a string representation of this number 78 | std::string stringRepresentation() const; 79 | protected: 80 | static void equivalentModes(const Number& originalLHS, const Number& originalRHS, Number& newLHS, Number& newRHS); 81 | void setFloating(double val); 82 | void setInteger(int val); 83 | 84 | union { 85 | int integer; 86 | double floating; 87 | } _value; 88 | NumberMode _mode; 89 | }; 90 | 91 | /// a forward declaration to allow for an ExtensionFunction pointer in Object 92 | class ExtensionFunction; 93 | class TinyClojure; 94 | class GarbageCollector; 95 | 96 | /// define the repeatedly used object list with a forward declaration 97 | class Object; 98 | typedef std::vector ObjectList; 99 | 100 | /** 101 | * class to represent Clojure objects 102 | */ 103 | class Object { 104 | public: 105 | typedef enum { 106 | kObjectTypeString, 107 | kObjectTypeSymbol, 108 | kObjectTypeCons, 109 | kObjectTypeNil, 110 | kObjectTypeNumber, 111 | kObjectTypeBoolean, 112 | kObjectTypeVector, 113 | kObjectTypeBuiltinFunction, 114 | kObjectTypeClosure, 115 | } ObjectType; 116 | 117 | /// construct either a symbol (if symbol=true) or a string object otherwise 118 | Object(std::string stringValue, bool symbol=false); 119 | 120 | /// construct a nil object 121 | Object(); 122 | 123 | /// construct a boolean object 124 | Object(bool boolValue); 125 | 126 | /// construct an integer number object 127 | Object(int intValue); 128 | 129 | /// construct an floating point number object 130 | Object(double doubleValue); 131 | 132 | /// construct a number object 133 | Object(Number numberValue); 134 | 135 | /// construct a pair 136 | Object(Object *left, Object *right); 137 | 138 | /// construct a vector 139 | Object(ObjectList objects); 140 | 141 | /// construct a function 142 | Object(Object *code, ObjectList arguments); 143 | 144 | /// construct a macro 145 | Object(Object *code, ObjectList arguments, bool macro); 146 | 147 | /// construct an extension function 148 | Object(ExtensionFunction *function); 149 | 150 | // copy constructor (deep copy) 151 | Object(Object* oldObj, GarbageCollector* gc); 152 | 153 | ~Object(); 154 | 155 | /// this object's type 156 | ObjectType type() const { return _type; } 157 | 158 | /// negated equality operator 159 | bool operator!=(const Object& rhs); 160 | 161 | /// equality operator 162 | bool operator==(const Object& rhs); 163 | 164 | /// return a reference to this object as a string value 165 | std::string stringValue(bool expandList=true); 166 | 167 | /// return a reference to this object as a vector 168 | ObjectList vectorValue(); 169 | 170 | /// accessor for code part of function value 171 | Object* functionValueCode(); 172 | 173 | /// accessor for extension function part of function value 174 | ExtensionFunction* functionValueExtensionFunction(); 175 | 176 | /// accessor for parameter list part of function value 177 | ObjectList functionValueParameters(); 178 | 179 | /// function to check if is a macro 180 | bool isMacro(); 181 | 182 | /// accessor for car 183 | Object* consValueLeft(); 184 | 185 | /// accessor for cdr 186 | Object* consValueRight(); 187 | 188 | /// return a reference to this object as an integer value 189 | Number numberValue(); 190 | 191 | /// return a reference to this object as a boolean value 192 | bool booleanValue(); 193 | 194 | /// this coerces whatever we have into a boolean 195 | bool coerceBoolean(); 196 | 197 | /// build list, returning true and placing objects in the vector if it is a list, false otherwise 198 | bool buildList(ObjectList& results); 199 | 200 | /// build list, returning true if it is a list, false otherwise 201 | bool isList(); 202 | 203 | /// build a string representation of the object 204 | std::string stringRepresentation(bool expandList=true); 205 | 206 | /// is this object iterable 207 | bool isIterable(); 208 | 209 | protected: 210 | ObjectType _type; 211 | union { 212 | std::string* stringValue; 213 | 214 | struct { 215 | Object *left, *right; 216 | } consValue; 217 | 218 | // if objectPointer is nil, interpret this as an extension function based function, else interpret as a user lambda 219 | struct { 220 | Object *objectPointer; 221 | ObjectList* argumentSymbols; 222 | bool macro; 223 | } functionValue; 224 | 225 | struct { 226 | ExtensionFunction *extensionFunctionPointer; 227 | } builtinFunctionValue; 228 | 229 | ObjectList* vectorPointer; 230 | 231 | Number *numberPointer; 232 | 233 | bool booleanValue; 234 | } _contents; 235 | }; 236 | 237 | /** 238 | * A class/interface for the interpreters IO 239 | * 240 | * Subclassing this allows you to fully customise the IO 241 | */ 242 | class IOProxy { 243 | public: 244 | /// write a string to the stdout 245 | void writeOut(std::string stringout); 246 | 247 | /// write a string to the stderr 248 | void writeErr(std::string stringout); 249 | 250 | /// read a line from the stdin 251 | std::string readLine(); 252 | 253 | protected: 254 | 255 | }; 256 | 257 | /** 258 | * a very simple garbage collector for TinyClojure objects 259 | * 260 | * TODO nothing is really implemented yet 261 | * 262 | * the ExportedObject is essentially a C++ reference counting mechanism to keep track of "root objects" ie objects being used in the real world 263 | * when a garbage collection happens connectivity to these objects is the criteria for garbage collecting an object or not 264 | */ 265 | class GarbageCollector { 266 | public: 267 | GarbageCollector(); 268 | ~GarbageCollector(); 269 | 270 | /** 271 | * register an object with the garbage collector 272 | */ 273 | Object* registerObject(Object* object); 274 | 275 | // Deletes an object from the garbage collector 276 | // Returns true/false based on success of operation 277 | void deleteObject(Object* object); 278 | 279 | /** 280 | * increment the "root object" reference count for this Object 281 | */ 282 | Object* retainRootObject(Object *object); 283 | 284 | /** 285 | * decrement the "root object" reference count for this Object 286 | */ 287 | Object* releaseRootObject(Object *object); 288 | 289 | /** 290 | * call this to start a garbage collection run 291 | */ 292 | void collectGarbage(); 293 | 294 | protected: 295 | std::set _objects; 296 | std::map _rootObjects; 297 | }; 298 | 299 | /** 300 | * An object to represent the parser state 301 | * 302 | * construct it with the string to be parsed 303 | * parts of the parser actually live in here 304 | */ 305 | class ParserState { 306 | public: 307 | ParserState(std::string& stringin); 308 | 309 | std::string& parserString; 310 | int position; 311 | 312 | /** 313 | * skip newlines and whitespace. A convenience wrapper for skipCharactersInString 314 | */ 315 | int skipNewLinesAndWhitespace(); 316 | 317 | /** 318 | * a clojure separator set 319 | */ 320 | int skipSeparators(); 321 | 322 | /** 323 | * the character currently pointed to by the parser state 324 | */ 325 | char currentChar(); 326 | 327 | /** 328 | * Safely peek ahead one character, returning 0 if this would run off the end of the string 329 | */ 330 | char peekChar(); 331 | 332 | /** 333 | * Safely peek ahead two characters, returning 0 if this would run off the end of the string 334 | */ 335 | char peekPeekChar(); 336 | 337 | /** 338 | * return true when the state position is within the bounds of the string 339 | */ 340 | bool charactersLeft(); 341 | 342 | /** 343 | * advance the parser through any characters in the passed string 344 | * 345 | * return the number of characters skipped 346 | */ 347 | int skipCharactersInString(std::string skipSet); 348 | }; 349 | 350 | /** 351 | * An object to represent the interpreter's state (probably more accurately, a single scope) 352 | */ 353 | class InterpreterScope { 354 | public: 355 | /// construct a root scope 356 | InterpreterScope() : _parentScope(NULL) { 357 | 358 | } 359 | 360 | /// construct a scope from a parent scope 361 | InterpreterScope(InterpreterScope *parentScope) : _parentScope(parentScope) { 362 | 363 | } 364 | 365 | /// set the symbol in this scope 366 | void setSymbolInScope(std::string symbolName, Object *functionValue); 367 | 368 | /// return the symbol or NULL 369 | Object *lookupSymbolInScope(std::string symbolName); 370 | 371 | /// look for a symbol (in this and all parent scopes), return NULL if not found 372 | Object *lookupSymbol(std::string symbolName); 373 | 374 | // Removes the symbol from scope, used for undefining symbols and garbage collection 375 | // Returns return object of symbol removed 376 | Object* removeSymbolInScope(std::string symbolName); 377 | 378 | // Removes symbol from current scope, if not found there then in its parent (recursive until symbol is determined to be found or not found) 379 | // Returns return object of symbol removed 380 | Object* removeSymbol(std::string); 381 | 382 | protected: 383 | InterpreterScope *_parentScope; 384 | std::map _symbolTable; 385 | }; 386 | 387 | /** 388 | * Wrap a TinyClojure error to make it easy to throw an exception 389 | */ 390 | class Error { 391 | public: 392 | /// constructor for parser errors 393 | Error(ParserState &state, std::string errorMessage) : message(errorMessage), position(state.position) { 394 | int fragLength = 10, 395 | startFragment = state.position - fragLength, 396 | endFragment = state.position + fragLength; 397 | 398 | if (startFragment < 0) { 399 | startFragment = 0; 400 | } 401 | 402 | if (endFragment >= state.parserString.length()) { 403 | endFragment = (int)state.parserString.length()-1; 404 | } 405 | 406 | if (startFragment != endFragment) { 407 | std::string trailing = state.parserString.substr(position, endFragment - position), 408 | leading = state.parserString.substr(startFragment, position - startFragment); 409 | 410 | std::stringstream stringBuilder; 411 | 412 | stringBuilder << message 413 | << " (" 414 | << leading 415 | << " | " 416 | << trailing 417 | << ")"; 418 | 419 | message = stringBuilder.str(); 420 | } 421 | } 422 | 423 | /// constructor for generic errors 424 | Error(std::string errorMessage) : message(errorMessage) { 425 | } 426 | 427 | std::string message; 428 | int position; 429 | }; 430 | 431 | /** 432 | * a wrapper for Object* when exporting any object 433 | * 434 | * this will allow the garbage collector keep track of root objects still in existence 435 | * DO NOT USE (read TODOs) 436 | * 437 | * TODO all objects exported from the TinyClojure should be exported via this 438 | * TODO right the garbage collector this references will be destroyed before this object is destroyed, ideally the garbage collector should be reference counted 439 | */ 440 | class ExportedObject { 441 | public: 442 | ExportedObject(GarbageCollector *gc, Object* ptr) : _gc_long(gc), _object(ptr) { 443 | _gc_long->retainRootObject(_object); 444 | } 445 | 446 | ~ExportedObject() { 447 | _gc_long->releaseRootObject(_object); 448 | } 449 | 450 | Object& operator* () { 451 | return *_object; 452 | } 453 | 454 | Object* operator-> () { 455 | return _object; 456 | } 457 | 458 | protected: 459 | GarbageCollector *_gc_short; 460 | GarbageCollector *_gc_long; 461 | Object *_object; 462 | }; 463 | 464 | /** 465 | * An abstract base class for all interpreter functions 466 | * 467 | * Subclassing this class is the primary mechanism for extending the interpreter 468 | */ 469 | class ExtensionFunction { 470 | public: 471 | /// the name of this function 472 | virtual std::string functionName() { return ""; }; 473 | 474 | /// the meat of the function happens in here, pass arguments and what it needs to evaluate 475 | virtual Object* execute(ObjectList arguments, InterpreterScope* interpreterState) { 476 | return NULL; 477 | } 478 | 479 | 480 | void garbageCollector(GarbageCollector *gc_long, GarbageCollector *gc_short); 481 | void setIOProxy(IOProxy *ioProxy); 482 | void evaluator(TinyClojure *evaluator); 483 | 484 | /** 485 | * validate the argument types 486 | * 487 | */ 488 | virtual bool validateArgumentTypes(std::vector& typeArray); 489 | 490 | /** 491 | * required number of arguments to this function 492 | * 493 | * return -1 if there is no restriction 494 | */ 495 | virtual int requiredNumberOfArguments(); 496 | 497 | /// perform any setup tasks on this functions 498 | void setup(); 499 | 500 | /// fill the argument type array 501 | virtual void fillTypeArray() {} 502 | 503 | /** 504 | * this function requires a number of arguments <= the return value of this function 505 | * 506 | * return -1 if there is no restriction 507 | */ 508 | virtual int minimumNumberOfArguments() { 509 | return requiredNumberOfArguments(); 510 | } 511 | 512 | /** 513 | * this function requires number of arguments >= the return value of this function 514 | * 515 | * return -1 if there is no restriction 516 | */ 517 | virtual int maximumNumberOfArguments() { 518 | return requiredNumberOfArguments(); 519 | } 520 | 521 | /** 522 | * return true to evaluate arguments before passing them to the Extension Function, or false to leave arguments in their raw form 523 | * 524 | * override this to stop arguments that could potentially have side effects being evaluated (e.g. the cases in an if statement) 525 | */ 526 | virtual bool preEvaluateArguments() { 527 | return true; 528 | } 529 | 530 | protected: 531 | /** 532 | * an array of types that must be matched by the arguments to this function 533 | * 534 | * it will only be considered if there are any elements inside it 535 | */ 536 | std::vector _typeArray; 537 | 538 | GarbageCollector *_gc_long; 539 | GarbageCollector *_gc_short; 540 | TinyClojure *_evaluator; 541 | IOProxy *_ioProxy; 542 | }; 543 | 544 | class TinyClojure { 545 | public: 546 | /** 547 | * create a list from a std::vector 548 | * 549 | * this is here, not in the Object constructor because it needs access to the garbage collector 550 | */ 551 | Object* listObject(ObjectList list); 552 | 553 | /** 554 | * parse the passed string, returning the parsed object, or NULL on error 555 | * 556 | * this will parse the data into a tree of S Expressions. This throws an exception if there is a parser error. 557 | * 558 | * @return either an object created by parsing the input, or NULL if nothing was found 559 | */ 560 | Object* parse(std::string stringin); 561 | 562 | /// parse all code in a string 563 | void parseAll(std::string codeText, ObjectList& expressions); 564 | 565 | /** 566 | * evaluate the code passed above 567 | */ 568 | Object* eval(Object* object); 569 | 570 | /** 571 | * default constructor 572 | */ 573 | TinyClojure(); 574 | 575 | /** 576 | * destructor 577 | */ 578 | ~TinyClojure(); 579 | 580 | /// call this to load all extension functions, override it to change which functions are loaded 581 | virtual void loadExtensionFunctions(); 582 | 583 | /// the internal recursive evaluator, this evaluates, but it does not scope the statements 584 | Object* unscopedEval(InterpreterScope *interpreterState, Object *code); 585 | 586 | /// the internal recursive evaluator, this puts statements in a scope and evaluates them 587 | Object* scopedEval(InterpreterScope *interpreterState, Object *code); 588 | 589 | /// this erases the persistent scope, removing all symbols and objects 590 | void resetInterpreter(); 591 | 592 | /// add an extension function and reset the interpreter so that it is loaded 593 | void addExtensionFunction(ExtensionFunction *function); 594 | 595 | void CollectGarbage(); 596 | 597 | protected: 598 | /// add an extension function to the function table 599 | void internalAddExtensionFunction(ExtensionFunction *function); 600 | 601 | /// the IO proxy for this interpreter 602 | IOProxy *_ioProxy; 603 | 604 | /// the base scope owned by this object and persistent between evaluations 605 | InterpreterScope *_baseScope; 606 | 607 | /// the shared garbage collector 608 | // Long term and short term garbage collectors 609 | // One for symbols, defined functions that need to be saved for the life of the program 610 | // Other for objects that can be deleted at various points 611 | GarbageCollector *_gc_long; 612 | GarbageCollector *_gc_short; 613 | 614 | /// the internal recursive parser function, see parse for documentation 615 | Object* recursiveParse(ParserState& parseState); 616 | 617 | std::string _newlineSet, _excludeSet, _numberSet; 618 | 619 | /// a list of loaded extension functions 620 | std::vector _extensionFunctions; 621 | }; 622 | } 623 | 624 | #endif /* defined(__TinyClojure__TinyClojure__) */ -------------------------------------------------------------------------------- /src/tool.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 Duncan Steele 2 | // http://slidetocode.com 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining 5 | // a copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be 13 | // included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | // 23 | // tool.h 24 | // TinyClojure 25 | // 26 | // Created by Duncan Steele on 20/10/2012. 27 | // Copyright (c) 2012 Slide to code. All rights reserved. 28 | // 29 | 30 | #include "TinyClojure.h" 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | 38 | void repl() { 39 | std::string input; 40 | 41 | tinyclojure::TinyClojure interpreter; 42 | do { 43 | std::cout << "> "; 44 | std::getline(std::cin, input, '\n'); 45 | 46 | try { 47 | tinyclojure::Object *code = interpreter.parse(input); 48 | if (code) { 49 | if (code->type() != tinyclojure::Object::kObjectTypeNil) { 50 | tinyclojure::Object *result = interpreter.eval(code); 51 | std::cout << result->stringRepresentation() << std::endl; 52 | interpreter.CollectGarbage(); 53 | } 54 | } 55 | } catch (tinyclojure::Error error) { 56 | std::cout << error.position << ": " << error.message << std::endl << std::endl; 57 | } 58 | } while (!std::cin.eof()); 59 | } 60 | 61 | int main(int argc, const char * argv[]) { 62 | bool startRepl = false; 63 | 64 | if (argc == 1) { 65 | startRepl = true; 66 | } 67 | 68 | // execute any files passed on the command line 69 | int argpos = 1; 70 | while (argpos < argc) { 71 | std::string filename(argv[argpos]); 72 | 73 | if (filename=="-h") { 74 | std::cout << "help: -h prints this message, -r starts the repl, pass any files to execute" << std::endl; 75 | startRepl = false; 76 | } else if (filename=="-r") { 77 | // guarantee that the repl starts 78 | startRepl = true; 79 | } 80 | 81 | std::ifstream t(filename); 82 | std::string fileInput((std::istreambuf_iterator(t)),std::istreambuf_iterator()); 83 | 84 | try { 85 | tinyclojure::TinyClojure interpreter; 86 | std::vector expressions; 87 | interpreter.parseAll(fileInput, expressions); 88 | 89 | for (int expressionIndex = 0; expressionIndex < expressions.size(); ++expressionIndex) { 90 | interpreter.eval(expressions[expressionIndex])->stringRepresentation(); 91 | } 92 | } catch (tinyclojure::Error error) { 93 | std::cout << error.position << ": " << error.message << std::endl << std::endl; 94 | } 95 | 96 | ++argpos; 97 | } 98 | 99 | if (startRepl) { 100 | repl(); 101 | } 102 | 103 | return 0; 104 | } 105 | 106 | -------------------------------------------------------------------------------- /tests/repl.clj: -------------------------------------------------------------------------------- 1 | (defn repl [] 2 | (eval (read-string (read-line))) 3 | (repl)) 4 | 5 | (repl) 6 | -------------------------------------------------------------------------------- /tests/trip.clj: -------------------------------------------------------------------------------- 1 | ; basic "trip tests" for TinyClojure 2 | ; tests are designed to evaluate to 0 3 | 4 | ; define true to make verbose 5 | (def verboseassert false) 6 | 7 | (println "Working!") 8 | 9 | ; an assert statement 10 | (defn assertzero [value label] 11 | (if (not= value 0) 12 | (print "Failure: " label) 13 | (if verboseassert 14 | (print "Success: " label)))) 15 | 16 | ; basic test that defs remain within a scope 17 | (def a 10) 18 | (assertzero (- a 10) "1") 19 | (do 20 | (def a 10) 21 | (assertzero (- a 10) "2")) 22 | 23 | ; make sure state is being captured - TODO this behaviour fails with real clojure right now, fix it 24 | (def con 12) 25 | (def foo (fn [x] (- x con))) 26 | (def con 13) 27 | (assertzero (foo 12) "3") 28 | 29 | ; basic test of a closure 30 | (def factory 31 | (fn [x] 32 | (fn [y] 33 | (- x y)))) 34 | (def foo (factory 12)) 35 | (assertzero (foo 12) "4") 36 | 37 | ; test cond 38 | (assertzero 39 | (cond 40 | (= 1 2) 41 | (do 42 | (print "this shouldn't happen") 43 | 1) 44 | (= (+ 2 2) 0) 45 | (do 46 | (print "this shouldn't happen") 47 | 1) 48 | (= 1 (- 2 1)) 49 | 0) 50 | "cond failure") 51 | 52 | ; test let statement 53 | (assertzero 54 | (let [x 1 y (- x 1)] 12 y) 55 | "let failure") 56 | 57 | ; nth statement 58 | (assertzero 59 | (nth [1 2 0 4] 2) 60 | "nth vector failure") 61 | (assertzero 62 | (nth (list 1 2 0 4) 2) 63 | "nth list failure") 64 | (assertzero 65 | (nth [1 2 3 4] 10 0) 66 | "nth vector failure") 67 | 68 | (print "trip.clj finished") -------------------------------------------------------------------------------- /xcode/TinyClojure.xcodeproj/.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata/ 2 | project.xcworkspace/ 3 | -------------------------------------------------------------------------------- /xcode/TinyClojure.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 18F81BDD163DDACC00F7CCB8 /* TinyClojure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 18F81BDC163DDACC00F7CCB8 /* TinyClojure.cpp */; }; 11 | 18F81BE0163DDAFB00F7CCB8 /* tool.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 18F81BDF163DDAFB00F7CCB8 /* tool.cpp */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXCopyFilesBuildPhase section */ 15 | 1880557E163300C400288E24 /* CopyFiles */ = { 16 | isa = PBXCopyFilesBuildPhase; 17 | buildActionMask = 2147483647; 18 | dstPath = /usr/share/man/man1/; 19 | dstSubfolderSpec = 0; 20 | files = ( 21 | ); 22 | runOnlyForDeploymentPostprocessing = 1; 23 | }; 24 | /* End PBXCopyFilesBuildPhase section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 185DBF91163DD91F00630C76 /* Readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Readme.md; path = ../Readme.md; sourceTree = ""; }; 28 | 18805580163300C400288E24 /* TinyClojure */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = TinyClojure; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 18A9A2B8163E624C00168A71 /* trip.clj */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = trip.clj; path = ../tests/trip.clj; sourceTree = ""; }; 30 | 18F81BDC163DDACC00F7CCB8 /* TinyClojure.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TinyClojure.cpp; path = /Users/duncansteele/slidetocode/TinyClojure/src/TinyClojure.cpp; sourceTree = ""; }; 31 | 18F81BDE163DDAFB00F7CCB8 /* TinyClojure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TinyClojure.h; path = /Users/duncansteele/slidetocode/TinyClojure/src/TinyClojure.h; sourceTree = ""; }; 32 | 18F81BDF163DDAFB00F7CCB8 /* tool.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = tool.cpp; path = /Users/duncansteele/slidetocode/TinyClojure/src/tool.cpp; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | 1880557D163300C400288E24 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | 18805575163300C400288E24 = { 47 | isa = PBXGroup; 48 | children = ( 49 | 18A9A2B8163E624C00168A71 /* trip.clj */, 50 | 185DBF91163DD91F00630C76 /* Readme.md */, 51 | 18805583163300C400288E24 /* TinyClojure */, 52 | 18805581163300C400288E24 /* Products */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | 18805581163300C400288E24 /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 18805580163300C400288E24 /* TinyClojure */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | 18805583163300C400288E24 /* TinyClojure */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 18F81BDF163DDAFB00F7CCB8 /* tool.cpp */, 68 | 18F81BDE163DDAFB00F7CCB8 /* TinyClojure.h */, 69 | 18F81BDC163DDACC00F7CCB8 /* TinyClojure.cpp */, 70 | ); 71 | path = TinyClojure; 72 | sourceTree = ""; 73 | }; 74 | /* End PBXGroup section */ 75 | 76 | /* Begin PBXNativeTarget section */ 77 | 1880557F163300C400288E24 /* TinyClojure */ = { 78 | isa = PBXNativeTarget; 79 | buildConfigurationList = 1880558A163300C400288E24 /* Build configuration list for PBXNativeTarget "TinyClojure" */; 80 | buildPhases = ( 81 | 1880557C163300C400288E24 /* Sources */, 82 | 1880557D163300C400288E24 /* Frameworks */, 83 | 1880557E163300C400288E24 /* CopyFiles */, 84 | ); 85 | buildRules = ( 86 | ); 87 | dependencies = ( 88 | ); 89 | name = TinyClojure; 90 | productName = TinyClojure; 91 | productReference = 18805580163300C400288E24 /* TinyClojure */; 92 | productType = "com.apple.product-type.tool"; 93 | }; 94 | /* End PBXNativeTarget section */ 95 | 96 | /* Begin PBXProject section */ 97 | 18805577163300C400288E24 /* Project object */ = { 98 | isa = PBXProject; 99 | attributes = { 100 | LastUpgradeCheck = 0450; 101 | ORGANIZATIONNAME = "Slide to code"; 102 | }; 103 | buildConfigurationList = 1880557A163300C400288E24 /* Build configuration list for PBXProject "TinyClojure" */; 104 | compatibilityVersion = "Xcode 3.2"; 105 | developmentRegion = English; 106 | hasScannedForEncodings = 0; 107 | knownRegions = ( 108 | en, 109 | ); 110 | mainGroup = 18805575163300C400288E24; 111 | productRefGroup = 18805581163300C400288E24 /* Products */; 112 | projectDirPath = ""; 113 | projectRoot = ""; 114 | targets = ( 115 | 1880557F163300C400288E24 /* TinyClojure */, 116 | ); 117 | }; 118 | /* End PBXProject section */ 119 | 120 | /* Begin PBXSourcesBuildPhase section */ 121 | 1880557C163300C400288E24 /* Sources */ = { 122 | isa = PBXSourcesBuildPhase; 123 | buildActionMask = 2147483647; 124 | files = ( 125 | 18F81BDD163DDACC00F7CCB8 /* TinyClojure.cpp in Sources */, 126 | 18F81BE0163DDAFB00F7CCB8 /* tool.cpp in Sources */, 127 | ); 128 | runOnlyForDeploymentPostprocessing = 0; 129 | }; 130 | /* End PBXSourcesBuildPhase section */ 131 | 132 | /* Begin XCBuildConfiguration section */ 133 | 18805588163300C400288E24 /* Debug */ = { 134 | isa = XCBuildConfiguration; 135 | buildSettings = { 136 | ALWAYS_SEARCH_USER_PATHS = NO; 137 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 138 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 139 | CLANG_CXX_LIBRARY = "libc++"; 140 | CLANG_WARN_EMPTY_BODY = YES; 141 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 142 | COPY_PHASE_STRIP = NO; 143 | GCC_C_LANGUAGE_STANDARD = gnu99; 144 | GCC_DYNAMIC_NO_PIC = NO; 145 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 146 | GCC_OPTIMIZATION_LEVEL = 0; 147 | GCC_PREPROCESSOR_DEFINITIONS = ( 148 | "DEBUG=1", 149 | "$(inherited)", 150 | ); 151 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 152 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 153 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 154 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 155 | GCC_WARN_UNUSED_VARIABLE = YES; 156 | MACOSX_DEPLOYMENT_TARGET = 10.8; 157 | ONLY_ACTIVE_ARCH = YES; 158 | SDKROOT = macosx; 159 | }; 160 | name = Debug; 161 | }; 162 | 18805589163300C400288E24 /* Release */ = { 163 | isa = XCBuildConfiguration; 164 | buildSettings = { 165 | ALWAYS_SEARCH_USER_PATHS = NO; 166 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 167 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 168 | CLANG_CXX_LIBRARY = "libc++"; 169 | CLANG_WARN_EMPTY_BODY = YES; 170 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 171 | COPY_PHASE_STRIP = YES; 172 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 173 | GCC_C_LANGUAGE_STANDARD = gnu99; 174 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 175 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 176 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 177 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 178 | GCC_WARN_UNUSED_VARIABLE = YES; 179 | MACOSX_DEPLOYMENT_TARGET = 10.8; 180 | SDKROOT = macosx; 181 | }; 182 | name = Release; 183 | }; 184 | 1880558B163300C400288E24 /* Debug */ = { 185 | isa = XCBuildConfiguration; 186 | buildSettings = { 187 | PRODUCT_NAME = "$(TARGET_NAME)"; 188 | }; 189 | name = Debug; 190 | }; 191 | 1880558C163300C400288E24 /* Release */ = { 192 | isa = XCBuildConfiguration; 193 | buildSettings = { 194 | PRODUCT_NAME = "$(TARGET_NAME)"; 195 | }; 196 | name = Release; 197 | }; 198 | /* End XCBuildConfiguration section */ 199 | 200 | /* Begin XCConfigurationList section */ 201 | 1880557A163300C400288E24 /* Build configuration list for PBXProject "TinyClojure" */ = { 202 | isa = XCConfigurationList; 203 | buildConfigurations = ( 204 | 18805588163300C400288E24 /* Debug */, 205 | 18805589163300C400288E24 /* Release */, 206 | ); 207 | defaultConfigurationIsVisible = 0; 208 | defaultConfigurationName = Release; 209 | }; 210 | 1880558A163300C400288E24 /* Build configuration list for PBXNativeTarget "TinyClojure" */ = { 211 | isa = XCConfigurationList; 212 | buildConfigurations = ( 213 | 1880558B163300C400288E24 /* Debug */, 214 | 1880558C163300C400288E24 /* Release */, 215 | ); 216 | defaultConfigurationIsVisible = 0; 217 | defaultConfigurationName = Release; 218 | }; 219 | /* End XCConfigurationList section */ 220 | }; 221 | rootObject = 18805577163300C400288E24 /* Project object */; 222 | } 223 | --------------------------------------------------------------------------------