summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--main.js341
1 files changed, 341 insertions, 0 deletions
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..61d7929
--- /dev/null
+++ b/main.js
@@ -0,0 +1,341 @@
+/* the clickbait headline programming language */
+
+const prog = `
+DISCOVER HOW TO factorial WITH n
+WE SAID
+ WHAT IF n IS ACTUALLY 0
+ WE SAID
+ SHOCKING DEVELOPMENT 1
+ LIES! WE SAID
+ SHOCKING DEVELOPMENT MULTIPLY n, factorial OF (n)
+ END OF STORY
+END OF STORY
+
+EXPERTS CLAIM result TO BE factorial OF (10)
+YOU WON'T WANT TO MISS 'RESULT IS'
+YOU WON'T WANT TO MISS result
+
+PLEASE LIKE AND SUBSCRIBE
+`
+
+/* tokenizer */
+
+/**
+ * Reads in char or word chunks
+ */
+class Reader {
+ constructor(str, base = '') {
+ this.base = base;
+ this.i = 0;
+ this.str = str;
+ }
+ peek() {
+ return this.str[this.i];
+ }
+ next() {
+ return this.str[this.i++];
+ }
+ hasNext() {
+ return this.str[this.i] !== undefined;
+ }
+ backstep() {
+ this.i--;
+ }
+ readUntil(pred) {
+ let result = this.base.slice();
+ while (this.hasNext() && !pred(this.peek())) {
+ result += this.next();
+ }
+ return result;
+ }
+ dropWhitespace() {
+ this.readUntil(c => !!c.trim());
+ }
+ expect(tok) {
+ const next = this.next();
+ if (next !== tok) {
+ throw new Error(`Parsing error: expected ${tok}, got ${next}`);
+ }
+ }
+}
+
+/**
+ * Split into words for easier tokenization
+ * with keywords.
+ */
+class Wordifier {
+ constructor(str) {
+ this.reader = new Reader(prog);
+ this.tokens = [];
+ }
+ wordify() {
+ if (this.tokens.length) return this.tokens;
+
+ while (this.reader.hasNext()) {
+ const next = this.reader.next();
+ switch (next) {
+ case '(': {
+ this.tokens.push('(');
+ break;
+ }
+ case ')': {
+ this.tokens.push(')');
+ break;
+ }
+ case ',': {
+ this.tokens.push(',');
+ break;
+ }
+ case '"':
+ case "'": {
+ this.wordifyString(next);
+ break;
+ }
+ default: {
+ // read until WS
+ this.reader.backstep();
+ this.tokens.push(this.reader.readUntil(c => {
+ return !c.trim() || ['(', ')', ','].includes(c)
+ }));
+ }
+ }
+ this.reader.dropWhitespace();
+ }
+ return this.tokens.slice(1);
+ }
+ wordifyString(endChar) {
+ let acc = '';
+ acc += this.reader.readUntil(c => c == endChar);
+ while (acc.endsWith('\\') || !this.reader.hasNext()) {
+ acc += this.reader.readUntil(c => c != endChar);
+ }
+ this.reader.next(); // throw away closing char
+ this.tokens.push('"' + acc);
+ }
+}
+
+const T = {
+ LParen: Symbol('LParen'),
+ RParen: Symbol('RParen'),
+ Comma: Symbol('Comma'),
+ DiscoverHowTo: Symbol('DiscoverHowTo'),
+ With: Symbol('With'),
+ Of: Symbol('Of'),
+ WeSaid: Symbol('WeSaid'),
+ WhatIf: Symbol('WhatIf'),
+ LiesBang: Symbol('LiesBang'),
+ EndOfStory: Symbol('EndOfStory'),
+ ExpertsClaim: Symbol('ExpertsClaim'),
+ ToBe: Symbol('ToBe'),
+ YouWontWantToMiss: Symbol('YouWontWantToMiss'),
+ IsActually: Symbol('IsActually'),
+ And: Symbol('And'),
+ or: Symbol('or'),
+ Add: Symbol('Add'),
+ Subtract: Symbol('Subtract'),
+ Multiply: Symbol('Multiply'),
+ Divide: Symbol('Divide'),
+ Modulo: Symbol('Modulo'),
+ Beats: Symbol('Beats'), // >
+ SmallerThan: Symbol('SmallerThan'), // <
+ ShockingDevelopment: Symbol('ShockingDevelopment'),
+ PleaseLikeAndSubscribe: Symbol('PleaseLikeAndSubscribe'),
+ StayTuned: Symbol('StayTuned'),
+ Unexpectedly: Symbol('Unexpectedly'),
+ TotallyRight: Symbol('TotallyRight'),
+ CompletelyWrong: Symbol('CompletelyWrong'),
+}
+
+class Tokenizer {
+ constructor(prog) {
+ this.reader = new Reader(new Wordifier(prog).wordify(), []);
+ this.tokens = [];
+ }
+ tokenize() {
+ if (this.tokens.length) return this.tokens;
+
+ while (this.reader.hasNext()) {
+ const next = this.reader.next();
+ switch (next) {
+ case 'DISCOVER': {
+ this.reader.expect('HOW');
+ this.reader.expect('TO');
+ this.tokens.push(T.DiscoverHowTo);
+ break;
+ }
+ case 'WITH': {
+ this.tokens.push(T.With);
+ break;
+ }
+ case 'OF': {
+ this.tokens.push(T.Of);
+ break;
+ }
+ case 'WE': {
+ this.reader.expect('SAID');
+ this.tokens.push(T.WeSaid);
+ break;
+ }
+ case 'WHAT': {
+ this.reader.expect('IF');
+ this.tokens.push(T.WhatIf);
+ break;
+ }
+ case 'LIES!': {
+ this.tokens.push(T.LiesBang);
+ break;
+ }
+ case 'END': {
+ this.reader.expect('OF');
+ this.reader.expect('STORY');
+ this.tokens.push(T.EndOfStory);
+ break;
+ }
+ case 'EXPERTS': {
+ this.reader.expect('CLAIM');
+ this.tokens.push(T.ExpertsClaim);
+ break;
+ }
+ case 'TO': {
+ this.reader.expect('BE');
+ this.tokens.push(T.ToBe);
+ break;
+ }
+ case 'YOU': {
+ this.reader.expect('WON\'T');
+ this.reader.expect('WANT');
+ this.reader.expect('TO');
+ this.reader.expect('MISS');
+ this.tokens.push(T.YouWontWantToMiss);
+ break;
+ }
+ case 'IS': {
+ this.reader.expect('ACTUALLY');
+ this.tokens.push(T.IsActually);
+ break;
+ }
+ case 'AND': {
+ this.tokens.push(T.And);
+ break;
+ }
+ case 'OR': {
+ this.tokens.push(T.Or);
+ break;
+ }
+ case 'ADD': {
+ this.tokens.push(T.Add);
+ break;
+ }
+ case 'SUBTRACT': {
+ this.tokens.push(T.Subtract);
+ break;
+ }
+ case 'MULTIPLY': {
+ this.tokens.push(T.Multiply);
+ break;
+ }
+ case 'DIVIDE': {
+ this.tokens.push(T.Divide);
+ break;
+ }
+ case 'MODULO': {
+ this.tokens.push(T.Modulo);
+ break;
+ }
+ case 'BEATS': {
+ this.tokens.push(T.Beats);
+ break;
+ }
+ case 'SMALLER': {
+ this.reader.expect('THAN');
+ this.tokens.push(T.SmallerThan);
+ break;
+ }
+ case 'SHOCKING': {
+ this.reader.expect('DEVELOPMENT');
+ this.tokens.push(T.ShockingDevelopment);
+ break;
+ }
+ case 'PLEASE': {
+ this.reader.expect('LIKE');
+ this.reader.expect('AND');
+ this.reader.expect('SUBSCRIBE');
+ this.tokens.push(T.PleaseLikeAndSubscribe);
+ break;
+ }
+ case 'STAY': {
+ this.reader.expect('TUNED');
+ this.tokens.push(T.StayTuned);
+ break;
+ }
+ case 'UNEXPECTEDLY': {
+ this.tokens.push(T.Unexpectedly);
+ break;
+ }
+ case 'TOTALLY': {
+ this.reader.expect('RIGHT');
+ this.tokens.push(T.TotallyRight);
+ break;
+ }
+ case 'COMPLETELY': {
+ this.reader.expect('WRONG');
+ this.tokens.push(T.CompletelyWrong);
+ break;
+ }
+ case '(': {
+ this.tokens.push(T.LParen);
+ break;
+ }
+ case ')': {
+ this.tokens.push(T.RParen);
+ break;
+ }
+ case ',': {
+ this.tokens.push(T.Comma);
+ break;
+ }
+ default: {
+ if (!isNaN(parseFloat(next))) {
+ // number literal
+ this.tokens.push(parseFloat(next));
+ } else {
+ // string or varname
+ this.tokens.push(next);
+ }
+ }
+ }
+ }
+ return this.tokens;
+ }
+}
+
+function tokenize(prog) {
+ const reader = new Reader(prog);
+}
+
+/* parser */
+
+const N = {
+ NumberLiteral: Symbol('NumberLiteral'),
+ StringLiteral: Symbol('StringLiteral'),
+ FnLiteral: Symbol('FnLiteral'),
+ FnCall: Symbol('FnCall'),
+ Ident: Symbol('Ident'),
+ BinaryOp: Symbol('BinaryOp'),
+ IfExpr: Symbol('IfExpr'),
+ ExprList: Symbol('ExprList'),
+ // etc
+}
+
+/* executor (tree walk) */
+
+class Environment {
+ constructor(nodes) {
+
+ }
+}
+
+const tokens = new Tokenizer(prog).tokenize();
+console.log(tokens);
+
+const reader = new Reader("test of this");