├── .gitignore
├── Grammar.txt
├── Makefile
├── README.md
├── hap
└── main.cpp
├── lib
├── Atomic.cpp
├── Atomic.h
├── Context.cpp
├── Context.h
├── Environment.cpp
├── Environment.h
├── Expression
│ ├── BinaryExpression.cpp
│ ├── BinaryExpression.h
│ ├── CallExpression.cpp
│ ├── CallExpression.h
│ ├── DotExpression.cpp
│ ├── DotExpression.h
│ ├── Expression.cpp
│ ├── Expression.h
│ ├── IdentifierExpression.cpp
│ ├── IdentifierExpression.h
│ ├── ListExpression.cpp
│ ├── ListExpression.h
│ ├── MapExpression.cpp
│ ├── MapExpression.h
│ ├── SubscriptExpression.cpp
│ ├── SubscriptExpression.h
│ ├── UnaryExpression.cpp
│ └── UnaryExpression.h
├── Interpreter.cpp
├── Interpreter.h
├── Operator.cpp
├── Operator.h
├── Parser
│ ├── Parser.cpp
│ ├── Parser.h
│ ├── core.cpp
│ ├── expression.cpp
│ ├── statement.cpp
│ └── value.cpp
├── Statement
│ ├── AtomicStatement.cpp
│ ├── AtomicStatement.h
│ ├── BlockStatement.cpp
│ ├── BlockStatement.h
│ ├── ControlStatement.cpp
│ ├── ControlStatement.h
│ ├── DelStatement.cpp
│ ├── DelStatement.h
│ ├── ExpressionStatement.cpp
│ ├── ExpressionStatement.h
│ ├── FlowStatement.cpp
│ ├── FlowStatement.h
│ ├── ForStatement.cpp
│ ├── ForStatement.h
│ ├── FunStatement.cpp
│ ├── FunStatement.h
│ ├── RetStatement.cpp
│ ├── RetStatement.h
│ ├── Statement.cpp
│ ├── Statement.h
│ ├── TraceStatement.cpp
│ ├── TraceStatement.h
│ ├── VarStatement.cpp
│ └── VarStatement.h
├── Token.cpp
├── Token.h
├── Value
│ ├── BooleanValue.cpp
│ ├── BooleanValue.h
│ ├── FloatValue.cpp
│ ├── FloatValue.h
│ ├── FunValue.cpp
│ ├── FunValue.h
│ ├── IntegerValue.cpp
│ ├── IntegerValue.h
│ ├── ListValue.cpp
│ ├── ListValue.h
│ ├── MapValue.cpp
│ ├── MapValue.h
│ ├── StringValue.cpp
│ ├── StringValue.h
│ ├── UndefinedValue.cpp
│ ├── UndefinedValue.h
│ ├── Value.cpp
│ └── Value.h
├── binary.cpp
├── binary.h
├── flow.h
├── indirect_compare.h
├── tokenize.cpp
├── tokenize.h
├── unary.cpp
└── unary.h
├── test
├── and.hap
├── and.out.expect
├── arithmetic.hap
├── arithmetic.out.expect
├── assign.hap
├── assign.out.expect
├── atomic.hap
├── atomic.out.expect
├── boolean.hap
├── boolean.out.expect
├── comment.hap
├── comment.out.expect
├── curry.hap
├── curry.out.expect
├── exit.hap
├── exit.out.expect
├── for.hap
├── for.out.expect
├── function.hap
├── function.out.expect
├── if.hap
├── if.out.expect
├── lambda.hap
├── lambda.out.expect
├── list-literal.hap
├── list-literal.out.expect
├── list-reference.hap
├── list-reference.out.expect
├── map-literal.hap
├── map-literal.out.expect
├── map-reference.hap
├── map-reference.out.expect
├── map.hap
├── map.out.expect
├── next.hap
├── next.out.expect
├── or.hap
├── or.out.expect
├── redo.hap
├── redo.out.expect
├── run.sh
├── subscript.hap
├── subscript.out.expect
├── trace.hap
├── trace.out.expect
├── var.hap
├── var.out.expect
├── when.hap
├── when.out.expect
├── whenever.hap
├── whenever.out.expect
├── while.hap
├── while.out.expect
├── xor.hap
└── xor.out.expect
├── tools
└── woc.pl
└── unit
├── main.cpp
├── parse.cpp
└── tokenize.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | *.o
2 | *.d
3 | bin
4 | test/*.actual
5 |
--------------------------------------------------------------------------------
/Grammar.txt:
--------------------------------------------------------------------------------
1 | program ::= statement*
2 |
3 | statement ::= atomic-statement
4 | | block-statement
5 | | del-statement
6 | | empty-statement
7 | | exit-statement
8 | | for-statement
9 | | fun-statement
10 | | if-statement
11 | | last-statement
12 | | next-statement
13 | | redo-statement
14 | | ret-statement
15 | | trace-statement
16 | | var-statement
17 | | when-statement
18 | | whenever-statement
19 | | while-statement
20 | | expression-statement
21 |
22 | atomic-statement ::= "atomic" statement ";"
23 |
24 | block-statement ::= "{" statement* "}"
25 |
26 | del-statement ::= "del" identifier ";"
27 |
28 | empty-statement ::= ";"
29 |
30 | exit-statement ::= "exit" ";"
31 |
32 | for-statement ::= "for"
33 | "("
34 | statement
35 | expression
36 | ";"
37 | expression
38 | ")"
39 | statement
40 |
41 | fun-statement ::= "fun"
42 | identifier
43 | "("
44 | list(identifier)
45 | ")"
46 | statement
47 |
48 | if-statement ::= "if" "(" expression ")" statement
49 |
50 | last-statement ::= "last" ";"
51 |
52 | next-statement ::= "next" ";"
53 |
54 | redo-statement ::= "redo" ";"
55 |
56 | ret-statement ::= "ret" expression? ";"
57 |
58 | trace-statement ::= "trace" expression ";"
59 |
60 | var-statement ::= "var" identifier ("=" expression)? ";"
61 |
62 | when-statement ::= "when" "(" expression ")" statement
63 |
64 | whenever-statement ::= "whenever" "(" expression ")" statement
65 |
66 | while-statement ::= "while" "(" expression ")" statement
67 |
68 | expression-statement ::= expression ";"
69 |
70 | expression ::= value
71 | | expression suffix*
72 | | expression binary-operator expression
73 | | unary-operator expression
74 |
75 | value ::= boolean-value
76 | | float-value
77 | | fun-value
78 | | integer-value
79 | | string-value
80 | | undefined-value
81 | | identifier-expression
82 |
83 | boolean-value ::= "false" | "true"
84 |
85 | float-value ::= ...
86 |
87 | fun-value ::= "lam" identifier? "(" list(identifier) ")"
88 | (":" expression | "{" statement* "}")
89 |
90 | integer-value ::= ...
91 |
92 | string-value ::= ...
93 |
94 | undefined-value ::= "undefined"
95 |
96 | identifier-expression ::= identifier
97 |
98 | suffix ::= call-suffix
99 | | dot-suffix
100 | | subscript-suffix
101 |
102 | call-suffix ::= "(" list(expression) ")"
103 |
104 | dot-suffix ::= "." identifier
105 |
106 | subscript-suffix ::= "[" expression "]"
107 |
108 | unary-operator ::= "+"
109 | | "-"
110 | | "not"
111 |
112 | binary-operator ::= "*"
113 | | "/"
114 | | "mod"
115 | | "+"
116 | | "-"
117 | | "<<"
118 | | ">>"
119 | | "<"
120 | | ">="
121 | | ">"
122 | | "<="
123 | | "=="
124 | | "<>"
125 | | "and"
126 | | "xor"
127 | | "or"
128 | | "="
129 | | ","
130 |
131 | list(x) ::= (x ("," x)*)?
132 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | LDFLAGS+=-lc++
2 |
3 | LIB_PATHS=lib lib/Expression lib/Parser lib/Statement lib/Value
4 |
5 | INCFLAGS=$(addprefix -I,$(LIB_PATHS))
6 | WARNFLAGS=$(addprefix -W,all error)
7 | CXXFLAGS+=-std=c++11 -stdlib=libc++ $(INCFLAGS) $(WARNFLAGS)
8 |
9 | CPPFLAGS+=-MD -MP
10 |
11 | BIN=./bin
12 | HAP=$(BIN)/hap
13 | HAP_SOURCE_PATHS=hap $(LIB_PATHS)
14 | HAP_SOURCES=$(wildcard $(addsuffix /*.cpp,$(HAP_SOURCE_PATHS)))
15 | HAP_OBJECTS=$(HAP_SOURCES:%.cpp=%.o)
16 | HAP_DEPS=$(HAP_SOURCES:%.cpp=%.d)
17 |
18 | UNIT=$(BIN)/hap-unit
19 | UNIT_SOURCE_PATHS=unit $(LIB_PATHS)
20 | UNIT_SOURCES=$(wildcard $(addsuffix /*.cpp,$(UNIT_SOURCE_PATHS)))
21 | UNIT_OBJECTS=$(UNIT_SOURCES:%.cpp=%.o)
22 | UNIT_DEPS=$(UNIT_SOURCES:%.cpp=%.d)
23 |
24 | TESTER=./test/run.sh
25 |
26 | .PHONY : all
27 | all : build unit-test test
28 |
29 | .PHONY : clean
30 | clean : clean-build clean-deps clean-test
31 |
32 | .PHONY : clean-build
33 | clean-build :
34 | @rm -f $(HAP)
35 | @rm -f $(HAP_OBJECTS)
36 | @rm -f $(UNIT)
37 | @rm -f $(UNIT_OBJECTS)
38 |
39 | .PHONY : clean-deps
40 | clean-deps :
41 | @rm -f $(HAP_DEPS)
42 | @rm -f $(UNIT_DEPS)
43 |
44 | .PHONY : clean-test
45 | clean-test :
46 | @rm -f test/*.actual
47 |
48 | .PHONY : build
49 | build : $(HAP)
50 |
51 | $(HAP) : $(HAP_OBJECTS)
52 | mkdir -p $(BIN)
53 | $(CXX) -o $@ $(LDFLAGS) $(HAP_OBJECTS)
54 |
55 | .PHONY : unit-test
56 | unit-test : $(UNIT)
57 | @$(UNIT)
58 |
59 | $(UNIT) : $(UNIT_OBJECTS)
60 | $(CXX) -o $@ $(LDFLAGS) $(UNIT_OBJECTS)
61 |
62 | TESTS=$(basename $(notdir $(wildcard test/*.hap)))
63 | define TESTRULE
64 | test-$1 : $(HAP)
65 | @$(TESTER) $$(realpath $(HAP)) $1
66 | test : test-$1
67 | endef
68 | .PHONY : $(foreach TEST,$(TESTS),test-$(TEST))
69 | $(foreach TEST,$(TESTS),$(eval $(call TESTRULE,$(TEST))))
70 |
71 | .PHONY : loc
72 | loc :
73 | @wc -l $(HAP_SOURCES) | sort -n
74 |
75 | .PHONY : woc
76 | woc :
77 | @./tools/woc.pl $(HAP_SOURCES)
78 |
79 | -include $(HAP_DEPS)
80 | -include $(UNIT_DEPS)
81 |
82 | define DEPENDS_ON_MAKEFILE
83 | $1 : Makefile
84 |
85 | endef
86 |
87 | $(call DEPENDS_ON_MAKEFILE,hap)
88 | $(foreach OBJECT,$(HAP_OBJECTS),$(eval $(call DEPENDS_ON_MAKEFILE,$(OBJECT))))
89 | $(foreach OBJECT,$(UNIT_OBJECTS),$(eval $(call DEPENDS_ON_MAKEFILE,$(OBJECT))))
90 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hap
2 |
3 | **Hap** is a dynamically typed, asynchronous, imperative programming language.
4 |
5 | **This is the original C++ prototype, which is no longer maintained; it’s superseded by [the new Haskell implementation of Hap](https://github.com/evincarofautumn/hap-hs), which has an improved performance model and a graphical mode.**
6 |
7 | Here is a simple example of Hap’s syntax and semantics:
8 |
9 | ```
10 | var health = 100;
11 |
12 | when (health <= 0) {
13 | print("Goodbye, cruel world!");
14 | exit;
15 | }
16 |
17 | print("Hello, sweet world!");
18 |
19 | while (true)
20 | health = health - 1;
21 | ```
22 |
23 | Hap code reads much like code in other imperative languages. Its syntax is reminiscent of JavaScript. However, note the use of `when`: this is an *asynchronous conditional*. The above code prints:
24 |
25 | ```
26 | Hello, sweet world!
27 | Goodbye, cruel world!
28 | ```
29 |
30 | *Synchronous* flow control statements such as `if`, `while`, and `for` are evaluated immediately. If the condition is true *when the statement is reached*, then the body is evaluated immediately, before proceeding to subsequent statements.
31 |
32 | *Asynchronous* flow control statements such as `when` are evaluated concurrently. The flow of control proceeds to subsequent statements immediately; only when the conditional actually becomes true is the body evaluated.
33 |
34 | This allows programs to be structured implicitly in terms of events and time-based constraints. You are not forced to subscribe to event handlers for predefined conditions—rather, you can create ad-hoc conditions as needed, and this is syntactically lightweight.
35 |
36 | # Types
37 |
38 |
39 | Type | Examples |
40 |
41 | int |
42 | 0
43 | 1234
|
44 |
45 |
46 | bool |
47 | false
48 | true
|
49 |
50 |
51 | text |
52 | ""
53 | "scare"
|
54 |
55 |
56 | list |
57 | []
58 |
59 | [1, 2, 3, 4, 5]
60 |
61 | [
62 | "this",
63 | "that",
64 | "and",
65 | "the",
66 | "other",
67 | "thing",
68 | ]
69 |
70 | [ [ +cos(x), -sin(x) ],
71 | [ +sin(x), +cos(x) ] ]
72 |
73 | [0, false, ""]
|
74 |
75 |
76 | map |
77 | {}
78 |
79 | { "one": 1, "two": 2, "three": 3 }
80 |
81 | [
82 | {
83 | en: "one",
84 | fr: "un",
85 | },
86 | {
87 | en: "two",
88 | fr: "deux",
89 | },
90 | {
91 | en: "three",
92 | fr: "trois",
93 | },
94 | ]
|
95 |
96 |
97 |
98 | # Statements
99 |
100 | ## General
101 |
102 |
103 | Statement | Description |
104 |
105 | var NAME;
106 | var NAME = EXPR;
|
107 | Creates a lexically scoped variable named NAME . Initializes it to EXPR , or gives it an initial value of undef if EXPR is not specified. |
108 |
109 |
110 | fun NAME(PARAM, PARAM, ...) STAT
|
111 | Creates a lexically scoped function named NAME with a body given by STAT using the given parameters. |
112 |
113 |
114 | EXPR;
|
115 | Synchronous. Evaluates EXPR and discards the result. |
116 |
117 |
118 | { STAT... }
|
119 | Synchronous. Evaluates a block of statements as a unit. Introduces a new lexical scope. |
120 |
121 |
122 | exit
|
123 | Exits the program entirely. |
124 |
125 |
126 | last
|
127 | Exits the current loop. |
128 |
129 |
130 | next
|
131 | Jumps to the next iteration of the current loop, re-evaluating the loop condition. |
132 |
133 |
134 | redo
|
135 | Redoes the current iteration of the current loop, without re-evaluating the loop condition (or step, in the case of for ). |
136 |
137 |
138 |
139 | ## Flow Control
140 |
141 |
142 |
143 | Statement |
144 | Synchronicity |
145 | How many times STAT is evaluated |
146 | When STAT is evaluated |
147 |
148 |
149 | if EXPR STAT
|
150 | Synchronous |
151 | Once |
152 | If EXPR is true when the statement is reached. |
153 |
154 |
155 | when EXPR STAT
|
156 | Asynchronous |
157 | Once |
158 | The first time EXPR becomes true; immediately if EXPR is already true. |
159 |
160 |
161 | whenever EXPR STAT
|
162 | Asynchronous |
163 | Once |
164 | Every time EXPR becomes true; immediately if EXPR is already true. |
165 |
166 |
167 | while EXPR STAT
|
168 | Synchronous |
169 | Repeatedly |
170 | As long as EXPR remains true; never if EXPR is already false. |
171 |
172 |
173 | for (INIT; COND; STEP) STAT
|
174 | Synchronous |
175 | Repeatedly |
176 | Equivalent to:
177 | INIT;
178 | while (COND) {
179 | STAT;
180 | STEP;
181 | }
182 | Except that variables declared in INIT are local to the loop, and STEP is evaluated even when the next statement is used. |
183 |
184 |
185 |
--------------------------------------------------------------------------------
/hap/main.cpp:
--------------------------------------------------------------------------------
1 | #include "Interpreter.h"
2 | #include "Parser.h"
3 | #include "Statement.h"
4 | #include "tokenize.h"
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | using namespace hap;
13 | using namespace std;
14 |
15 | int main(int argc, char** argv) try {
16 | --argc;
17 | ++argv;
18 | if (argc != 1)
19 | throw runtime_error("Bad command line.");
20 | ifstream input(argv[0]);
21 | const auto global = make_shared();
22 | Interpreter(global).run
23 | (Parser(tokenize(input), global).accept_program());
24 | } catch (const exception& error) {
25 | cerr << error.what() << '\n';
26 | return 1;
27 | }
28 |
--------------------------------------------------------------------------------
/lib/Atomic.cpp:
--------------------------------------------------------------------------------
1 | #include "Atomic.h"
2 |
3 | #include "Context.h"
4 |
5 | namespace hap {
6 |
7 | Atomic::Atomic(Context& context) : context(context), atomic(context.atomic) {
8 | context.atomic = true;
9 | }
10 |
11 | Atomic::~Atomic() {
12 | context.atomic = atomic;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/lib/Atomic.h:
--------------------------------------------------------------------------------
1 | #ifndef HAP_ATOMIC_H
2 | #define HAP_ATOMIC_H
3 |
4 | namespace hap {
5 |
6 | class Context;
7 |
8 | class Atomic {
9 | public:
10 | Atomic(Context&);
11 | ~Atomic();
12 | private:
13 | Context& context;
14 | bool atomic;
15 | };
16 |
17 | }
18 |
19 | #endif
20 |
--------------------------------------------------------------------------------
/lib/Context.cpp:
--------------------------------------------------------------------------------
1 |
2 | #include "Context.h"
3 |
4 | #include "Atomic.h"
5 | #include "BooleanValue.h"
6 |
7 | using namespace std;
8 |
9 | namespace hap {
10 |
11 | Context::Context() : atomic(false) {}
12 |
13 | void Context::interrupt
14 | (const shared_ptr environment) {
15 | if (atomic)
16 | return;
17 | vector> statements;
18 | for (auto listener = listeners.begin();
19 | listener != listeners.end();
20 | ++listener) {
21 | if (dead(listener))
22 | continue;
23 | const auto& condition(listener->first);
24 | auto& handler(listener->second);
25 | const bool value = atomic_eval_as
26 | (condition, *this, handler.environment)->value;
27 | switch (handler.behavior) {
28 | case ONCE:
29 | if (value) {
30 | statements.push_back(handler.statement);
31 | dead_listeners.push_back(listener);
32 | }
33 | break;
34 | case REPEATEDLY:
35 | if (value && !handler.previous)
36 | statements.push_back(handler.statement);
37 | break;
38 | }
39 | handler.previous = value;
40 | }
41 | for (const auto& dead_listener : dead_listeners)
42 | listeners.erase(dead_listener);
43 | dead_listeners.clear();
44 | for (const auto& statement : statements)
45 | statement->execute(*this, environment);
46 | }
47 |
48 | bool Context::dead(ListenerMap::iterator listener) const {
49 | return find(begin(dead_listeners), end(dead_listeners), listener)
50 | != dead_listeners.end();
51 | }
52 |
53 | void Context::listen
54 | (const shared_ptr condition,
55 | const Behavior behavior,
56 | const shared_ptr body,
57 | const shared_ptr environment) {
58 | Handler handler{behavior, false, body, environment};
59 | listeners.insert(make_pair(condition, handler));
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/lib/Context.h:
--------------------------------------------------------------------------------
1 | #ifndef HAP_CONTEXT_H
2 | #define HAP_CONTEXT_H
3 |
4 | #include "Statement.h"
5 |
6 | #include