From f2aec5ca1434ff4dc43c51b47faa8347e1ebf3e2 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 4 Jan 2026 23:37:57 -0800 Subject: Better impl --- src/autocomplete.ts | 66 ++++++++++++++++++++++++++++++++++++++++++++++ src/highlight.ts | 61 ++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 76 ++++++++++++++++++++++++++++------------------------- src/syntax.grammar | 53 +++++++++++++++++++------------------ 4 files changed, 194 insertions(+), 62 deletions(-) create mode 100644 src/autocomplete.ts create mode 100644 src/highlight.ts (limited to 'src') diff --git a/src/autocomplete.ts b/src/autocomplete.ts new file mode 100644 index 0000000..6f894ad --- /dev/null +++ b/src/autocomplete.ts @@ -0,0 +1,66 @@ +import { + CompletionContext, + CompletionResult, + Completion, +} from "@codemirror/autocomplete"; + +const keywords: Completion[] = [ + // Function and variable declarations + { label: "DISCOVER HOW TO", type: "keyword", info: "Function declaration" }, + { label: "WITH", type: "keyword", info: "Function parameters" }, + { label: "EXPERTS CLAIM", type: "keyword", info: "Variable assignment" }, + { label: "TO BE", type: "keyword", info: "Assignment operator" }, + + // Control flow + { label: "WHAT IF", type: "keyword", info: "If statement" }, + { label: "LIES!", type: "keyword", info: "Else statement" }, + { label: "SHOCKING DEVELOPMENT", type: "keyword", info: "Return statement" }, + + // Blocks + { label: "RUMOR HAS IT", type: "keyword", info: "Block start" }, + { label: "END OF STORY", type: "keyword", info: "Block end" }, + + // I/O + { label: "YOU WON'T WANT TO MISS", type: "keyword", info: "Print statement" }, + { label: "LATEST NEWS ON", type: "keyword", info: "Input statement" }, + + // Boolean literals + { label: "TOTALLY RIGHT", type: "keyword", info: "True" }, + { label: "COMPLETELY WRONG", type: "keyword", info: "False" }, + + // Operators + { label: "IS ACTUALLY", type: "keyword", info: "Equality comparison" }, + { label: "BEATS", type: "keyword", info: "Greater than" }, + { label: "SMALLER THAN", type: "keyword", info: "Less than" }, + { label: "AND", type: "keyword", info: "Logical AND" }, + { label: "OR", type: "keyword", info: "Logical OR" }, + { label: "PLUS", type: "keyword", info: "Addition" }, + { label: "MINUS", type: "keyword", info: "Subtraction" }, + { label: "TIMES", type: "keyword", info: "Multiplication" }, + { label: "DIVIDED BY", type: "keyword", info: "Division" }, + { label: "MODULO", type: "keyword", info: "Modulus" }, + { label: "OF", type: "keyword", info: "Function call" }, + + // Program end + { + label: "PLEASE LIKE AND SUBSCRIBE", + type: "keyword", + info: "End of program", + }, +]; + +export function tabloidCompletion( + context: CompletionContext, +): CompletionResult | null { + const word = context.matchBefore(/[A-Z'][A-Z\-'\s]*/); + + if (!word || (word.from === word.to && !context.explicit)) { + return null; + } + + return { + from: word.from, + options: keywords, + filter: false, // Let CodeMirror handle filtering + }; +} diff --git a/src/highlight.ts b/src/highlight.ts new file mode 100644 index 0000000..7835929 --- /dev/null +++ b/src/highlight.ts @@ -0,0 +1,61 @@ +import { styleTags, tags as t } from "@lezer/highlight"; + +export const highlighting = styleTags({ + // Comments + Comment: t.lineComment, + + // Literals + NumberLiteral: t.number, + StringLiteral: t.string, + BooleanLiteral: t.bool, + + // Identifiers + Identifier: t.variableName, + "FunctionDecl/Identifier": t.function(t.definition(t.variableName)), + "FunctionCall/Identifier": t.function(t.variableName), + "ArgumentList/Identifier": t.variableName, + + // Keywords - Definitions (use controlKeyword for better color) + discoverHowTo: t.controlKeyword, + expertsClaim: t.keyword, + toBe: t.operator, + with: t.keyword, + of: t.keyword, + + // Keywords - Control flow + whatIf: t.controlKeyword, + liesBang: t.controlKeyword, + shockingDevelopment: t.controlKeyword, + + // Keywords - I/O + youWontWantToMiss: t.keyword, + latestNewsOn: t.keyword, + + // Keywords - Block delimiters + rumorHasIt: t.brace, + endOfStory: t.brace, + + // Keywords - Program structure + pleaseLikeAndSubscribe: t.moduleKeyword, + + // Operators - Comparison + isActually: t.compareOperator, + beats: t.compareOperator, + smallerThan: t.compareOperator, + + // Operators - Logical + and: t.logicOperator, + or: t.logicOperator, + + // Operators - Arithmetic + plus: t.arithmeticOperator, + minus: t.arithmeticOperator, + times: t.arithmeticOperator, + dividedBy: t.arithmeticOperator, + modulo: t.arithmeticOperator, + + // Punctuation + "(": t.paren, + ")": t.paren, + ",": t.separator, +}); diff --git a/src/index.ts b/src/index.ts index 14b4ffe..690572b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,51 +1,55 @@ -import {parser} from "./syntax.grammar" -import {LRLanguage, LanguageSupport, indentNodeProp, foldNodeProp, foldInside, delimitedIndent} from "@codemirror/language" -import {styleTags, tags as t} from "@lezer/highlight" +import { parser } from "./syntax.grammar"; +import { + LRLanguage, + LanguageSupport, + indentNodeProp, + foldNodeProp, + foldInside, + delimitedIndent, +} from "@codemirror/language"; +import { highlighting } from "./highlight"; +import { tabloidCompletion } from "./autocomplete"; +import { autocompletion } from "@codemirror/autocomplete"; + +// Wrap parser to debug +const originalParse = parser.parse.bind(parser); +parser.parse = function (input, fragments, ranges) { + console.log( + "[PARSER] parse called with input:", + typeof input === "string" ? input.substring(0, 100) : input, + ); + const result = originalParse(input, fragments, ranges); + console.log("[PARSER] parse result:", result.toString()); + return result; +}; export const tabloidLanguage = LRLanguage.define({ parser: parser.configure({ props: [ indentNodeProp.add({ - Block: delimitedIndent({closing: "endOfStory", align: false}), - ParenBlock: delimitedIndent({closing: ")", align: false}), - FunctionDecl: context => context.baseIndent + context.unit, - IfStatement: context => context.baseIndent + context.unit, + Block: delimitedIndent({ closing: "endOfStory", align: false }), + ParenBlock: delimitedIndent({ closing: ")", align: false }), + FunctionDecl: (context) => context.baseIndent + context.unit, + IfStatement: (context) => context.baseIndent + context.unit, }), foldNodeProp.add({ Block: foldInside, ParenBlock: foldInside, }), - styleTags({ - Identifier: t.variableName, - NumberLiteral: t.number, - StringLiteral: t.string, - BooleanLiteral: t.bool, - FunctionCall: t.function(t.variableName), - discoverHowTo: t.definitionKeyword, - expertsClaim: t.definitionKeyword, - toBe: t.operatorKeyword, - rumorHasIt: t.keyword, - endOfStory: t.keyword, - whatIf: t.controlKeyword, - liesBang: t.controlKeyword, - shockingDevelopment: t.controlKeyword, - youWontWantToMiss: t.keyword, - latestNewsOn: t.keyword, - pleaseLikeAndSubscribe: t.keyword, - "with of": t.keyword, - "isActually beats smallerThan": t.compareOperator, - "and or": t.logicOperator, - "plus minus times dividedBy modulo": t.arithmeticOperator, - "( )": t.paren, - ",": t.separator - }) - ] + highlighting, + ], }), languageData: { - commentTokens: {} - } -}) + commentTokens: {}, + closeBrackets: { brackets: ["(", '"', "'"] }, + autocomplete: tabloidCompletion, + }, +}); export function tabloid() { - return new LanguageSupport(tabloidLanguage) + return new LanguageSupport(tabloidLanguage, [ + autocompletion({ override: [tabloidCompletion] }), + ]); } + +export { tabloidCompletion }; diff --git a/src/syntax.grammar b/src/syntax.grammar index bffd9c4..ce7ba38 100644 --- a/src/syntax.grammar +++ b/src/syntax.grammar @@ -85,17 +85,18 @@ ProgramEnd { pleaseLikeAndSubscribe } -@skip { space | newline } +@skip { space | newline | Comment } @tokens { space { $[ \t]+ } newline { $[\n\r] } + Comment { "#" (![\n\r] )* } identifierChar { $[A-Z_\-] | $[0-9] | "'" } word { ($[A-Z_\-] | "'") identifierChar* } - @precedence { StringLiteral, NumberLiteral, BooleanLiteral, discoverHowTo, expertsClaim, latestNewsOn, pleaseLikeAndSubscribe, rumorHasIt, shockingDevelopment, whatIf, youWontWantToMiss, toBe, with, of, endOfStory, liesBang, isActually, dividedBy, smallerThan, and, or, plus, minus, times, modulo, beats, Identifier } + @precedence { Comment, StringLiteral, NumberLiteral, BooleanLiteral, discoverHowTo, expertsClaim, latestNewsOn, pleaseLikeAndSubscribe, rumorHasIt, shockingDevelopment, whatIf, youWontWantToMiss, toBe, with, of, endOfStory, liesBang, isActually, dividedBy, smallerThan, and, or, plus, minus, times, modulo, beats, Identifier } Identifier { word } @@ -105,30 +106,30 @@ ProgramEnd { BooleanLiteral { "TOTALLY" space "RIGHT" | "COMPLETELY" space "WRONG" } - discoverHowTo { "DISCOVER" space "HOW" space "TO" } - with { "WITH" } - of { "OF" } - expertsClaim { "EXPERTS" space "CLAIM" } - toBe { "TO" space "BE" } - rumorHasIt { "RUMOR" space "HAS" space "IT" } - whatIf { "WHAT" space "IF" } - liesBang { "LIES!" } - endOfStory { "END" space "OF" space "STORY" } - shockingDevelopment { "SHOCKING" space "DEVELOPMENT" } - youWontWantToMiss { "YOU" space "WON'T" space "WANT" space "TO" space "MISS" } - latestNewsOn { "LATEST" space "NEWS" space "ON" } - pleaseLikeAndSubscribe { "PLEASE" space "LIKE" space "AND" space "SUBSCRIBE" } - - isActually { "IS" space "ACTUALLY" } - and { "AND" } - or { "OR" } - plus { "PLUS" } - minus { "MINUS" } - times { "TIMES" } - dividedBy { "DIVIDED" space "BY" } - modulo { "MODULO" } - beats { "BEATS" } - smallerThan { "SMALLER" space "THAN" } + discoverHowTo[@name="discoverHowTo"] { "DISCOVER" space "HOW" space "TO" } + with[@name="with"] { "WITH" } + of[@name="of"] { "OF" } + expertsClaim[@name="expertsClaim"] { "EXPERTS" space "CLAIM" } + toBe[@name="toBe"] { "TO" space "BE" } + rumorHasIt[@name="rumorHasIt"] { "RUMOR" space "HAS" space "IT" } + whatIf[@name="whatIf"] { "WHAT" space "IF" } + liesBang[@name="liesBang"] { "LIES!" } + endOfStory[@name="endOfStory"] { "END" space "OF" space "STORY" } + shockingDevelopment[@name="shockingDevelopment"] { "SHOCKING" space "DEVELOPMENT" } + youWontWantToMiss[@name="youWontWantToMiss"] { "YOU" space "WON'T" space "WANT" space "TO" space "MISS" } + latestNewsOn[@name="latestNewsOn"] { "LATEST" space "NEWS" space "ON" } + pleaseLikeAndSubscribe[@name="pleaseLikeAndSubscribe"] { "PLEASE" space "LIKE" space "AND" space "SUBSCRIBE" } + + isActually[@name="isActually"] { "IS" space "ACTUALLY" } + and[@name="and"] { "AND" } + or[@name="or"] { "OR" } + plus[@name="plus"] { "PLUS" } + minus[@name="minus"] { "MINUS" } + times[@name="times"] { "TIMES" } + dividedBy[@name="dividedBy"] { "DIVIDED" space "BY" } + modulo[@name="modulo"] { "MODULO" } + beats[@name="beats"] { "BEATS" } + smallerThan[@name="smallerThan"] { "SMALLER" space "THAN" } "(" ")" "," } -- cgit v1.2.3-70-g09d2