5 ms·
> It has the best performance and the highest level abstractions in one package. The highest level abstractions, are you kidding me? I am currently writing a
by loup-vaillant 11y ago
> It has the best performance and the highest level abstractions in one package.
The highest level abstractions, are you kidding me?
I am currently writing a little interpreter in C++. I would have used Ocaml, but the rest of the project is in C++. Well, just try to write an abstract syntax tree in C++. In languages such as ML and Haskell, this is easy:
type expression = Litteral of value
| Symbol of string
| Funcall of expression * expression
| Conditional of expression * expression * expression
| While_loop of expression * expression
| Sequence of expression list
And I'm pretty much done. Now in C++:
--------------------------------------------------------------------------------
#ifndef __Expression_h__
#define __Expression_h__
#include <cstdint>
#include <string>
#include "Value.h"
namespace Script {
class Expression;
class Expression {
public:
enum Tag {
litteral,
symbol,
funcall,
conditional,
while_loop,
sequence
};
union Val {
Value *litteral;
std::string *symbol; // or a true symbol?
std::vector<Expression> *composite;
};
Expression(const Value&);
Expression(const std::string&);
Expression(Tag, std::vector<Expression>*); // takes ownership
~Expression();
private:
Tag _tag;
Val _val;
};
} // namespace Script
#endif // __Expression_h__
--------------------------------------------------------------------------------
Don't forget the cpp file either:
--------------------------------------------------------------------------------
#include "Expression.h"
#include <cstdio>
using namespace Script;
Expression::Expression(const Value& val)
: _tag(litteral)
{
_val.litteral = new Value(val);
}
Expression::Expression(const std::string& sym)
: _tag(symbol)
{
_val.symbol = new std::string(sym);
}
Expression::Expression(Tag tag, std::vector<Expression>* exprs)
{
if ((tag == funcall && exprs->size() == 2) ||
(tag == conditional && exprs->size() == 3) ||
(tag == while_loop && exprs->size() == 2) ||
tag == sequence) {
_tag = tag;
_val.composite = exprs;
} else {
std::fprintf(stderr, "Expression constructor: Invalid Expression\n");
exit(1);
}
}
Expression::~Expression()
{
switch (_tag) {
case litteral :
delete _val.litteral;
break;
case symbol :
delete _val.symbol;
break;
case funcall :
case conditional:
case while_loop :
case sequence :
delete _val.composite;
break;
default:
std::fprintf(stderr, "Expression destructor: broken class invariant\n");
exit(1);
}
}
--------------------------------------------------------------------------------
I may use smart pointers instead of doing my own RAII by hand, but that's still a hassle. Some may point out that my ugly unsafe union type is not the way to to this, I should use a class hierarchy and polymorphism. But just imagine the sheer amount of code I'd have to write.
C++ is a low level language. It's high level pretense is nothing but a thin veneer that cracks under the slightest scratch. Or, someone show me how to implement an abstract syntax tree in less than 20 lines of code. I'm lenient: Haskell and ML only need 6.
(Yes, I'm using Sum types, a feature that's pretty much unique to ML languages. But no, that's not cheating: providing enumeration and unions but somehow failing to combine them into sum types is a serious oversight.)
- userbinator 11y agoI think you're deliberately making it more verbose than it needs to be. The class definition could be condensed into: class Expression { enum Tag { litteral, symbol, funcall, conditional, while_loop, sequence } _tag; union { Value *litteral; std::string *symbol; // or a true symbol? std::vector<Expression> *composite; } _val; }; And you could probably use templates or macros to autogenerate most of those constructors/destructors automatically.
- fsloth 11y agoThe ML example was missing the second half of the ML niceness - efficient pattern matching at syntax levels which makes for a really nice and understandable code, especially when writing complex compound clauses. "...could probably use templates or macros to autogenerate.." ... which kind of highlights that ML example even more. In Ocaml or F# that original definition would have been more or less enough to start writing nice, efficient parsing code. The sum types are not the only thing, IMO, that ML-family has going for it. As for example F# contains pairs and lists as syntax level constructs (rather than library add-ons) lot of code can be written at a really high level before optimization - in the same language - if it's needed.
- loup-vaillant 11y ago> I think you're deliberately making it more verbose than it needs to be. I swear I am not. This is will be production code, for which I earn my salary. Your code is terser, but still fundamentally the same as mine. It's still unsafe (we could access the wrong union member), and it still has to work around incomplete definitions and the lack of trivial constructors (I wouldn't use pointers otherwise). Automating my constructors and destructors would be amazing. Unfortunately, I don't know how to do it —not without writing over a hundred lines of cryptic boilerplate first.
- b169118 11y agoWhy do you need to use pointers for string and vector? Aren't they already heap allocated internally?