Skip to content

Commit

Permalink
🧹 Remove global variables from syntax highlighter
Browse files Browse the repository at this point in the history
The syntax highlighter used to work by calling `initTranslations()` with
the current level and language, which then manipulates global variables
so that the syntax highlighter code has the right information to
highlight the different levels and languages of Hedy correctly.

To remove the need for global variables, we instead parameterize the
Lezer parsers and pass the level and language into the generator at
every time it is used.
  • Loading branch information
rix0rrr committed Jan 4, 2025
1 parent 51f1612 commit ffc8ef2
Show file tree
Hide file tree
Showing 29 changed files with 952 additions and 792 deletions.
35 changes: 35 additions & 0 deletions build-tools/heroku/generate-lezer-parsers
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,40 @@ cd $scriptdir
for grammarFile in ../../highlighting/lezer-grammars/level*.grammar ; do
baseName=$(basename "$grammarFile")
tsFile=${baseName%.grammar}-parser.ts

# We need to parameterize the parsers generated by lezer-generator with a level and a language.
# There is limited customizability in the tool, but lezer-generator will
# generate a source file with a predictable format, that we can line-edit.
npx lezer-generator --typeScript "$grammarFile" -o ../../static/js/lezer-parsers/$tsFile

out=../../static/js/lezer-parsers/$tsFile
mv $out $out.tmp

echo "// This file was generated by lezer-generator. You probably shouldn't edit it." >> $out
echo 'import {LRParser} from "@lezer/lr"' >> $out
echo 'import {specializeKeywordGen, extendKeywordGen} from "./tokens"' >> $out
echo 'export function generateParser(level: number, language: string): LRParser {' >> $out

# So we don't get "unused import" errors
if grep specializeKeyword $out.tmp > /dev/null; then
echo ' const specializeKeyword = specializeKeywordGen(level, language);' >> $out
else
echo ' void specializeKeywordGen;' >> $out
fi

# So we don't get "unused import" errors
if grep extendKeyword $out.tmp > /dev/null; then
echo ' const extendKeyword = extendKeywordGen(level, language);' >> $out
else
echo ' void extendKeywordGen;' >> $out
fi

echo ' return LRParser.deserialize({' >> $out
tail -n +5 $out.tmp >> $out
echo '}' >> $out

rm $out.tmp



done
1 change: 1 addition & 0 deletions hedy_translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def get_target_keyword(keyword_dict, keyword):
return keyword



def translate_keywords(input_string, from_lang="en", to_lang="nl", level=1):
""" "Return code with keywords translated to language of choice in level of choice"""

