1 | // Target Language Constructs
|
2 | //
|
3 | // We're generating a subset of C++.
|
4 | //
|
5 | // - Done:
|
6 | // - initializer lists
|
7 | // - exceptions
|
8 | // - default arguments
|
9 | // - namespaces
|
10 | //
|
11 | // - advanced:
|
12 | // - What do Python closures get translated to? Oil uses them in a few
|
13 | // places, e.g. for the readline callbacks.
|
14 | // - C++ 20 coroutines (but we're almost certainly not using this)
|
15 |
|
16 | #include <sys/mman.h> // mmap()
|
17 |
|
18 | #include <initializer_list>
|
19 | #include <memory> // shared_ptr
|
20 | #include <stdexcept>
|
21 | #include <unordered_map>
|
22 | #include <vector>
|
23 |
|
24 | #include "mycpp/runtime.h"
|
25 | #include "vendor/greatest.h"
|
26 |
|
27 | using std::unordered_map;
|
28 |
|
29 | class RootingScope2 {
|
30 | public:
|
31 | RootingScope2() {
|
32 | }
|
33 | RootingScope2(const char* func_name) {
|
34 | log(">>> %s", func_name);
|
35 | }
|
36 | ~RootingScope2() {
|
37 | }
|
38 | };
|
39 |
|
40 | #define ROOTING_REPORT 1
|
41 |
|
42 | #if ROOTING_REPORT
|
43 | #define FUNC_NAME_2() __PRETTY_FUNCTION__
|
44 | #else
|
45 | #define FUNC_NAME_2()
|
46 | #endif
|
47 |
|
48 | class MyList {
|
49 | public:
|
50 | MyList(std::initializer_list<int> init) : v_() {
|
51 | for (int i : init) {
|
52 | v_.push_back(i);
|
53 | }
|
54 | }
|
55 | std::vector<int> v_;
|
56 | };
|
57 |
|
58 | template <class T>
|
59 | class Array {
|
60 | public:
|
61 | Array() : v_() {
|
62 | }
|
63 |
|
64 | Array(std::initializer_list<T> init) : v_() {
|
65 | for (T i : init) {
|
66 | v_.push_back(i);
|
67 | }
|
68 | }
|
69 |
|
70 | void append(T item) {
|
71 | RootingScope2 _r(FUNC_NAME_2());
|
72 |
|
73 | v_.push_back(item);
|
74 | }
|
75 |
|
76 | int size() {
|
77 | return v_.size();
|
78 | }
|
79 |
|
80 | std::vector<T> v_;
|
81 | };
|
82 |
|
83 | class FatalError {};
|
84 |
|
85 | class ParseError : public FatalError {
|
86 | public:
|
87 | ParseError(const char* reason) : reason_(reason) {
|
88 | }
|
89 | const char* reason() const {
|
90 | RootingScope2 _r(FUNC_NAME_2());
|
91 |
|
92 | return reason_;
|
93 | }
|
94 |
|
95 | private:
|
96 | const char* reason_;
|
97 | };
|
98 |
|
99 | // https://stackoverflow.com/questions/8480640/how-to-throw-a-c-exception
|
100 | int compare(int a, int b) {
|
101 | if (a < 0 || b < 0) {
|
102 | throw std::invalid_argument("received negative value");
|
103 | }
|
104 | return a < b;
|
105 | }
|
106 |
|
107 | int parse(const char* text) {
|
108 | if (text[0] == 'f') {
|
109 | throw ParseError("started with f");
|
110 | }
|
111 | return 0;
|
112 | }
|
113 |
|
114 | void throw_fatal() {
|
115 | throw FatalError();
|
116 | }
|
117 |
|
118 | void except_subclass_demo() {
|
119 | try {
|
120 | throw_fatal();
|
121 | // parse("f");
|
122 | } catch (ParseError& e) {
|
123 | // Doesn't get caught. Does this rely on RTTI, or is it static?
|
124 | // I think it's static but increases the size of the exception table.
|
125 | log("Got ParseError: %s", e.reason());
|
126 | }
|
127 | }
|
128 |
|
129 | TEST except_demo() {
|
130 | int num_caught = 0;
|
131 |
|
132 | log("compare(3, 1): %d", compare(1, 3));
|
133 | log("compare(5, 4): %d", compare(5, 4));
|
134 |
|
135 | try {
|
136 | log("compare(-1, 3): %d", compare(-1, 3));
|
137 | } catch (const std::invalid_argument& e) {
|
138 | log("Got exception: %s", e.what());
|
139 | num_caught++;
|
140 | }
|
141 |
|
142 | log("");
|
143 |
|
144 | try {
|
145 | log("parse('foo'): %d", parse("foo"));
|
146 | } catch (const ParseError& e) {
|
147 | log("Got exception: %s", e.reason());
|
148 | num_caught++;
|
149 | }
|
150 |
|
151 | try {
|
152 | log("parse('bar'): %d", parse("bar"));
|
153 | } catch (const ParseError& e) {
|
154 | log("Got exception: %s", e.reason());
|
155 | num_caught++; // we don't get here
|
156 | }
|
157 |
|
158 | try {
|
159 | except_subclass_demo();
|
160 | } catch (const FatalError& e) {
|
161 | log("Got FatalError");
|
162 | num_caught++;
|
163 | }
|
164 |
|
165 | ASSERT_EQ_FMT(3, num_caught, "%d");
|
166 |
|
167 | PASS();
|
168 | }
|
169 |
|
170 | TEST template_demo() {
|
171 | Array<int> a;
|
172 | a.append(1);
|
173 | a.append(2);
|
174 | a.append(3);
|
175 | log("a.size() = %d", a.size());
|
176 |
|
177 | Array<MyList*> a2;
|
178 | a2.append(new MyList{1, 2, 3});
|
179 | a2.append(new MyList{4, 5, 6});
|
180 | log("a2.size() = %d", a2.size());
|
181 |
|
182 | PASS();
|
183 | }
|
184 |
|
185 | // prototype
|
186 | void f(int a, int b = -1, const char* s = nullptr);
|
187 |
|
188 | void f(int a, int b, const char* s) {
|
189 | log("");
|
190 | log("a = %d", a);
|
191 | log("b = %d", b);
|
192 | log("s = %p", s);
|
193 | }
|
194 |
|
195 | class Foo {
|
196 | public:
|
197 | // Is there any downside to these default args?
|
198 | // Only for virtual functions. Note that they are re-evaluated at each call
|
199 | // site, which is fine.
|
200 | //
|
201 | // https://google.github.io/styleguide/cppguide.html#Default_Arguments
|
202 | Foo(int i, bool always_strict = false);
|
203 |
|
204 | void Print() {
|
205 | log("i = %d", i);
|
206 | log("always_strict = %d", always_strict);
|
207 | }
|
208 |
|
209 | int i;
|
210 | bool always_strict;
|
211 | };
|
212 |
|
213 | Foo::Foo(int i, bool always_strict) : i(i), always_strict(always_strict) {
|
214 | }
|
215 |
|
216 | TEST default_args_demo() {
|
217 | f(42, 43, "foo");
|
218 | f(42, 43);
|
219 | f(42);
|
220 |
|
221 | Foo a(98);
|
222 | a.Print();
|
223 | Foo b(99, true);
|
224 | b.Print();
|
225 |
|
226 | PASS();
|
227 | }
|
228 |
|
229 | namespace core {
|
230 | namespace util {
|
231 | void p_die(const char* s) {
|
232 | log("p_die %s", s);
|
233 | }
|
234 | } // namespace util
|
235 | } // namespace core
|
236 |
|
237 | namespace tdop {
|
238 | using core::util::p_die;
|
239 |
|
240 | class Parser {
|
241 | public:
|
242 | Parser(int token) : token_(token) {
|
243 | log("Parser %d", token);
|
244 | p_die("Parser");
|
245 | }
|
246 | int token_;
|
247 | };
|
248 | } // namespace tdop
|
249 |
|
250 | namespace typed_arith_parse {
|
251 | // using namespace core; This makes EVERYTHING available.
|
252 |
|
253 | namespace util = core::util;
|
254 |
|
255 | // This lets us use "Parser""
|
256 | using tdop::Parser;
|
257 |
|
258 | TEST namespace_demo() {
|
259 | log("");
|
260 | log("namespace_demo()");
|
261 | f(42);
|
262 | auto unused1 = new tdop::Parser(42);
|
263 | auto unused2 = new Parser(43);
|
264 | (void)unused1;
|
265 | (void)unused2;
|
266 |
|
267 | util::p_die("ns");
|
268 |
|
269 | PASS();
|
270 | }
|
271 | } // namespace typed_arith_parse
|
272 |
|
273 | // Conclusion: every Python module should have is own namespace
|
274 | //
|
275 | // from core.util import log => using core::util::log
|
276 | // from core import util => namespace util = core::util;
|
277 |
|
278 | // test out the size of 5 uint16_t. OK it's actually padded, which is nice!
|
279 | // Because there is no big element.
|
280 | struct Extent {
|
281 | uint16_t s_line_id;
|
282 | uint16_t s_col;
|
283 | uint16_t e_line_id;
|
284 | uint16_t e_col;
|
285 | uint16_t src_id;
|
286 | };
|
287 |
|
288 | class expr__Const {
|
289 | public:
|
290 | expr__Const(int i) : i_(i) {
|
291 | }
|
292 | int i_;
|
293 | };
|
294 |
|
295 | namespace expr {
|
296 | typedef expr__Const Const;
|
297 | }
|
298 |
|
299 | using std::make_shared;
|
300 | using std::shared_ptr;
|
301 |
|
302 | shared_ptr<expr__Const> f(shared_ptr<expr__Const> arg) {
|
303 | log("arg.use_count() = %d", arg.use_count());
|
304 | return shared_ptr<expr__Const>(new expr__Const(arg->i_ + 10));
|
305 | }
|
306 |
|
307 | TEST shared_ptr_demo() {
|
308 | std::shared_ptr<expr__Const> e = make_shared<expr__Const>(5);
|
309 | log("e->i_ = %d", e->i_);
|
310 | log("e.use_count() = %d", e.use_count());
|
311 |
|
312 | // 16, not 24?
|
313 | // These are contiguous.
|
314 | log("sizeof(e) = %zu", sizeof(e));
|
315 | log("");
|
316 |
|
317 | std::shared_ptr<expr__Const> e2(new expr__Const(7));
|
318 | log("e2->i_ = %d", e2->i_);
|
319 | log("e2.use_count() = %d", e2.use_count());
|
320 | log("sizeof(e2) = %zu", sizeof(e2));
|
321 | log("");
|
322 |
|
323 | std::shared_ptr<expr__Const> e3 = f(e2);
|
324 |
|
325 | log("e3->i_ = %d", e3->i_);
|
326 | log("e3.use_count() = %d", e3.use_count());
|
327 | log("sizeof(e3) = %zu", sizeof(e3));
|
328 | log("");
|
329 |
|
330 | PASS();
|
331 | }
|
332 |
|
333 | TEST map_demo() {
|
334 | unordered_map<int, int> m;
|
335 | log("m.size = %d", m.size());
|
336 |
|
337 | // Hm integers have a hash function
|
338 | m[3] = 4;
|
339 | m[5] = 9;
|
340 | log("m.size = %d", m.size());
|
341 |
|
342 | // Hm you always get the pairs
|
343 | // Should this be const auto& or something?
|
344 | for (auto item : m) {
|
345 | log("iterating %d %d", item.first, item.second);
|
346 | }
|
347 |
|
348 | log("---");
|
349 |
|
350 | unordered_map<Extent*, int> m2;
|
351 | log("m2.size = %d", m2.size());
|
352 |
|
353 | // hm do I want this operator overloading?
|
354 | m2[nullptr] = 42;
|
355 | log("m2.size = %d", m2.size());
|
356 |
|
357 | log("retrieved = %d", m2[nullptr]);
|
358 |
|
359 | PASS();
|
360 | }
|
361 |
|
362 | TEST sizeof_demo() {
|
363 | log("sizeof(int): %d", sizeof(int));
|
364 | log("sizeof(int*): %d", sizeof(int*));
|
365 | log("sizeof(Extent): %d", sizeof(Extent));
|
366 | log("");
|
367 |
|
368 | // Good, this is 50.
|
369 | Extent ext_array[5];
|
370 | log("sizeof(ext_array): %d", sizeof(ext_array));
|
371 |
|
372 | PASS();
|
373 | }
|
374 |
|
375 | TEST test_misc() {
|
376 | MyList l{1, 2, 3};
|
377 | log("size: %d", l.v_.size());
|
378 | log("");
|
379 |
|
380 | // Dict literal syntax?
|
381 | // Dict d {{"key", 1}, {"val", 2}};
|
382 |
|
383 | log("");
|
384 | expr::Const c(42);
|
385 | log("expr::Const = %d", c.i_);
|
386 |
|
387 | // dumb_alloc::Summarize();
|
388 |
|
389 | PASS();
|
390 | }
|
391 |
|
392 | struct Point {
|
393 | int x;
|
394 | int y;
|
395 | };
|
396 |
|
397 | // structs don't have any constructors, so don't need any constexpr stuff
|
398 | constexpr Point p = {3, 4};
|
399 |
|
400 | // members must be public to allow initializer list
|
401 | class PointC {
|
402 | public:
|
403 | // constructor is allowed
|
404 | // needs to be constexpr
|
405 | constexpr PointC(int x, int y) : x_(x), y_(y) {
|
406 | }
|
407 | // this is allowed too
|
408 | int get_x() {
|
409 | return x_;
|
410 | }
|
411 | // this is allowed too
|
412 | virtual int mag() const {
|
413 | return x_ * x_ + y_ * y_;
|
414 | }
|
415 |
|
416 | int x_;
|
417 | int y_;
|
418 | };
|
419 |
|
420 | constexpr PointC pc = {5, 6};
|
421 |
|
422 | class SubPointC : public PointC {
|
423 | public:
|
424 | constexpr SubPointC(int x, int y) : PointC(x, y) {
|
425 | }
|
426 | virtual int mag() const {
|
427 | return 0;
|
428 | }
|
429 | };
|
430 |
|
431 | constexpr SubPointC sub = {7, 8};
|
432 |
|
433 | class Compound {
|
434 | public:
|
435 | PointC c1;
|
436 | PointC c2;
|
437 | };
|
438 |
|
439 | // This works, but what about pointers?
|
440 | constexpr Compound c = {{0, 1}, {8, 9}};
|
441 |
|
442 | TEST static_literals() {
|
443 | ASSERT_EQ(3, p.x);
|
444 | ASSERT_EQ(4, p.y);
|
445 |
|
446 | ASSERT_EQ(5, pc.x_);
|
447 | ASSERT_EQ(6, pc.y_);
|
448 |
|
449 | // I'm surprised virtual functions are allowed! We're compiling with
|
450 | // -std=c++11.
|
451 | // But this is just curiosity. We don't need this in ASDL.
|
452 | ASSERT_EQ_FMT(61, pc.mag(), "%d");
|
453 |
|
454 | ASSERT_EQ_FMT(0, sub.mag(), "%d");
|
455 |
|
456 | ASSERT_EQ(0, c.c1.x_);
|
457 | ASSERT_EQ(1, c.c1.y_);
|
458 | ASSERT_EQ(8, c.c2.x_);
|
459 | ASSERT_EQ(9, c.c2.y_);
|
460 |
|
461 | PASS();
|
462 | }
|
463 |
|
464 | enum class Color_e { red, blue };
|
465 |
|
466 | TEST enum_demo() {
|
467 | Color_e c1 = Color_e::red;
|
468 | Color_e c2 = Color_e::blue;
|
469 | int array[2] = {3, 4};
|
470 |
|
471 | // You can cast these strong enums to an integer. We don't do that in the
|
472 | // MyPy source, but maybe we could? It's kind of a pain though.
|
473 |
|
474 | log("c1 %d", static_cast<int>(c1));
|
475 | log("c2 %d", static_cast<int>(c2));
|
476 |
|
477 | log("array[c1] %d", array[static_cast<int>(c1)]);
|
478 |
|
479 | PASS();
|
480 | }
|
481 |
|
482 | class Node {
|
483 | public:
|
484 | int i;
|
485 | int j;
|
486 | Node* left;
|
487 | int k;
|
488 | // padding here on 64-bit, but not 32-bit
|
489 | Node* right;
|
490 | };
|
491 |
|
492 | #if 0
|
493 | constexpr uint16_t Node_mask() {
|
494 | uint16_t mask = 0;
|
495 |
|
496 | constexpr int stride = sizeof(void*);
|
497 |
|
498 | constexpr int o1 = offsetof(Node, left);
|
499 | static_assert(o1 % stride == 0, "oops");
|
500 |
|
501 | constexpr int o2 = offsetof(Node, right);
|
502 | static_assert(o2 % stride == 0, "oops");
|
503 |
|
504 | constexpr int b1 = o1 / stride;
|
505 | constexpr int b2 = o2 / stride;
|
506 |
|
507 | mask |= 1 << b1;
|
508 | mask |= 1 << b2;
|
509 |
|
510 | return mask;
|
511 | }
|
512 |
|
513 | #else
|
514 |
|
515 | // C++ 11 version has to be a single expression!
|
516 |
|
517 | constexpr uint16_t Node_mask() {
|
518 | return (1 << (offsetof(Node, left) / sizeof(void*)) |
|
519 | 1 << (offsetof(Node, right) / sizeof(void*)));
|
520 | }
|
521 |
|
522 | #endif
|
523 |
|
524 | void print_bin(int n) {
|
525 | for (int i = 15; i >= 0; --i) {
|
526 | if (n & (1 << i))
|
527 | putchar('1');
|
528 | else
|
529 | putchar('0');
|
530 | }
|
531 | putchar('\n');
|
532 | }
|
533 |
|
534 | TEST field_mask_demo() {
|
535 | int c1 = offsetof(Node, left);
|
536 | int c2 = offsetof(Node, right);
|
537 | log("c1 = %d, c2 = %d, sizeof(void*) = %d", c1, c2, sizeof(void*));
|
538 |
|
539 | log("Node_mask");
|
540 | print_bin(Node_mask());
|
541 |
|
542 | PASS();
|
543 | }
|
544 |
|
545 | class Base {
|
546 | public:
|
547 | Base(int i) : i(i) {
|
548 | }
|
549 | static constexpr ObjHeader obj_header() {
|
550 | return ObjHeader::ClassFixed(kZeroMask, sizeof(Base));
|
551 | }
|
552 | int i;
|
553 | Node* left;
|
554 | Node* right;
|
555 | };
|
556 |
|
557 | class Derived : public Base {
|
558 | public:
|
559 | Derived(int i, int j) : Base(i), j(j) {
|
560 | // annoying: should be in initializer list
|
561 | FIELD_MASK(*ObjHeader::FromObject(this)) |= 0x5;
|
562 | }
|
563 | int j;
|
564 | Node* three;
|
565 | };
|
566 |
|
567 | // Demonstrate problem with Local<T>
|
568 | #if 0
|
569 | TEST smartptr_inheritance_demo() {
|
570 | Local<Base> b = Alloc<Base>(2);
|
571 | Local<Derived> d = Alloc<Derived>(4, 5);
|
572 |
|
573 | ASSERT_EQ_FMT(2, b->i, "%d");
|
574 |
|
575 | ASSERT_EQ_FMT(4, d->i, "%d");
|
576 | ASSERT_EQ_FMT(5, d->j, "%d");
|
577 |
|
578 | ASSERT_EQ_FMT(0x9, b->field_mask_, "%d");
|
579 | ASSERT_EQ_FMT(0x5, d->field_mask_, "%d");
|
580 |
|
581 | PASS();
|
582 | }
|
583 | #endif
|
584 |
|
585 | char* realloc(char* buf, size_t num_bytes) {
|
586 | void* result = mmap(nullptr, num_bytes, PROT_READ | PROT_WRITE,
|
587 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
588 | memcpy(result, buf, num_bytes);
|
589 |
|
590 | // Now make it unreadable
|
591 | int m = mprotect(buf, num_bytes, PROT_NONE);
|
592 | log("mprotect = %d", m);
|
593 |
|
594 | return static_cast<char*>(result);
|
595 | }
|
596 |
|
597 | TEST mmap_demo() {
|
598 | size_t num_bytes = 1;
|
599 |
|
600 | void* tmp = mmap(nullptr, num_bytes, PROT_READ | PROT_WRITE,
|
601 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
602 | char* space = static_cast<char*>(tmp);
|
603 |
|
604 | *space = 42;
|
605 |
|
606 | log("space %p", space);
|
607 |
|
608 | log("value = %d", *space);
|
609 |
|
610 | space = realloc(space, num_bytes);
|
611 | log("value = %d", *space);
|
612 |
|
613 | // Can't use this anymore
|
614 | char* bad = static_cast<char*>(tmp);
|
615 | (void)bad;
|
616 |
|
617 | PASS();
|
618 | }
|
619 |
|
620 | TEST comma_demo() {
|
621 | auto i = 3;
|
622 | auto k = (i++, 5);
|
623 | log("k = %d", k);
|
624 |
|
625 | auto n = new Node();
|
626 | log("n = %p, n->i = %d, n->j = %d", n, n->i, n->j);
|
627 |
|
628 | // Hacky workaround ... actually this sorta works. Gah.
|
629 | Node* tmp;
|
630 | auto n2 = (tmp = new Node(), tmp->i = 42, tmp);
|
631 | log("n2 = %p, n2->i = %d, n2->j = %d", n2, n2->i, n2->j);
|
632 |
|
633 | PASS();
|
634 | }
|
635 |
|
636 | // Trick here to print types at compile time
|
637 | //
|
638 | // https://stackoverflow.com/questions/60203857/print-a-types-name-at-compile-time-without-aborting-compilation
|
639 |
|
640 | template <typename T>
|
641 | [[gnu::warning("your type here")]] bool print_type() {
|
642 | return true;
|
643 | }
|
644 |
|
645 | TEST signed_unsigned_demo() {
|
646 | char c = '\xff';
|
647 | log("c = %d", c);
|
648 | log("c = %u", c);
|
649 | log("c > 127 = %d", c > 127); // FALSE because it's char
|
650 | log("'\\xff' > 127 = %d", '\xff' > 127); // also FALSE
|
651 |
|
652 | #if 0
|
653 | bool b1 = print_type<decltype(c)>();
|
654 |
|
655 | // The type of literal '\xff' is 'char'
|
656 | bool b2 = print_type<decltype('\xff')>();
|
657 |
|
658 | log("b1 = %d", b1);
|
659 | log("b2 = %d", b2);
|
660 | #endif
|
661 |
|
662 | PASS();
|
663 | }
|
664 |
|
665 | class Object {
|
666 | public:
|
667 | uint32_t header;
|
668 | };
|
669 |
|
670 | class Writer : public Object {
|
671 | public:
|
672 | // This vtable causes the quirk!
|
673 | #if 1
|
674 | virtual int f() {
|
675 | return 42;
|
676 | }
|
677 | #endif
|
678 | };
|
679 |
|
680 | void RootGlobalVar(Object* root) {
|
681 | // Super weird behavior!!! The param root is 8 bytes ahead of the argument
|
682 | // gStdout!
|
683 | log("root = %p", root);
|
684 | }
|
685 |
|
686 | Writer* gStdout = nullptr;
|
687 |
|
688 | Writer* Stdout() {
|
689 | if (gStdout == nullptr) {
|
690 | gStdout = new Writer();
|
691 | log("gStdout = %p", gStdout);
|
692 |
|
693 | log("no cast");
|
694 | RootGlobalVar(gStdout);
|
695 | log("");
|
696 |
|
697 | log("reinterpret_cast");
|
698 | RootGlobalVar(reinterpret_cast<Object*>(gStdout));
|
699 | log("");
|
700 |
|
701 | log("static_cast");
|
702 | RootGlobalVar(static_cast<Object*>(gStdout));
|
703 | log("");
|
704 | }
|
705 | return gStdout;
|
706 | }
|
707 |
|
708 | TEST param_passing_demo() {
|
709 | Writer* writer = Stdout();
|
710 | log("writer %p", writer);
|
711 | log("");
|
712 |
|
713 | // Same behavior: surprising!
|
714 | Object* obj = writer;
|
715 | log("obj %p", obj);
|
716 | log("");
|
717 |
|
718 | PASS();
|
719 | }
|
720 |
|
721 | #define ENUM(name, schema)
|
722 |
|
723 | #define SUM(name, ...)
|
724 |
|
725 | #define VARIANT(...)
|
726 |
|
727 | #define USE(path)
|
728 |
|
729 | #define SUM_NS(name)
|
730 |
|
731 | #define PROD(name) struct name
|
732 |
|
733 | #define SCHEMA(name)
|
734 |
|
735 | TEST tea_macros_demo() {
|
736 | // The preprocessor does NOT expand this. Instead we have a separate parser
|
737 | // that does it. Hm not bad.
|
738 | //
|
739 | // Problem: the processor has to expand imports.
|
740 |
|
741 | USE("frontend/syntax.asdl");
|
742 |
|
743 | // Without commas
|
744 |
|
745 | ENUM(
|
746 | suffix_op,
|
747 |
|
748 | Nullary % Token |
|
749 | Unary {
|
750 | Token word;
|
751 | Word arg_word
|
752 | }
|
753 |
|
754 | );
|
755 |
|
756 | // More natural comma syntax. Although less consistent with C++.
|
757 | // TODO: See what clang-format does on these.
|
758 | // Oh it treats:
|
759 | // - % and | as binary operators
|
760 | // - ; breaks a line but comma doesn't , which I might not want
|
761 | //
|
762 | // OK () and , looks better, but no line breaking. Maybe there is a
|
763 | // clang-format option.
|
764 | //
|
765 | // Enabled WhitespaceSensitiveMacros for now.
|
766 |
|
767 | SUM(suffix_op,
|
768 | Nullary #Token,
|
769 | Unary(Token word, Word arg_word),
|
770 | Static(Token tok, BigStr arg)
|
771 | );
|
772 |
|
773 | SUM(suffix_op,
|
774 |
|
775 | Nullary #Token;
|
776 | Unary {
|
777 | Token word;
|
778 | Word arg_word;
|
779 | }
|
780 | Static {
|
781 | Token tok;
|
782 | BigStr arg;
|
783 | }
|
784 | );
|
785 |
|
786 | // The C++ compiler parses and validates these
|
787 | // Problem: recursive types and so forth. We would need forward declarations
|
788 | // and all that?
|
789 | // It's also a bit more verbose.
|
790 | // How to do the % reference? typedef?
|
791 |
|
792 | PROD(Token) {
|
793 | int id;
|
794 | BigStr val;
|
795 | };
|
796 | struct Word {};
|
797 |
|
798 | SUM_NS(suffix_op) {
|
799 | // typedef Token Nullary;
|
800 | struct Unary {
|
801 | Token op;
|
802 | Word arg_word;
|
803 | };
|
804 | }
|
805 |
|
806 | SCHEMA(
|
807 | data Token(Id id, BigStr val);
|
808 |
|
809 | enum suffix_op {
|
810 | Nullary %Token
|
811 | | Unary(Token op, Word arg_word)
|
812 | }
|
813 |
|
814 | // I guess we retain * for reference semantics and so forth
|
815 | // *out = val; can be useful
|
816 |
|
817 | data Other(Word[] words, Dict<BigStr, Word>* mydict, BigStr? option);
|
818 |
|
819 | // List<Word>* is also possible, but a bit verbose
|
820 | // Word words[] would be more like C++
|
821 | //
|
822 | // Probably want something more clearly different like:
|
823 | //
|
824 | // Word... words
|
825 | // [Word] words -- synonym for List<Word>* words
|
826 | // Word@ words -- not bad, for repetition
|
827 | //
|
828 | // There are also grammars with + and [] though
|
829 | );
|
830 |
|
831 | printf("Sum types defined");
|
832 |
|
833 | PASS();
|
834 | }
|
835 |
|
836 | // DEMO of putting PURE interfaces with CALLERS, like Go and TypeScript
|
837 | // structural types
|
838 |
|
839 | // First package
|
840 | class Reader1 {
|
841 | public:
|
842 | virtual int Read(int n) = 0;
|
843 | };
|
844 |
|
845 | // Second package
|
846 | class Reader2 {
|
847 | public:
|
848 | virtual int Read(int n) = 0;
|
849 | };
|
850 |
|
851 | class Writer2 {
|
852 | public:
|
853 | virtual int Write(int n) = 0;
|
854 | };
|
855 |
|
856 | // Tea could calculate implicit interfaces globally, and then emit these
|
857 | // explicit inheritance relationships
|
858 |
|
859 | // Multiply inheriting from abstract methods seems fine!
|
860 | class Concrete : public Reader1, public Reader2, public Writer2 {
|
861 | public:
|
862 | virtual int Read(int n) {
|
863 | printf("Concrete Read(%d)\n", n);
|
864 | return 0;
|
865 | }
|
866 |
|
867 | virtual int Write(int n) {
|
868 | printf("Concrete Write(%d)\n", n);
|
869 | return 0;
|
870 | }
|
871 | };
|
872 |
|
873 | class Concrete1 : public Reader1 {};
|
874 |
|
875 | class Concrete2 : public Reader1, public Reader2 {};
|
876 |
|
877 | /*
|
878 | Would be something like
|
879 |
|
880 | interface Reader1 {
|
881 | func Read(n Int) -> Int
|
882 | }
|
883 | interface Reader2 {
|
884 | func Read(n Int) -> Int
|
885 | }
|
886 | class Concrete {
|
887 | func Read(n Int) -> Int{
|
888 | log("echo")
|
889 | return 0
|
890 | }
|
891 | }
|
892 | */
|
893 |
|
894 | TEST tea_interface() {
|
895 | Concrete val;
|
896 |
|
897 | // 8 bytes
|
898 | log("sizeof(Concrete1) = %d", sizeof(Concrete1));
|
899 | // 16 bytes
|
900 | log("sizeof(Concrete2) = %d", sizeof(Concrete2));
|
901 |
|
902 | // 24 bytes because it has 3 vtables?
|
903 | // Hm yes, this idea doesn't scale because objects become bloated with
|
904 | // vtables. Though interestingly 2 different vtables can point to the same
|
905 | // concrete Read() method.
|
906 | log("sizeof(Concrete) = %d", sizeof(Concrete));
|
907 | log("sizeof(val) = %d", sizeof(val));
|
908 |
|
909 | Concrete* c = &val;
|
910 | c->Read(3);
|
911 |
|
912 | Reader1* r1 = c;
|
913 | r1->Read(4);
|
914 |
|
915 | Reader2* r2 = c;
|
916 | r2->Read(5);
|
917 |
|
918 | Writer2* w = c;
|
919 | w->Write(6);
|
920 |
|
921 | PASS();
|
922 | }
|
923 |
|
924 | namespace runtime_asdl {
|
925 |
|
926 | class lvalue_t {};
|
927 |
|
928 | class lvalue__Named : public lvalue_t {};
|
929 |
|
930 | class lvalue__Indexed : public lvalue_t {};
|
931 |
|
932 | #if 0
|
933 | namespace lvalue {
|
934 | typedef lvalue__Named Named;
|
935 | typedef lvalue__Indexed Indexed;
|
936 | }
|
937 | #endif
|
938 |
|
939 | // A CLASS can substitute for a namespace, but it can be "imported" with "using"
|
940 | struct lvalue {
|
941 | #if 0
|
942 | class Named: public lvalue_t {
|
943 | };
|
944 | class Indexed: public lvalue_t {
|
945 | };
|
946 | #endif
|
947 |
|
948 | // typedef lvalue__Named Named;
|
949 | // typedef lvalue__Indexed Indexed;
|
950 | using Named = lvalue__Named;
|
951 | using Indexed = lvalue__Indexed;
|
952 | };
|
953 |
|
954 | }; // namespace runtime_asdl
|
955 |
|
956 | namespace hnode_asdl {
|
957 | #if 0
|
958 | namespace hnode_e {
|
959 | const int Record = 1;
|
960 | const int Array = 2;
|
961 | const int Leaf = 3;
|
962 | const int External = 4;
|
963 | };
|
964 | #endif
|
965 |
|
966 | // Not enum class, a namespace
|
967 | struct hnode_e {
|
968 | #if 0
|
969 | static const int Record = 1;
|
970 | static const int Array = 2;
|
971 | static const int Leaf = 3;
|
972 | static const int External = 4;
|
973 | #endif
|
974 | enum no_name {
|
975 | Record = 1,
|
976 | Array = 2,
|
977 | Leaf = 3,
|
978 | External = 4,
|
979 | };
|
980 | };
|
981 |
|
982 | struct scope_e {
|
983 | enum no_name {
|
984 | Record = 1,
|
985 | Array = 2,
|
986 | Leaf = 3,
|
987 | External = 4,
|
988 | };
|
989 | };
|
990 |
|
991 | enum Other {
|
992 | Record = 2,
|
993 | };
|
994 |
|
995 | }; // namespace hnode_asdl
|
996 |
|
997 | using hnode_asdl::hnode_e;
|
998 | using runtime_asdl::lvalue;
|
999 | // namespace lvalue = runtime_asdl::lvalue;
|
1000 |
|
1001 | TEST asdl_namespace_demo() {
|
1002 | lvalue::Named n;
|
1003 | lvalue::Indexed i;
|
1004 |
|
1005 | (void)n;
|
1006 | (void)i;
|
1007 |
|
1008 | log("Record = %d", hnode_e::Record);
|
1009 | log("Array = %d", hnode_e::Array);
|
1010 |
|
1011 | // In Python, it's lvalue.Named(), not lvalue__Named
|
1012 | //
|
1013 | // Although you could change that everywhere
|
1014 | //
|
1015 | // from _devbuild.gen.runtime_asdl import lvalue
|
1016 | //
|
1017 | // can you reverse it?
|
1018 |
|
1019 | PASS();
|
1020 | }
|
1021 |
|
1022 | class C1 {
|
1023 | public:
|
1024 | int i_;
|
1025 | };
|
1026 |
|
1027 | class C2 {
|
1028 | public:
|
1029 | C2() {
|
1030 | }
|
1031 | C2(int i) : i_(i) {
|
1032 | }
|
1033 | int i_ = 42;
|
1034 | };
|
1035 |
|
1036 | // Demo: we can use {} initialization for all fields
|
1037 | //
|
1038 | // Later, if we turn self.i = i into a initialization list, this will be
|
1039 | // cheaper than memset() in theory
|
1040 | class C3 {
|
1041 | public:
|
1042 | C3() {
|
1043 | }
|
1044 | C3(int i) : i_(i) {
|
1045 | }
|
1046 | int i_{};
|
1047 | double f_{};
|
1048 | C2* c2_{};
|
1049 | C2* uninitialized;
|
1050 | };
|
1051 |
|
1052 | TEST member_init_demo() {
|
1053 | C1 c1;
|
1054 | // Uninitialized
|
1055 | log("c1.i_ = %d", c1.i_);
|
1056 |
|
1057 | C2 c2;
|
1058 | log("c2.i_ = %d", c2.i_); // from in-class initialization
|
1059 |
|
1060 | C2 cc2(99);
|
1061 | log("cc2.i_ = %d", cc2.i_); // from constructor
|
1062 |
|
1063 | C3 c3;
|
1064 | log("c3.i_ = %d", c3.i_); // in-class
|
1065 | log("c3.f_ = %f", c3.f_); // in-class
|
1066 | log("c3.c2_ = %p", c3.c2_); // in-class
|
1067 | log("c3.uninitialized = %p", c3.uninitialized); // in-class
|
1068 |
|
1069 | PASS();
|
1070 | }
|
1071 |
|
1072 | GREATEST_MAIN_DEFS();
|
1073 |
|
1074 | int main(int argc, char** argv) {
|
1075 | gHeap.Init(1 << 20);
|
1076 |
|
1077 | GREATEST_MAIN_BEGIN();
|
1078 |
|
1079 | RUN_TEST(typed_arith_parse::namespace_demo);
|
1080 |
|
1081 | RUN_TEST(test_misc);
|
1082 | RUN_TEST(map_demo);
|
1083 | RUN_TEST(shared_ptr_demo);
|
1084 | RUN_TEST(template_demo);
|
1085 | RUN_TEST(default_args_demo);
|
1086 | RUN_TEST(sizeof_demo);
|
1087 | RUN_TEST(except_demo);
|
1088 | RUN_TEST(static_literals);
|
1089 | RUN_TEST(enum_demo);
|
1090 | RUN_TEST(field_mask_demo);
|
1091 | // RUN_TEST(smartptr_inheritance_demo);
|
1092 |
|
1093 | RUN_TEST(mmap_demo);
|
1094 | RUN_TEST(comma_demo);
|
1095 | RUN_TEST(signed_unsigned_demo);
|
1096 | RUN_TEST(param_passing_demo);
|
1097 |
|
1098 | RUN_TEST(tea_macros_demo);
|
1099 | RUN_TEST(tea_interface);
|
1100 |
|
1101 | RUN_TEST(asdl_namespace_demo);
|
1102 |
|
1103 | RUN_TEST(member_init_demo);
|
1104 |
|
1105 | GREATEST_MAIN_END(); /* display results */
|
1106 | return 0;
|
1107 | }
|