├── .gitignore ├── Constants.h ├── Exceptions.h ├── Interpreter.h ├── Parser.h ├── README.md ├── SyntaxNodes ├── AdditionNode.h ├── DivisionNode.h ├── ElseNode.h ├── ForNode.h ├── FunctionCallNode.h ├── FunctionDeclarationNode.h ├── IfNode.h ├── MultiplicationNode.h ├── Node.h ├── ReadNode.h ├── SayNode.h ├── SubtractionNode.h └── VariableNode.h ├── Transpiler.h ├── Util.h ├── main.cpp └── public ├── favicon.png ├── index.html ├── index.js ├── style.css └── tutorial.html /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .idea/ 3 | .vercel/ 4 | cmake-build-debug/ 5 | CMakeLists.txt 6 | public/main.js 7 | public/main.wasm 8 | -------------------------------------------------------------------------------- /Constants.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "Util.h" 8 | #include "SyntaxNodes/Node.h" 9 | #include "SyntaxNodes/IfNode.h" 10 | 11 | const std::vector keywords = {"let", "say", "read", "if", "else", "for", "function", "call", "add", "subtract", "multiply", "divide"}; 12 | 13 | 14 | const std::unordered_map ifOperators = { 15 | {"is", IS}, 16 | {"equals", EQUALS}, 17 | {"not-equals", NOT_EQUALS}, 18 | {"greater-than", GREATER_THAN}, 19 | {"greater-equal", GREATER_EQUAL}, 20 | {"less-than", LESS_THAN}, 21 | {"less-equal", LESS_EQUAL}, 22 | {"divisible-by", DIVISIBLE_BY} 23 | }; 24 | 25 | std::string enumToString(NodeType value) { 26 | static std::unordered_map enumMap = { 27 | {ROOT, "ROOT"}, 28 | {LET, "LET"}, 29 | {FOR, "FOR"}, 30 | {IF, "IF"}, 31 | {ELSE, "ELSE"}, 32 | {SAY, "SAY"}, 33 | {READ, "READ"}, 34 | {FUNC_DECL, "FUNC_DECL"}, 35 | {FUNC_CALL, "FUNC_CALL"}, 36 | {ADD, "ADD"}, 37 | {SUB, "SUB"}, 38 | {MUL, "MUL"}, 39 | {DIV, "DIV"} 40 | }; 41 | if(find(enumMap, value)) { 42 | return enumMap[value]; 43 | } 44 | return "UNKNOWN"; 45 | } 46 | 47 | // this is auto generated, don't worry 48 | const std::unordered_map allNumbers = { 49 | {"zero", 0}, 50 | {"one", 1}, 51 | {"two", 2}, 52 | {"three", 3}, 53 | {"four", 4}, 54 | {"five", 5}, 55 | {"six", 6}, 56 | {"seven", 7}, 57 | {"eight", 8}, 58 | {"nine", 9}, 59 | {"ten", 10}, 60 | {"eleven", 11}, 61 | {"twelve", 12}, 62 | {"thirteen", 13}, 63 | {"fourteen", 14}, 64 | {"fifteen", 15}, 65 | {"sixteen", 16}, 66 | {"seventeen", 17}, 67 | {"eighteen", 18}, 68 | {"nineteen", 19}, 69 | {"twenty", 20}, 70 | {"twenty-one", 21}, 71 | {"twenty-two", 22}, 72 | {"twenty-three", 23}, 73 | {"twenty-four", 24}, 74 | {"twenty-five", 25}, 75 | {"twenty-six", 26}, 76 | {"twenty-seven", 27}, 77 | {"twenty-eight", 28}, 78 | {"twenty-nine", 29}, 79 | {"thirty", 30}, 80 | {"thirty-one", 31}, 81 | {"thirty-two", 32}, 82 | {"thirty-three", 33}, 83 | {"thirty-four", 34}, 84 | {"thirty-five", 35}, 85 | {"thirty-six", 36}, 86 | {"thirty-seven", 37}, 87 | {"thirty-eight", 38}, 88 | {"thirty-nine", 39}, 89 | {"forty", 40}, 90 | {"forty-one", 41}, 91 | {"forty-two", 42}, 92 | {"forty-three", 43}, 93 | {"forty-four", 44}, 94 | {"forty-five", 45}, 95 | {"forty-six", 46}, 96 | {"forty-seven", 47}, 97 | {"forty-eight", 48}, 98 | {"forty-nine", 49}, 99 | {"fifty", 50}, 100 | {"fifty-one", 51}, 101 | {"fifty-two", 52}, 102 | {"fifty-three", 53}, 103 | {"fifty-four", 54}, 104 | {"fifty-five", 55}, 105 | {"fifty-six", 56}, 106 | {"fifty-seven", 57}, 107 | {"fifty-eight", 58}, 108 | {"fifty-nine", 59}, 109 | {"sixty", 60}, 110 | {"sixty-one", 61}, 111 | {"sixty-two", 62}, 112 | {"sixty-three", 63}, 113 | {"sixty-four", 64}, 114 | {"sixty-five", 65}, 115 | {"sixty-six", 66}, 116 | {"sixty-seven", 67}, 117 | {"sixty-eight", 68}, 118 | {"sixty-nine", 69}, 119 | {"seventy", 70}, 120 | {"seventy-one", 71}, 121 | {"seventy-two", 72}, 122 | {"seventy-three", 73}, 123 | {"seventy-four", 74}, 124 | {"seventy-five", 75}, 125 | {"seventy-six", 76}, 126 | {"seventy-seven", 77}, 127 | {"seventy-eight", 78}, 128 | {"seventy-nine", 79}, 129 | {"eighty", 80}, 130 | {"eighty-one", 81}, 131 | {"eighty-two", 82}, 132 | {"eighty-three", 83}, 133 | {"eighty-four", 84}, 134 | {"eighty-five", 85}, 135 | {"eighty-six", 86}, 136 | {"eighty-seven", 87}, 137 | {"eighty-eight", 88}, 138 | {"eighty-nine", 89}, 139 | {"ninety", 90}, 140 | {"ninety-one", 91}, 141 | {"ninety-two", 92}, 142 | {"ninety-three", 93}, 143 | {"ninety-four", 94}, 144 | {"ninety-five", 95}, 145 | {"ninety-six", 96}, 146 | {"ninety-seven", 97}, 147 | {"ninety-eight", 98}, 148 | {"ninety-nine", 99} 149 | }; 150 | 151 | int getConstant(const std::string &number) { 152 | if (find(allNumbers, number)) { 153 | return allNumbers.at(number); 154 | } 155 | return -1; 156 | } -------------------------------------------------------------------------------- /Exceptions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class CustomException : public std::runtime_error { 7 | public: 8 | explicit CustomException(const std::string &msg) 9 | : std::runtime_error(msg) {} 10 | }; 11 | 12 | CustomException UndefinedVariableError(const std::string &str) { 13 | return CustomException("Variable " + str + " is not defined\n"); 14 | } 15 | 16 | CustomException InvalidNumberError(const std::string &str) { 17 | return CustomException("Invalid number " + str + ", only 0-99 allowed, example: forty-five\n"); 18 | } 19 | 20 | CustomException FuncDeclError() { 21 | return CustomException("Wrong function declaration, example: function Fn\n"); 22 | } 23 | 24 | CustomException FuncCallError() { 25 | return CustomException("Wrong function call, example: call Fn\n"); 26 | } 27 | 28 | CustomException FuncAlreadyDefError(const std::string &str) { 29 | return CustomException("Function " + str + " is already declared\n"); 30 | } 31 | CustomException FucNotDefError(const std::string &str) { 32 | return CustomException("Function " + str + " is not defined\n"); 33 | } 34 | 35 | CustomException UnknownKeywordError(const std::string &str) { 36 | return CustomException("Unknown keyword: " + str + '\n'); 37 | } 38 | 39 | CustomException VarDeclError() { 40 | return CustomException("Wrong variable declaration, example: let N be five\n"); 41 | } 42 | 43 | CustomException SayError() { 44 | return CustomException("Wrong say statement, example: say N\n"); 45 | } 46 | 47 | CustomException ReadError() { 48 | return CustomException("Wrong read statement, example: read N\n"); 49 | } 50 | 51 | CustomException IfError() { 52 | return CustomException("Wrong if statement, example: if N greater-equal eight\n"); 53 | } 54 | 55 | CustomException ElseError() { 56 | return CustomException("Else without previous if\n"); 57 | } 58 | 59 | CustomException ForError() { 60 | return CustomException("Wrong for statement, example: for every N from one to fifteen\n"); 61 | } 62 | 63 | CustomException AdditionError() { 64 | return CustomException("Wrong addition, example: add four to Number\n"); 65 | } 66 | 67 | CustomException SubtractionError() { 68 | return CustomException("Wrong subtraction, example: subtract four from N"); 69 | } 70 | 71 | CustomException MultiplicationError() { 72 | return CustomException("Wrong multiplication, example: multiply N by four"); 73 | } 74 | 75 | CustomException DivisionError() { 76 | return CustomException("Wrong division, example: divide N by four"); 77 | } 78 | 79 | CustomException DivisionByZeroError() { 80 | return CustomException("Wrong multiplication, example: multiply N by four"); 81 | } 82 | 83 | CustomException IndentError() { 84 | return CustomException("Indents must be a multiple of 4\n"); 85 | } 86 | 87 | CustomException OutOfBoundsValueError(int x) { 88 | return CustomException("Value " + std::to_string(x) + " is not in the range [0-99]\n"); 89 | } -------------------------------------------------------------------------------- /Interpreter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "Parser.h" 8 | #include "Exceptions.h" 9 | 10 | using namespace emscripten; 11 | 12 | EM_JS(const char*, getInputFromUser_JS, (), { 13 | var input = prompt("Please enter a value:"); 14 | var lengthBytes = lengthBytesUTF8(input) + 1; 15 | var stringOnWasmHeap = _malloc(lengthBytes); 16 | stringToUTF8(input, stringOnWasmHeap, lengthBytes); 17 | return stringOnWasmHeap; 18 | }); 19 | 20 | std::string getInput() { 21 | char* inputChar = (char*)getInputFromUser_JS(); 22 | std::string inputStr = std::string(inputChar); 23 | free(inputChar); 24 | return strip(inputStr); 25 | } 26 | 27 | 28 | class Interpreter { 29 | std::unordered_map variables; 30 | std::unordered_map functions; 31 | std::string result; 32 | 33 | int getVariableOrConstant(const std::string &str) { 34 | if (getConstant(str) != -1) { 35 | return getConstant(str); 36 | } else if (find(variables, str)) { 37 | return variables[str]; 38 | } else { 39 | throw UndefinedVariableError(str); 40 | } 41 | } 42 | 43 | void eval(Node *node) { 44 | if (node->nodeType == ROOT) { 45 | for (Node *n: node->body) { 46 | eval(n); 47 | } 48 | } 49 | if (node->nodeType == LET) { 50 | auto *variableNode = dynamic_cast(node); 51 | variables[variableNode->name] = getVariableOrConstant(variableNode->value); 52 | } 53 | if (node->nodeType == SAY) { 54 | auto *sayNode = dynamic_cast(node); 55 | std::string variableName = sayNode->variable; 56 | if(variableName == "space") { 57 | result += ' '; 58 | } 59 | else if(variableName == "line") { 60 | result += '\n'; 61 | } 62 | else { 63 | result += std::to_string(getVariableOrConstant(variableName)); 64 | } 65 | } 66 | if (node->nodeType == READ) { 67 | auto *readNode = dynamic_cast(node); 68 | std::string variableName = readNode->variable; 69 | std::string str; 70 | str = getInput(); 71 | if (getConstant(str) == -1) { 72 | throw InvalidNumberError(str); 73 | } 74 | variables[variableName] = getConstant(str); 75 | } 76 | if (node->nodeType == IF) { 77 | auto *ifNode = dynamic_cast(node); 78 | std::string variableName = ifNode->variable; 79 | int value = getVariableOrConstant(variableName); 80 | IfOperator ifOperator = ifNode->ifOperator; 81 | std::string operand = ifNode->operand; 82 | bool condition; 83 | if (ifOperator == IS) { 84 | if (operand == "odd") { 85 | condition = value % 2 == 1; 86 | } else { 87 | condition = value % 2 == 0; 88 | } 89 | } else { 90 | int operandValue = getVariableOrConstant(operand); 91 | if (ifOperator == EQUALS) { 92 | condition = value == operandValue; 93 | } 94 | if (ifOperator == NOT_EQUALS) { 95 | condition = value != operandValue; 96 | } 97 | if (ifOperator == GREATER_THAN) { 98 | condition = value > operandValue; 99 | } 100 | if (ifOperator == GREATER_EQUAL) { 101 | condition = value >= operandValue; 102 | } 103 | if (ifOperator == LESS_THAN) { 104 | condition = value < operandValue; 105 | } 106 | if (ifOperator == LESS_EQUAL) { 107 | condition = value <= operandValue; 108 | } 109 | if (ifOperator == DIVISIBLE_BY) { 110 | condition = value % operandValue == 0; 111 | } 112 | } 113 | if (condition) { 114 | for (Node *n: node->body) { 115 | eval(n); 116 | } 117 | } else if (ifNode->elseBody) { 118 | for (Node *n: *ifNode->elseBody) { 119 | eval(n); 120 | } 121 | } 122 | 123 | } 124 | if (node->nodeType == FOR) { 125 | auto *forNode = dynamic_cast(node); 126 | int start = getVariableOrConstant(forNode->start); 127 | int stop = getVariableOrConstant(forNode->stop); 128 | if (start <= stop) { 129 | for (int i = start; i <= stop; i++) { 130 | variables[forNode->variable] = i; 131 | for (Node *n: node->body) { 132 | eval(n); 133 | } 134 | } 135 | } else { 136 | for (int i = start; i >= stop; i--) { 137 | variables[forNode->variable] = i; 138 | for (Node *n: node->body) { 139 | eval(n); 140 | } 141 | } 142 | } 143 | } 144 | if (node->nodeType == FUNC_DECL) { 145 | auto *funcDeclNode = dynamic_cast(node); 146 | std::string functionName = funcDeclNode->name; 147 | functions[funcDeclNode->name] = node; 148 | node->nodeType = ROOT; 149 | } 150 | if (node->nodeType == FUNC_CALL) { 151 | auto *funcCallNode = dynamic_cast(node); 152 | std::string functionName = funcCallNode->name; 153 | if (notFind(functions, functionName)) { 154 | throw FucNotDefError(functionName); 155 | } 156 | functions[functionName]->nodeType = ROOT; 157 | eval(functions[functionName]); 158 | functions[functionName]->nodeType = FUNC_DECL; 159 | } 160 | if (node->nodeType == ADD) { 161 | auto *additionNode = dynamic_cast(node); 162 | std::string value = additionNode->value; 163 | std::string receiver = additionNode->receiver; 164 | int extraValue = getVariableOrConstant(value); 165 | int receiverValue = getVariableOrConstant(receiver); 166 | if (notFind(variables, receiver)) { 167 | throw UndefinedVariableError(receiver); 168 | } 169 | receiverValue += extraValue; 170 | if (receiverValue > 99) { 171 | throw OutOfBoundsValueError(receiverValue); 172 | } 173 | variables[receiver] = receiverValue; 174 | } 175 | if (node->nodeType == SUB) { 176 | auto *subtractionNode = dynamic_cast(node); 177 | std::string value = subtractionNode->value; 178 | std::string receiver = subtractionNode->receiver; 179 | int subtractValue = getVariableOrConstant(value); 180 | int receiverValue = getVariableOrConstant(receiver); 181 | if (notFind(variables, receiver)) { 182 | throw UndefinedVariableError(receiver); 183 | } 184 | receiverValue -= subtractValue; 185 | if (receiverValue < 0) { 186 | throw OutOfBoundsValueError(receiverValue); 187 | } 188 | variables[receiver] = receiverValue; 189 | } 190 | if (node->nodeType == MUL) { 191 | auto *multiplicationNode = dynamic_cast(node); 192 | std::string value = multiplicationNode->value; 193 | std::string receiver = multiplicationNode->receiver; 194 | int multiplierValue = getVariableOrConstant(value); 195 | int receiverValue = getVariableOrConstant(receiver); 196 | if (notFind(variables, receiver)) { 197 | throw UndefinedVariableError(receiver); 198 | } 199 | receiverValue *= multiplierValue; 200 | if (receiverValue > 99) { 201 | throw OutOfBoundsValueError(receiverValue); 202 | } 203 | variables[receiver] = receiverValue; 204 | } 205 | if (node->nodeType == DIV) { 206 | auto *divisionNode = dynamic_cast(node); 207 | std::string value = divisionNode->value; 208 | std::string receiver = divisionNode->receiver; 209 | int dividerValue = getVariableOrConstant(value); 210 | int receiverValue = getVariableOrConstant(receiver); 211 | if (notFind(variables, receiver)) { 212 | throw UndefinedVariableError(receiver); 213 | } 214 | if (dividerValue == 0) { 215 | throw DivisionByZeroError(); 216 | } 217 | receiverValue /= dividerValue; 218 | variables[receiver] = receiverValue; 219 | } 220 | } 221 | 222 | void deleteNode(Node *root) { 223 | for (Node *node: root->body) { 224 | deleteNode(node); 225 | } 226 | delete root; 227 | } 228 | 229 | void releaseMemory(Node *node) { 230 | variables.clear(); 231 | functions.clear(); 232 | deleteNode(node); 233 | } 234 | 235 | public: 236 | void debug(Node *root) { 237 | std::cout << enumToString(root->nodeType); 238 | std::cout << "("; 239 | for (Node *node: root->body) { 240 | debug(node); 241 | } 242 | std::cout << ")"; 243 | } 244 | 245 | void run(Node *node) { 246 | eval(node); 247 | releaseMemory(node); 248 | } 249 | std::string getResult() { 250 | std::string copy(result); 251 | result.clear(); 252 | return copy; 253 | } 254 | }; -------------------------------------------------------------------------------- /Parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "Util.h" 12 | #include "Exceptions.h" 13 | #include "SyntaxNodes/Node.h" 14 | #include "SyntaxNodes/VariableNode.h" 15 | #include "SyntaxNodes/SayNode.h" 16 | #include "SyntaxNodes/ReadNode.h" 17 | #include "SyntaxNodes/IfNode.h" 18 | #include "SyntaxNodes/ElseNode.h" 19 | #include "SyntaxNodes/ForNode.h" 20 | #include "SyntaxNodes/FunctionDeclarationNode.h" 21 | #include "SyntaxNodes/FunctionCallNode.h" 22 | #include "SyntaxNodes/AdditionNode.h" 23 | #include "SyntaxNodes/SubtractionNode.h" 24 | #include "SyntaxNodes/MultiplicationNode.h" 25 | #include "SyntaxNodes/DivisionNode.h" 26 | #include "Constants.h" 27 | 28 | 29 | class Parser { 30 | std::vector removeEmptyLines(const std::vector &lines) { 31 | std::vector newLines; 32 | for (const std::string &line: lines) { 33 | int spaces = 0; 34 | for (const char &c: line) { 35 | if (std::isspace(c)) { 36 | spaces++; 37 | } 38 | } 39 | if (spaces != line.size()) { 40 | newLines.push_back(line); 41 | } 42 | } 43 | return newLines; 44 | } 45 | 46 | std::vector removeCommentLines(const std::vector &lines) { 47 | std::vector newLines; 48 | std::vector comments; 49 | for (const std::string &line: lines) { 50 | std::size_t len = line.size(); 51 | if (len >= 2 && line[len - 1] == '/' && line[len - 2] == '/') { 52 | comments.push_back(line); 53 | } else { 54 | newLines.push_back(line); 55 | } 56 | } 57 | return newLines; 58 | } 59 | 60 | std::vector getIndents(const std::vector &lines) { 61 | std::vector indents; 62 | for (const std::string &line: lines) { 63 | int spaces = 0; 64 | for (int i = 0; i < line.size() && std::isspace(line[i]); i++) { 65 | if (line[i] == ' ') { 66 | spaces++; 67 | } 68 | if (line[i] == '\t') { 69 | spaces += 4; 70 | } 71 | } 72 | indents.push_back(spaces); 73 | } 74 | return indents; 75 | } 76 | 77 | bool checkIndent(const std::vector &lines, const std::vector &indents) { 78 | for (int i = 1; i < indents.size(); i++) { 79 | if (indents[i] % 4 != 0 || indents[i] > indents[i - 1] + 4) { 80 | return false; 81 | } 82 | std::string word = split(strip(lines[i - 1]), ' ')[0]; 83 | bool increaseIndent = word == "for" || word == "if" || word == "else" || word == "function"; 84 | 85 | if (increaseIndent && indents[i] != indents[i - 1] + 4) { 86 | return false; 87 | } 88 | if (!increaseIndent && indents[i] > indents[i - 1]) { 89 | return false; 90 | } 91 | } 92 | if (indents[0] != 0) { 93 | return false; 94 | } 95 | return true; 96 | } 97 | 98 | public: 99 | Node *parse(const std::string &code) { 100 | std::vector lines = split(code, '\n'); 101 | lines = removeEmptyLines(lines); 102 | lines = removeCommentLines(lines); 103 | std::vector indents = getIndents(lines); 104 | if (!checkIndent(lines, indents)) { 105 | throw IndentError(); 106 | } 107 | 108 | Node *root = new Node{ROOT}; 109 | std::stack level; 110 | level.push(root); 111 | for (int i = 0; i < lines.size(); i++) { 112 | std::vector words = split(strip(lines[i]), ' '); 113 | std::string task = words[0]; 114 | int levelChange = 0; 115 | if (i > 0) { 116 | levelChange = (indents[i - 1] - indents[i]) / 4; 117 | } 118 | while (levelChange > 0) { 119 | level.pop(); 120 | levelChange--; 121 | } 122 | if (notFind(keywords, task)) { 123 | throw UnknownKeywordError(task); 124 | } 125 | Node *node; 126 | if (task == "let") { 127 | node = new VariableNode(words); 128 | level.top()->add(node); 129 | } 130 | if (task == "say") { 131 | node = new SayNode(words); 132 | level.top()->add(node); 133 | } 134 | if (task == "read") { 135 | node = new ReadNode(words); 136 | level.top()->add(node); 137 | } 138 | if (task == "if") { 139 | node = new IfNode(words); 140 | level.top()->add(node); 141 | level.push(node); 142 | } 143 | if (task == "else") { 144 | Node *prev = level.top()->body.back(); 145 | node = new ElseNode(prev); 146 | auto *ifNode = dynamic_cast(prev); 147 | ifNode->setElseBody(node->body); 148 | level.top()->add(node); 149 | level.push(node); 150 | } 151 | if (task == "for") { 152 | node = new ForNode(words); 153 | level.top()->add(node); 154 | level.push(node); 155 | } 156 | if (task == "function") { 157 | node = new FunctionDeclarationNode(words); 158 | level.top()->add(node); 159 | level.push(node); 160 | } 161 | if (task == "call") { 162 | node = new FunctionCallNode(words); 163 | level.top()->add(node); 164 | } 165 | if (task == "add") { 166 | node = new AdditionNode(words); 167 | level.top()->add(node); 168 | } 169 | if (task == "subtract") { 170 | node = new SubtractionNode(words); 171 | level.top()->add(node); 172 | } 173 | if (task == "multiply") { 174 | node = new MultiplicationNode(words); 175 | level.top()->add(node); 176 | } 177 | if (task == "divide") { 178 | node = new DivisionNode(words); 179 | level.top()->add(node); 180 | } 181 | } 182 | return root; 183 | } 184 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Story programming language 2 | [story-language.vercel.app](https://story-language.vercel.app) 3 | 4 | I started this project as a joke, but it turned out to be more than this. 5 | The language has a interpreter written in C++, and also a C++ transpiler. 6 | 7 | The syntax is weird on purpose, but also very easy to read for non programmers, easier than Python. 8 | The language features variables, input/output, arithmetics, if statements, for loops and functions. 9 | 10 | I also created a Web Assembly frontend for the language, and a small documentation. 11 | 12 | 13 | ## Rules 14 | 15 | 1. Identifiers must be capitalized 16 | 2. Numbers can be written only in letters, 57❌ fifty-seven✔️ 17 | 3. Can't use numbers outside the range [zero, ninety-nine] 18 | 4. Indentation must be a multiple of 4 19 | 20 | ## Variable declaration 21 | 22 | `let X` (defaults as 0) 23 | 24 | `let Y be ten` 25 | 26 | `let Z be Y` 27 | 28 | ## Output 29 | 30 | `say X` 31 | 32 | `say two` 33 | 34 | `say space` (evaluates as ' ') 35 | 36 | `say line` (evaluates as '\n') 37 | 38 | ## Input 39 | 40 | `read X` 41 | 42 | ## Arithmetic 43 | 44 | `add one to X` 45 | 46 | `subtract one from X` 47 | 48 | `multiply X by two` 49 | 50 | `divide X by two` 51 | 52 | ## If statement 53 | 54 | `if X is odd` 55 | 56 | `if X is even` 57 | 58 | `if X equals nine` 59 | 60 | `if X not-equals nine` 61 | 62 | `if X is less-than nine` 63 | 64 | `if X is greater-than nine` 65 | 66 | `if X is less-equal nine` 67 | 68 | `if X is greater-equal nine` 69 | 70 | `if X is divisible-by nine` 71 | 72 | ## Else statement 73 | ``` 74 | if X is divisible-by nine 75 | say X 76 | else 77 | say zero 78 | ``` 79 | 80 | ## For loop 81 | ``` 82 | for every N from five to Z 83 | for every M from Z to N 84 | ``` 85 | ## Comment 86 | 87 | `code//` 88 | 89 | ## Function declaration 90 | 91 | ``` 92 | function F 93 | say one 94 | ``` 95 | 96 | ## Function call 97 | 98 | `call F` 99 | 100 | ## Recursive Fibonacci sequence example 101 | ``` 102 | let A be zero 103 | 104 | let B be one 105 | let N be one 106 | 107 | function F 108 | let C be A 109 | add B to C 110 | let A be B 111 | let B be C 112 | 113 | say C 114 | say space 115 | 116 | add one to N 117 | 118 | if N less-equal ten 119 | call F 120 | 121 | call F 122 | ``` 123 | 124 | Output: 125 | 126 | ``` 127 | 1 2 3 5 8 13 21 34 55 89 128 | ``` 129 | 130 | ## Requirements 131 | 1. C++ 11 compiler 132 | 2. [Emscripten](https://emscripten.org) for Web Assembly 133 | 134 | ## How to run 135 | 1. In the root directory `emcc main.cpp --bind -o public/main.js -s NO_DISABLE_EXCEPTION_CATCHING` 136 | 2. Serve `public/index.html` 137 | 138 | [![Story website screenshot](https://i.imgur.com/URLas7U.png)](https://story-language.vercel.app) -------------------------------------------------------------------------------- /SyntaxNodes/AdditionNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Node.h" 5 | #include "../Exceptions.h" 6 | 7 | class AdditionNode : public Node { 8 | public: 9 | std::string receiver; 10 | std::string value; 11 | 12 | explicit AdditionNode(const std::vector &words) { 13 | if (words.size() != 4 || words[2] != "to" || std::islower(words[3][0])) { 14 | throw AdditionError(); 15 | } 16 | nodeType = ADD; 17 | value = words[1]; 18 | receiver = words[3]; 19 | } 20 | }; -------------------------------------------------------------------------------- /SyntaxNodes/DivisionNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "Node.h" 6 | #include "../Exceptions.h" 7 | 8 | class DivisionNode : public Node { 9 | public: 10 | std::string receiver; 11 | std::string value; 12 | 13 | explicit DivisionNode(const std::vector &words) { 14 | if (words.size() != 4 || words[2] != "by" || std::islower(words[1][0])) { 15 | throw DivisionError(); 16 | } 17 | nodeType = DIV; 18 | value = words[3]; 19 | receiver = words[1]; 20 | } 21 | }; -------------------------------------------------------------------------------- /SyntaxNodes/ElseNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Node.h" 4 | #include "../Exceptions.h" 5 | 6 | class ElseNode : public Node { 7 | public: 8 | explicit ElseNode(Node *node) { 9 | if (node->nodeType != IF) { 10 | throw ElseError(); 11 | } 12 | nodeType = ELSE; 13 | } 14 | }; -------------------------------------------------------------------------------- /SyntaxNodes/ForNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Node.h" 5 | #include "../Exceptions.h" 6 | 7 | class ForNode : public Node { 8 | public: 9 | std::string variable; 10 | std::string start; 11 | std::string stop; 12 | 13 | explicit ForNode(const std::vector &words) { 14 | if (words.size() != 7 || words[1] != "every" || words[3] != "from" || words[5] != "to" || 15 | std::islower(words[2][0])) { 16 | throw ForError(); 17 | } 18 | nodeType = FOR; 19 | variable = words[2]; 20 | start = words[4]; 21 | stop = words[6]; 22 | } 23 | }; -------------------------------------------------------------------------------- /SyntaxNodes/FunctionCallNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Node.h" 5 | #include "../Exceptions.h" 6 | 7 | class FunctionCallNode : public Node { 8 | public: 9 | std::string name; 10 | 11 | explicit FunctionCallNode(const std::vector &words) { 12 | if (words.size() != 2 || std::islower(words[1][0])) { 13 | throw FuncCallError(); 14 | } 15 | nodeType = FUNC_CALL; 16 | name = words[1]; 17 | } 18 | }; -------------------------------------------------------------------------------- /SyntaxNodes/FunctionDeclarationNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Node.h" 5 | #include "../Exceptions.h" 6 | 7 | 8 | class FunctionDeclarationNode : public Node { 9 | public: 10 | std::string name; 11 | 12 | explicit FunctionDeclarationNode(const std::vector &words) { 13 | if (words.size() != 2 || std::islower(words[1][0])) { 14 | throw FuncDeclError(); 15 | } 16 | nodeType = FUNC_DECL; 17 | name = words[1]; 18 | } 19 | }; -------------------------------------------------------------------------------- /SyntaxNodes/IfNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "Node.h" 6 | #include "../Exceptions.h" 7 | #include "../Constants.h" 8 | 9 | 10 | class IfNode : public Node { 11 | public: 12 | std::string variable; 13 | IfOperator ifOperator; 14 | std::string operand; 15 | std::vector *elseBody; 16 | 17 | explicit IfNode(const std::vector &words) { 18 | if (words.size() != 4 || notFind(ifOperators, words[2])) { 19 | throw IfError(); 20 | } 21 | if (words[2] == "is" && notFind({"even", "odd"}, words[3])) { 22 | throw IfError(); 23 | } 24 | nodeType = IF; 25 | variable = words[1]; 26 | ifOperator = ifOperators.at(words[2]); 27 | operand = words[3]; 28 | elseBody = nullptr; 29 | } 30 | 31 | void setElseBody(std::vector &nodes) { 32 | elseBody = &nodes; 33 | } 34 | }; -------------------------------------------------------------------------------- /SyntaxNodes/MultiplicationNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Node.h" 5 | #include "../Exceptions.h" 6 | 7 | class MultiplicationNode : public Node { 8 | public: 9 | std::string receiver; 10 | std::string value; 11 | 12 | explicit MultiplicationNode(const std::vector &words) { 13 | if (words.size() != 4 || words[2] != "by" || std::islower(words[1][0])) { 14 | throw MultiplicationError(); 15 | } 16 | nodeType = MUL; 17 | value = words[3]; 18 | receiver = words[1]; 19 | } 20 | 21 | }; -------------------------------------------------------------------------------- /SyntaxNodes/Node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../Exceptions.h" 5 | 6 | enum NodeType { 7 | ROOT, LET, FOR, IF, ELSE, SAY, READ, FUNC_DECL, FUNC_CALL, ADD, SUB, MUL, DIV 8 | }; 9 | 10 | enum IfOperator { 11 | IS, 12 | EQUALS, 13 | NOT_EQUALS, 14 | GREATER_THAN, 15 | GREATER_EQUAL, 16 | LESS_THAN, 17 | LESS_EQUAL, 18 | DIVISIBLE_BY, 19 | }; 20 | 21 | enum IfOperand { 22 | EVEN, 23 | ODD 24 | }; 25 | 26 | class Node { 27 | public: 28 | NodeType nodeType; 29 | std::vector body; 30 | 31 | Node() : nodeType(ROOT) {} 32 | 33 | explicit Node(NodeType nodeType) : nodeType(nodeType) {} 34 | 35 | virtual ~Node() = default; 36 | 37 | void add(Node *node) { 38 | body.push_back(node); 39 | } 40 | }; -------------------------------------------------------------------------------- /SyntaxNodes/ReadNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Node.h" 5 | #include "../Exceptions.h" 6 | 7 | class ReadNode : public Node { 8 | public: 9 | std::string variable; 10 | 11 | explicit ReadNode(const std::vector &words) { 12 | if (words.size() != 2) { 13 | throw ReadError(); 14 | } 15 | nodeType = READ; 16 | variable = words[1]; 17 | } 18 | }; -------------------------------------------------------------------------------- /SyntaxNodes/SayNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Node.h" 5 | #include "../Exceptions.h" 6 | 7 | class SayNode : public Node { 8 | public: 9 | std::string variable; 10 | 11 | explicit SayNode(const std::vector &words) { 12 | if (words.size() != 2) { 13 | throw SayError(); 14 | } 15 | nodeType = SAY; 16 | variable = words[1]; 17 | } 18 | }; -------------------------------------------------------------------------------- /SyntaxNodes/SubtractionNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Node.h" 5 | #include "../Exceptions.h" 6 | 7 | class SubtractionNode : public Node { 8 | public: 9 | std::string receiver; 10 | std::string value; 11 | 12 | explicit SubtractionNode(const std::vector &words) { 13 | if (words.size() != 4 || words[2] != "from" || std::islower(words[3][0])) { 14 | throw SubtractionError(); 15 | } 16 | nodeType = SUB; 17 | value = words[1]; 18 | receiver = words[3]; 19 | } 20 | }; -------------------------------------------------------------------------------- /SyntaxNodes/VariableNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Node.h" 5 | #include "../Exceptions.h" 6 | 7 | class VariableNode : public Node { 8 | public: 9 | std::string name; 10 | std::string value; 11 | 12 | explicit VariableNode(const std::vector &words) { 13 | if (words.size() == 2 && std::isupper(words[1][0])) { 14 | nodeType = LET; 15 | name = words[1]; 16 | value = "zero"; 17 | } else if (words.size() == 4 && std::isupper(words[1][0]) && words[2] == "be") { 18 | nodeType = LET; 19 | name = words[1]; 20 | value = words[3]; 21 | } else { 22 | throw VarDeclError(); 23 | } 24 | } 25 | }; -------------------------------------------------------------------------------- /Transpiler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "Parser.h" 7 | #include "Exceptions.h" 8 | 9 | class Transpiler { 10 | 11 | std::string code; 12 | std::string functions; 13 | std::stack indents; 14 | std::stack isFunction; 15 | std::vector variables; 16 | 17 | template 18 | void write(const char* format, Args... args) { 19 | if(isFunction.top()) { 20 | functions += indents.top() + fmt(format, args...) + "\n"; 21 | } 22 | else { 23 | code += indents.top() + fmt(format, args...) + "\n"; 24 | } 25 | } 26 | 27 | void translate(Node *node, int indentValue=0) { 28 | indents.emplace(repeatString(" ", indentValue * 4)); 29 | if (node->nodeType == ROOT) { 30 | write("{"); 31 | for (Node *n: node->body) { 32 | translate(n, indentValue + 1); 33 | } 34 | write("}"); 35 | } 36 | if (node->nodeType == LET) { 37 | auto *variableNode = dynamic_cast(node); 38 | if(notFind(variables, variableNode->name)) { 39 | variables.push_back(variableNode->name); 40 | } 41 | std::string value = variableNode->value; 42 | if(getConstant(value) != -1) { 43 | value = std::to_string(getConstant(value)); 44 | } 45 | write("%s = %s;", variableNode->name, value); 46 | } 47 | if (node->nodeType == SAY) { 48 | auto *sayNode = dynamic_cast(node); 49 | std::string variable = sayNode->variable; 50 | if(getConstant(variable) != -1) { 51 | variable = std::to_string(getConstant(variable)); 52 | } 53 | if(variable == "space") { 54 | variable = "' '"; 55 | } 56 | if(variable == "line") { 57 | variable = "'\\n'"; 58 | } 59 | write("cout << %s;", variable); 60 | } 61 | if (node->nodeType == READ) { 62 | auto *readNode = dynamic_cast(node); 63 | if(notFind(variables, readNode->variable)) { 64 | variables.emplace_back(readNode->variable); 65 | } 66 | write("cin >> %s;", readNode->variable); 67 | } 68 | if (node->nodeType == ADD) { 69 | auto *additionNode = dynamic_cast(node); 70 | std::string value = additionNode->value; 71 | if(getConstant(value) != -1) { 72 | value = std::to_string(getConstant(value)); 73 | } 74 | write("%s = %s + %s;", additionNode->receiver, additionNode->receiver, value); 75 | } 76 | if (node->nodeType == SUB) { 77 | auto *subtractionNode = dynamic_cast(node); 78 | std::string value = subtractionNode->value; 79 | if(getConstant(value) != -1) { 80 | value = std::to_string(getConstant(value)); 81 | } 82 | write("%s = %s - %s;", subtractionNode->receiver, subtractionNode->receiver, value); 83 | } 84 | if(node->nodeType == MUL) { 85 | auto *multiplicationNode = dynamic_cast(node);\ 86 | std::string value = multiplicationNode->value; 87 | if(getConstant(value) != -1) { 88 | value = std::to_string(getConstant(value)); 89 | } 90 | write("%s = %s * %s;", multiplicationNode->receiver, multiplicationNode->receiver, value); 91 | } 92 | if(node->nodeType == DIV) { 93 | auto *divisionNode = dynamic_cast(node); 94 | std::string value = divisionNode->value; 95 | if(getConstant(value) != -1) { 96 | value = std::to_string(getConstant(value)); 97 | } 98 | write("%s = %s / %s;", divisionNode->receiver, divisionNode->receiver, value); 99 | } 100 | if (node->nodeType == IF) { 101 | auto *ifNode = dynamic_cast(node); 102 | if(ifNode->ifOperator == IS) { 103 | std::string condition; 104 | if (ifNode->operand == "odd") { 105 | condition = "% 2 == 1"; 106 | } else { 107 | condition = "% 2 == 0"; 108 | } 109 | write("if (%s %s) {", ifNode->variable, condition); 110 | } 111 | else if(ifNode->ifOperator == DIVISIBLE_BY) { 112 | write("if (%s %% %s == 0) {", ifNode->variable, ifNode->operand); 113 | } 114 | else { 115 | static std::unordered_map operators = { 116 | {EQUALS, "=="}, 117 | {NOT_EQUALS, "!="}, 118 | {GREATER_THAN, ">"}, 119 | {GREATER_EQUAL, ">="}, 120 | {LESS_THAN, "<"}, 121 | {LESS_EQUAL, "<="} 122 | }; 123 | std::string operand = ifNode->operand; 124 | if(getConstant(operand) != -1) { 125 | operand = std::to_string(getConstant(operand)); 126 | } 127 | write("if (%s %s %s) {", ifNode->variable, operators.at(ifNode->ifOperator), operand); 128 | } 129 | for(Node *n: node->body) { 130 | translate(n, indentValue + 1); 131 | } 132 | write("}"); 133 | } 134 | if(node->nodeType == ELSE) { 135 | write("else {"); 136 | for(Node *n: node->body) { 137 | translate(n, indentValue + 1); 138 | } 139 | write("}"); 140 | } 141 | if(node->nodeType == FOR) { 142 | auto *forNode = dynamic_cast (node); 143 | std::string variable = forNode->variable; 144 | std::string start = forNode->start; 145 | std::string stop = forNode->stop; 146 | if(getConstant(start) != -1) { 147 | start = std::to_string(getConstant(start)); 148 | } 149 | if(getConstant(stop) != -1) { 150 | stop = std::to_string(getConstant(stop)); 151 | } 152 | if(notFind(variables, variable)) { 153 | variables.push_back(variable); 154 | } 155 | if(start <= stop) { 156 | write("for (%s = %s; %s <= %s; %s++) {", 157 | variable, start, variable, stop, variable); 158 | } 159 | else { 160 | write("for (%s = %s; %s >= %s; %s--) {", 161 | variable, start, variable, stop, variable); 162 | } 163 | for (Node *n: node->body) { 164 | translate(n, indentValue + 1); 165 | } 166 | write("}"); 167 | } 168 | if(node->nodeType == FUNC_DECL) { 169 | auto *funcDeclNode = dynamic_cast(node); 170 | isFunction.emplace(true); 171 | indents.emplace(""); 172 | indentValue = 0; 173 | write("void %s() {", funcDeclNode->name); 174 | for (Node *n: node->body) { 175 | translate(n, indentValue + 1); 176 | } 177 | write("}\n"); 178 | isFunction.pop(); 179 | indents.pop(); 180 | 181 | } 182 | if(node->nodeType == FUNC_CALL) { 183 | auto *funcCallNode = dynamic_cast(node); 184 | write("%s();", funcCallNode->name); 185 | } 186 | indents.pop(); 187 | } 188 | 189 | void deleteNode(Node *root) { 190 | for (Node *node: root->body) { 191 | deleteNode(node); 192 | } 193 | delete root; 194 | } 195 | 196 | void releaseMemory(Node *node) { 197 | deleteNode(node); 198 | variables.clear(); 199 | functions.clear(); 200 | while(!indents.empty()) { 201 | indents.pop(); 202 | } 203 | while(!isFunction.empty()) { 204 | isFunction.pop(); 205 | } 206 | } 207 | 208 | public: 209 | void run(Node *node) { 210 | indents.emplace(""); 211 | isFunction.push(false); 212 | translate(node); 213 | std::string codeStart = "#include \nusing namespace std;\n\n"; 214 | if(!variables.empty()) { 215 | codeStart += "int "; 216 | for(int i = 0; i < variables.size(); i++) { 217 | codeStart += variables[i]; 218 | if(i != variables.size() - 1) { 219 | codeStart += ", "; 220 | } 221 | } 222 | codeStart += ";\n\n"; 223 | } 224 | if(!functions.empty()) { 225 | codeStart += functions + "\n"; 226 | } 227 | codeStart += "int main() "; 228 | code = codeStart + code; 229 | releaseMemory(node); 230 | } 231 | std::string getCode() { 232 | std::string copy(code); 233 | code.clear(); 234 | return copy; 235 | } 236 | }; -------------------------------------------------------------------------------- /Util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | std::vector split(const std::string &s, char delimiter) { 9 | std::vector result; 10 | std::stringstream ss(s); 11 | std::string item; 12 | 13 | while (std::getline(ss, item, delimiter)) { 14 | result.push_back(item); 15 | } 16 | 17 | return result; 18 | } 19 | 20 | std::string strip(const std::string& str) { 21 | std::string result; 22 | int firstNonSpace = -1, lastNonSpace = -1; 23 | for(int i = 0; i < str.size(); i++) { 24 | if(!std::isspace(str[i])) { 25 | if(firstNonSpace == -1) { 26 | firstNonSpace = i; 27 | } 28 | lastNonSpace = i; 29 | } 30 | } 31 | if(firstNonSpace == -1 || lastNonSpace == -1) { 32 | return ""; 33 | } 34 | return str.substr(firstNonSpace, lastNonSpace - firstNonSpace + 1); 35 | } 36 | 37 | template 38 | bool find(const Container& container, const Value& value) { 39 | return std::find(container.begin(), container.end(), value) != container.end(); 40 | } 41 | 42 | bool find(const std::initializer_list& init_list, const std::string& value) { 43 | return std::find(init_list.begin(), init_list.end(), value) == init_list.end(); 44 | } 45 | 46 | template 47 | bool find(const std::unordered_map &container, const K &key) { 48 | return container.find(key) != container.end(); 49 | } 50 | 51 | template 52 | bool notFind(const Container& container, const Value& value) { 53 | return std::find(container.begin(), container.end(), value) == container.end(); 54 | } 55 | 56 | bool notFind(const std::initializer_list& init_list, const std::string& value) { 57 | return std::find(init_list.begin(), init_list.end(), value) == init_list.end(); 58 | } 59 | 60 | template 61 | bool notFind(const std::unordered_map &container, const K &key) { 62 | return container.find(key) == container.end(); 63 | } 64 | 65 | template 66 | T fmtArg(T t) { 67 | return t; 68 | } 69 | 70 | const char* fmtArg(const std::string& s) { 71 | return s.c_str(); 72 | } 73 | 74 | template 75 | std::string fmt(const char* format, Args... args) { 76 | size_t size = snprintf(nullptr, 0, format, fmtArg(args)...) + 1; 77 | std::unique_ptr buffer(new char[size]); 78 | 79 | snprintf(buffer.get(), size, format, fmtArg(args)...); 80 | return {buffer.get(), buffer.get() + size - 1}; 81 | } 82 | 83 | std::string repeatString(const std::string& str, int times) { 84 | std::string result; 85 | for(int i = 0; i < times; i++) { 86 | result += str; 87 | } 88 | return result; 89 | } 90 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "Parser.h" 5 | #include "Transpiler.h" 6 | #include "Interpreter.h" 7 | 8 | using namespace emscripten; 9 | 10 | struct CodeResult { 11 | std::string output; 12 | bool ok; 13 | 14 | CodeResult() : output(""), ok(false) {} 15 | 16 | CodeResult(const std::string &o, bool k) : output(o), ok(k) {} 17 | }; 18 | 19 | CodeResult transpileCode(const std::string &code) { 20 | try { 21 | Parser parser; 22 | Node *root = parser.parse(code); 23 | Transpiler transpiler; 24 | transpiler.run(root); 25 | return {transpiler.getCode(), true}; 26 | } 27 | catch (std::exception &e) { 28 | return {e.what(), false}; 29 | } 30 | } 31 | 32 | CodeResult runCode(const std::string &code) { 33 | try { 34 | Parser parser; 35 | Node *root = parser.parse(code); 36 | Interpreter interpreter; 37 | interpreter.run(root); 38 | return {interpreter.getResult(), true}; 39 | } catch (std::exception &e) { 40 | return {e.what(), false}; 41 | } 42 | } 43 | 44 | EMSCRIPTEN_BINDINGS(module) { 45 | value_object("RunCodeResult") 46 | .field("output", &CodeResult::output) 47 | .field("ok", &CodeResult::ok); 48 | function("transpileCode", &transpileCode); 49 | function("runCode", &runCode); 50 | } 51 | 52 | int main() {} 53 | 54 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soborat/story-programming-language/8efc96c5d977afb9b30f87ad097a6a3e070a39ee/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Story Language 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 |
16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 |
25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/index.js: -------------------------------------------------------------------------------- 1 | const leftPanel = document.querySelector('.panel.left'); 2 | const rightPanel = document.querySelector('.panel.right'); 3 | let currentTab = 0; 4 | const files = localStorage.getItem('files') 5 | ? JSON.parse(localStorage.getItem('files')) 6 | : [ 7 | { 8 | name: 'Main', 9 | content: 'for every I from one to ten\n' + 10 | ' for every J from one to I\n' + 11 | ' say J\n' + 12 | ' say space\n' + 13 | ' say line', 14 | }, 15 | ]; 16 | window.onload = function () { 17 | loadTabs(); 18 | if (files[currentTab]) { 19 | leftPanel.value = files[currentTab].content; 20 | } 21 | }; 22 | 23 | function navigateToTutorial() { 24 | window.open('tutorial.html', '_blank'); 25 | } 26 | 27 | function loadTabs() { 28 | const fileSystem = document.querySelector('.file-system'); 29 | files.forEach((file, index) => { 30 | const tab = document.createElement('div'); 31 | tab.classList.add('file-tab'); 32 | tab.setAttribute('data-index', index); 33 | 34 | const tabName = document.createTextNode(file.name); 35 | tab.appendChild(tabName); 36 | 37 | if (index !== 0) { 38 | const closeButton = document.createElement('div'); 39 | closeButton.classList.add('close-btn'); 40 | closeButton.textContent = '×'; 41 | closeButton.onclick = function (event) { 42 | event.stopPropagation(); 43 | if (confirm('Are you sure you want to delete this file?')) { 44 | tab.remove(); 45 | files.splice(index, 1); 46 | if(currentTab === index) { 47 | currentTab = 0; 48 | leftPanel.value = files[currentTab].content; 49 | } 50 | document.querySelectorAll('.file-tab').forEach((node) => node.remove()); 51 | loadTabs(); 52 | saveToLocalStorage(); 53 | } 54 | }; 55 | tab.appendChild(closeButton); 56 | } 57 | 58 | tab.onclick = function () { 59 | switchTab(tab); 60 | }; 61 | 62 | fileSystem.insertBefore(tab, fileSystem.lastElementChild); 63 | }); 64 | fileSystem.querySelector('.file-tab').classList.add('active'); 65 | } 66 | 67 | function createNewTab() { 68 | const fileName = prompt('Enter the name of the new file:'); 69 | if (fileName && fileName.length > 20) { 70 | alert('File name should be less than 20 characters!'); 71 | } else if (fileName && !files.find((file) => file.name === fileName)) { 72 | files.push({name: fileName, content: ''}); 73 | saveAllFiles(); 74 | document.querySelectorAll('.file-tab').forEach((node) => node.remove()); 75 | loadTabs(); 76 | } else if (files.find((file) => file.name === fileName)) { 77 | alert('File with this name already exists!'); 78 | } 79 | } 80 | 81 | function switchTab(tabElement) { 82 | saveAllFiles(); 83 | document.querySelectorAll('.file-tab').forEach((tab) => tab.classList.remove('active')); 84 | tabElement.classList.add('active'); 85 | currentTab = parseInt(tabElement.getAttribute('data-index')); 86 | leftPanel.value = files[currentTab].content; 87 | } 88 | 89 | function transpile() { 90 | let code = leftPanel.value; 91 | let res = Module.transpileCode(code); 92 | res.output = res.output.replace(//g, '>'); 94 | if(res.ok) { 95 | rightPanel.classList.remove('exception'); 96 | } 97 | else { 98 | rightPanel.classList.add('exception'); 99 | } 100 | rightPanel.innerHTML = res.output; 101 | saveAllFiles(); 102 | } 103 | function runCode() { 104 | let code = leftPanel.value; 105 | let res = Module.runCode(code); 106 | if(res.ok) { 107 | rightPanel.classList.remove('exception'); 108 | } 109 | else { 110 | rightPanel.classList.add('exception'); 111 | } 112 | document.querySelector('.panel.right').innerHTML = res.output; 113 | saveAllFiles(); 114 | } 115 | 116 | document.querySelector(".panel.left").addEventListener('keydown', function (e) { 117 | if (e.keyCode === 9) { 118 | e.preventDefault(); 119 | 120 | if (document.execCommand && typeof document.execCommand === "function") { 121 | document.execCommand('insertText', false, ' '); 122 | } else { 123 | const selection = window.getSelection(); 124 | const range = selection.getRangeAt(0); 125 | range.deleteContents(); 126 | const node = document.createTextNode(' '); 127 | range.insertNode(node); 128 | 129 | range.setStartAfter(node); 130 | range.setEndAfter(node); 131 | selection.removeAllRanges(); 132 | selection.addRange(range); 133 | } 134 | } 135 | }); 136 | 137 | function saveAllFiles() { 138 | files[currentTab].content = leftPanel.value; 139 | saveToLocalStorage(); 140 | } 141 | 142 | function saveToLocalStorage() { 143 | localStorage.setItem('files', JSON.stringify(files)); 144 | } 145 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Arial', sans-serif; 3 | background-color: #222; 4 | color: #f4f4f4; 5 | margin: 0; 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | height: 100vh; 10 | } 11 | 12 | .container { 13 | width: 90%; 14 | height: 90%; 15 | display: flex; 16 | flex-direction: column; 17 | } 18 | 19 | .file-system { 20 | display: flex; 21 | align-items: center; 22 | padding: 0.5em; 23 | border-bottom: 1px solid #555; 24 | margin-bottom: 1em; 25 | } 26 | 27 | .file-tab { 28 | display: flex; 29 | align-items: center; 30 | justify-content: space-between; 31 | padding: 0.8em 2em 0.8em 1em; 32 | cursor: pointer; 33 | margin-right: 0.5em; 34 | background-color: #444; 35 | transition: background-color 0.3s; 36 | border-radius: 5px; 37 | position: relative; 38 | } 39 | 40 | .file-tab.active { 41 | background-color: #555; 42 | } 43 | 44 | .file-tab .close-btn { 45 | color: white; 46 | border-radius: 50%; 47 | padding: 0.2em; 48 | width: 20px; 49 | height: 20px; 50 | display: flex; 51 | align-items: center; 52 | justify-content: center; 53 | position: absolute; 54 | right: 5px; 55 | top: 5px; 56 | cursor: pointer; 57 | } 58 | 59 | .panels-container { 60 | flex: 1; 61 | display: flex; 62 | align-items: stretch; 63 | height: 100%; 64 | } 65 | 66 | .panel { 67 | flex: 1; 68 | height: 100%; 69 | padding: 1em; 70 | border: 1px solid #555; 71 | box-sizing: border-box; 72 | background-color: #333; 73 | overflow-y: auto; 74 | color: white; 75 | font-size: 16px; 76 | font-family: Arial, sans-serif; 77 | resize: none; 78 | } 79 | 80 | .left-container { 81 | flex: 1; 82 | height: 100%; 83 | display: flex; 84 | flex-direction: column; 85 | margin-right: 1em; 86 | } 87 | 88 | .new-tab, .actions button { 89 | background-color: #555; 90 | border: none; 91 | padding: 10px 15px; 92 | color: #f4f4f4; 93 | cursor: pointer; 94 | transition: background-color 0.3s; 95 | display: block; 96 | } 97 | 98 | .actions button { 99 | margin-bottom: 0.5em; 100 | } 101 | 102 | .left, .right { 103 | outline: none; 104 | } 105 | 106 | .exception { 107 | color: red; 108 | } 109 | 110 | .actions { 111 | display: flex; 112 | flex-direction: column; 113 | justify-content: center; 114 | padding: 0 10px; 115 | } 116 | 117 | button.tutorial-btn { 118 | background-color: #4A90E2; 119 | } 120 | 121 | button.transpile-btn { 122 | background-color: #FF5733; 123 | } 124 | 125 | button.run-btn { 126 | background-color: #4CAF50; 127 | } 128 | 129 | button:hover { 130 | filter: brightness(90%); 131 | } -------------------------------------------------------------------------------- /public/tutorial.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 27 | 28 | 29 |
30 |

Rules

31 |
    32 |
  1. Identifiers must be capitalized
  2. 33 |
  3. Numbers can be written only in letters, 57❌ fifty-seven✔️
  4. 34 |
  5. Can't use numbers outside the range [zero, ninety-nine]
  6. 35 |
  7. Indentation must be a multiple of 4
  8. 36 |
37 |
38 |
39 |

Variable declaration

40 |

let X
(defaults as 0)

41 |

let Y be ten

42 |

let Z be Y

43 |
44 |
45 |

Output

46 |

say X

47 |

say two

48 |

say space
(evaluates as '  ')

49 |

say line
(evaluates as '\n')

50 |
51 |
52 |

Input

53 |

read X

54 |
55 |
56 |

Arithmetic

57 |

add one to X

58 |

subtract one from X

59 |

multiply X by two

60 |

divide X by two

61 |
62 |
63 |

If statement

64 |

if X is odd

65 |

if X is even

66 |

if X equals nine

67 |

if X not-equals nine

68 |

if X is less-than nine

69 |

if X is greater-than nine

70 |

if X is less-equal nine

71 |

if X is greater-equal nine

72 |

if X is divisible-by nine

73 | 74 |
75 |
76 |

Else statement

77 |

if X is divisible-by nine

78 |

    say X

79 |

else

80 |

    say zero

81 |
82 |
83 |

For loop

84 |

for every N from five to Z

85 |

    for every M from Z to N

86 |
87 |
88 |

Comment

89 |

code//

90 |
91 |
92 |

Function declaration

93 |

function F

94 |

    say one

95 |
96 |
97 |

Function call

98 |

call F

99 |
100 |
101 |

Recursive Fibonacci sequence code example

102 |
103 | let A be zero
104 | let B be one
105 | let N be one
106 | 
107 | function F
108 |     let C be A
109 |     add B to C
110 |     let A be B
111 |     let B be C
112 |     say C
113 |     say space
114 |     add one to N
115 |     if N less-equal ten
116 |         call F
117 | 
118 | call F
119 |     
120 |

Output:

121 |

1 2 3 5 8 13 21 34 55 89

122 |
123 | 124 | --------------------------------------------------------------------------------