Expand Down
17 changes: 7 additions & 10 deletions static/js/adventure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import ClassicEditor from "./ckeditor";
import { CustomWindow } from './custom-window';
import { languagePerLevel, keywords } from "./lezer-parsers/language-packages";
import { SyntaxNode } from "@lezer/common";
import { initializeTranslation } from "./lezer-parsers/tokens";
import DOMPurify from "dompurify";
import TRADUCTION_IMPORT from '../../highlighting/highlighting-trad.json';
import { convert } from "./utils";
Expand All @@ -25,7 +24,7 @@ export async function initializeCustomAdventurePage(_options: InitializeCustomiz
const editorSolutionExampleContainer = document.querySelector('#adventure-solution-editor') as HTMLElement;
// Initialize the editor with the default language
let lang = (document.querySelector('#languages_dropdown') as HedySelect).selected[0]
const TRADUCTIONS = convert(TRADUCTION_IMPORT) as Map<string, Map<string,string>>;
const TRADUCTIONS = convert(TRADUCTION_IMPORT) as Map<string, Map<string,string>>;
if (!TRADUCTIONS.has(lang)) { lang = 'en'; }
let TRADUCTION = TRADUCTIONS.get(lang) as Map<string,string>;

Expand All @@ -46,7 +45,7 @@ export async function initializeCustomAdventurePage(_options: InitializeCustomiz

// Autosave customize adventure page
autoSave("customize_adventure")

showWarningIfMultipleLevels()
document.querySelectorAll('#levels_dropdown div div .option').forEach((el) => {
el.addEventListener('click', () => {
Expand Down Expand Up @@ -104,7 +103,7 @@ function formatKeyword(name: string) {

function findCoincidences(name: string, TRADUCTION: Map<string, string>) {
let coincidences = [];
for (const [key, regexString] of TRADUCTION) {
for (const [key, regexString] of TRADUCTION) {
if (new RegExp(`^(${regexString})$`, 'gu').test(name)) {
coincidences.push(key)
}
Expand Down Expand Up @@ -143,10 +142,8 @@ function initializeEditor(language: string, editorContainer: HTMLElement, soluti
export function addCurlyBracesToCode(code: string, level: number, language: string = 'en') {
// If code already has curly braces, we don't do anything about it
if (code.match(/\{(\w|_)+\}/g)) return code

initializeTranslation({keywordLanguage: language, level: level})

let parser = languagePerLevel[level];
let parser = languagePerLevel[level](language);
let parseResult = parser.parse(code);
let formattedCode = ''
let previous_node: SyntaxNode | undefined = undefined
Expand Down Expand Up @@ -205,17 +202,17 @@ export function addCurlyBracesToCode(code: string, level: number, language: stri
j += 1;
}
formattedCode = resultingLines.join('\n');

return formattedCode;
}

export function addCurlyBracesToKeyword(name: string) {
let lang = (document.querySelector('#languages_dropdown') as HedySelect).selected[0]
const TRADUCTIONS = convert(TRADUCTION_IMPORT) as Map<string, Map<string,string>>;
const TRADUCTIONS = convert(TRADUCTION_IMPORT) as Map<string, Map<string,string>>;
if (!TRADUCTIONS.has(lang)) { lang = 'en'; }
let TRADUCTION = TRADUCTIONS.get(lang) as Map<string,string>;

for (const [key, regexString] of TRADUCTION) {
for (const [key, regexString] of TRADUCTION) {
if ((new RegExp(`^(${regexString})$`, 'gu').test(name)) || name === key) {
return `{${key}}`
}
Expand Down
40 changes: 12 additions & 28 deletions static/js/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { LocalSaveWarning } from './local-save-warning';
import { HedyEditor, EditorType } from './editor';
import { stopDebug } from "./debugging";
import { HedyCodeMirrorEditorCreator } from './cm-editor';
import { initializeTranslation } from './lezer-parsers/tokens';
import { initializeActivity } from './user-activity';
import { IndexTabs, SwitchAdventureEvent } from './index-tabs';
export let theGlobalDebugger: any;
Expand Down Expand Up @@ -218,7 +217,6 @@ export function initializeCodePage(options: InitializeCodePageOptions) {
if ($editor.length) {
const dir = $('body').attr('dir');
theGlobalEditor = editorCreator.initializeEditorWithGutter($editor, EditorType.MAIN, dir);
initializeTranslation({keywordLanguage: theKeywordLanguage, level: theLevel});
attachMainEditorEvents(theGlobalEditor);
initializeDebugger({
editor: theGlobalEditor,
Expand Down Expand Up @@ -369,10 +367,6 @@ export function initializeViewProgramPage(options: InitializeViewProgramPageOpti
// We need to enable the main editor for the program page as well
const dir = $('body').attr('dir');
theGlobalEditor = editorCreator.initializeEditorWithGutter($('#editor'), EditorType.MAIN, dir);
initializeTranslation({
keywordLanguage: options.lang,
level: options.level
});
attachMainEditorEvents(theGlobalEditor);
theGlobalEditor.contents = options.code;
initializeDebugger({
Expand All @@ -386,12 +380,6 @@ export function initializeViewProgramPage(options: InitializeViewProgramPageOpti
export function initializeHighlightedCodeBlocks(where: Element, initializeAll?: boolean) {
const dir = $("body").attr("dir");
initializeParsons();
if (theLevel) {
initializeTranslation({
keywordLanguage: theKeywordLanguage,
level: theLevel
})
}
// Any code blocks we find inside 'turn-pre-into-ace' get turned into
// read-only editors (for syntax highlighting)
for (const container of $(where).find('.turn-pre-into-ace').get()) {
Expand Down Expand Up @@ -452,11 +440,7 @@ function convertPreviewToEditor(preview: HTMLPreElement, container: HTMLElement,
const levelStr = $(preview).attr('level');
const lang = $(preview).attr('lang');
if (levelStr && lang) {
initializeTranslation({
keywordLanguage: lang,
level: parseInt(levelStr, 10),
})
exampleEditor.setHighlighterForLevel(parseInt(levelStr, 10));
exampleEditor.setHighlighterForLevel(parseInt(levelStr, 10), lang);
}
}

Expand All @@ -474,8 +458,8 @@ export function stopit() {
document.onkeydown = null;
$('#keybinding_modal').hide();
$('#sleep_modal').hide();
if (sleepRunning) {

if (sleepRunning) {
sleepRunning = false;
}

Expand Down Expand Up @@ -586,7 +570,7 @@ export async function runit(level: number, lang: string, raw: boolean, disabled_
error.showWarning(response.Warning);
}


if (adventure && response.save_info) {
adventure.save_info = response.save_info;
adventure.editor_contents = code;
Expand Down Expand Up @@ -676,7 +660,7 @@ function updateProgramCount() {
const countText = programCountDiv.text();
const regex = /(\d+)/;
const match = countText.match(regex);

if (match && match.length > 0) {
const currentCount = parseInt(match[0]);
const newCount = currentCount - 1;
Expand All @@ -688,7 +672,7 @@ function updateProgramCount() {
function updateSelectOptions(selectName: string) {
let optionsArray: string[] = [];
const select = $(`select[name='${selectName}']`);

// grabs all the levels and names from the remaining adventures
$(`[id="program_${selectName}"]`).each(function() {
const text = $(this).text().trim();
Expand Down Expand Up @@ -725,8 +709,8 @@ export async function delete_program(id: string, prompt: string) {
updateSelectOptions('adventure');
// this function decreases the total programs saved
updateProgramCount();
const response = await postJson('/programs/delete', { id });
const response = await postJson('/programs/delete', { id });

// issue request on the Bar component.
modal.notifySuccess(response.message);
});
Expand Down Expand Up @@ -1002,7 +986,7 @@ export function runPythonProgram(this: any, code: string, sourceMap: any, hasTur
$('#stopit').hide();
$('#runit').show();
$('#runit').show();
if (Sk.execLimit != 1) {
if (Sk.execLimit != 1) {
return ClientMessages ['Program_too_long'];
} else {
return null;
Expand Down Expand Up @@ -1051,7 +1035,7 @@ export function runPythonProgram(this: any, code: string, sourceMap: any, hasTur
}

// Check if the program was correct but the output window is empty: Return a warning
if ((!hasClear) && $('#output').is(':empty') && $('#turtlecanvas').is(':empty') && !hasMusic) {
if ((!hasClear) && $('#output').is(':empty') && $('#turtlecanvas').is(':empty') && !hasMusic) {
error.showWarning(ClientMessages['Empty_output']);
return;
}
Expand Down Expand Up @@ -1186,7 +1170,7 @@ export function runPythonProgram(this: any, code: string, sourceMap: any, hasTur
function builtinRead(x: string) {
if (x in skulptExternalLibraries) {
const tmpPath = skulptExternalLibraries[x]["path"];

let request = new XMLHttpRequest();
request.open("GET", tmpPath, false);
request.send();
Expand Down Expand Up @@ -1762,7 +1746,7 @@ function updatePageElements() {
$('#commands_dropdown_container').show()
$('#hand_in_button').show()
}
if (currentTab === 'parsons'){
if (currentTab === 'parsons'){
$('#share_program_button').hide()
$('#read_outloud_button_container').hide()
$('#cheatsheet_dropdown_container').hide()
Expand Down
Loading

0 comments on commit ffc8ef2

Please sign in to comment.