sellquiz
Version:
An open source domain-specific language for online assessment
228 lines • 9.32 kB
JavaScript
/******************************************************************************
* SELL - SIMPLE E-LEARNING LANGUAGE *
* *
* Copyright (c) 2019-2021 TH Köln *
* Author: Andreas Schwenk, contact@compiler-construction.com *
* *
* Partly funded by: Digitale Hochschule NRW *
* https://www.dh.nrw/kooperationen/hm4mint.nrw-31 *
* *
* GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 *
* *
* This library is licensed as described in LICENSE, which you should have *
* received as part of this distribution. *
* *
* This software is distributed on "AS IS" basis, WITHOUT WARRENTY OF ANY *
* KIND, either impressed or implied. *
******************************************************************************/
import { SellInput, SellInputElementType } from './quiz.js';
import { Lexer } from './lex.js';
import { symtype, SellSymbol } from './symbol.js';
export class ParseText {
constructor(parent) {
this.p = parent;
}
// title =
// { ID | "#" ID | MISC } "\n";
parseTitle() {
this.p.parseWhitespaces = true;
let title = '';
while (!this.p.is('§EOL') && !this.p.is('§END')) {
if (this.p.isIdent()) {
title += this.p.tk;
this.p.next();
}
else if (this.p.is('#')) {
this.p.next();
if (this.p.isIdent()) {
let tag = this.p.tk; // TODO: yet unused
this.p.next();
}
else
this.p.err("expected identifer after '#'");
}
else {
title += this.p.charToHTML(this.p.tk);
this.p.next();
}
}
this.p.terminal('§EOL');
this.p.parseWhitespaces = false;
this.p.q.titleHtml = title;
}
endItemizeIfApplicable() {
if (this.p.isItemizeItem)
this.p.q.html += '</li>';
if (this.p.isItemize)
this.p.q.html += '</ul>';
this.p.isItemizeItem = false;
this.p.isItemize = false;
}
// text =
// { single_multiple_choice | itemize | inline_listing | listing
// | inline_math | im_input | ID | MISC };
parseText(parsingHint = false) {
this.p.parseWhitespaces = true;
while (!this.p.is('§END') && !this.p.is('§CODE_START')) {
if (parsingHint && this.p.is("§EOL"))
break;
// end itemize, if applicable
if (this.p.tk_col == 1 && !this.p.is('*'))
this.endItemizeIfApplicable();
// parse
if (this.p.is('§EOL') && this.p.singleMultipleChoiceFeedbackHTML.length > 0) {
this.p.q.html += ' ' + this.p.singleMultipleChoiceFeedbackHTML;
this.p.singleMultipleChoiceFeedbackHTML = '';
this.p.q.html += ']§'; // end of single-multiple choice
}
if (this.p.is('§EOL') && this.p.isItemizeItem) {
this.p.next();
this.p.q.html += '</li>';
this.p.isItemizeItem = false;
}
else if (this.p.tk_col == 1 && this.p.is("["))
this.parseSingleMultipleChoice(false /*multiple choice*/);
else if (this.p.tk_col == 1 && this.p.is("("))
this.parseSingleMultipleChoice(true /*single choice*/);
else if (this.p.tk_col == 1 && this.p.is('*'))
this.parseItemize();
else if (this.p.is('`'))
this.p.q.html += this.parseInlineListing();
else if (this.p.is('```'))
this.p.q.html += this.parseListing();
else if (this.p.is('$'))
this.p.imParser.parseInlineMath();
else if (this.p.is('#'))
this.p.q.html += this.p.imInputParser.parseIM_Input();
else if (this.p.isIdent()) {
// "__"/"_" are used to stard and end bold/italic font.
// Tokens include underscores in general for most part of SELL, especially the code part.
// Splitting is done here for text.
let tokens = Lexer.splitStringAndKeepDelimiters(this.p.tk, ["__", "_"]);
this.p.next();
for (let i = 0; i < tokens.length; i++) {
if (tokens[i] === '_') {
this.p.isItalicFont = !this.p.isItalicFont;
this.p.q.html += this.p.isItalicFont ? '<i>' : '</i>';
}
else if (tokens[i] === '__') {
this.p.isBoldFont = !this.p.isBoldFont;
this.p.q.html += this.p.isBoldFont ? '<b>' : '</b>';
}
else
this.p.q.html += tokens[i];
}
}
else {
this.p.q.html += this.p.charToHTML(this.p.tk);
this.p.next();
}
}
this.p.parseWhitespaces = false;
this.endItemizeIfApplicable();
}
// itemize =
// "*";
parseItemize() {
this.p.terminal('*');
if (this.p.isItemize == false) {
this.p.q.html += '<ul>';
}
this.p.isItemize = true;
this.p.isItemizeItem = true;
this.p.q.html += '<li>';
}
// single_multiple_choice =
// "(" ("x"|expr) ")"
// | "[" ("x"|expr) "]";
parseSingleMultipleChoice(isSingleChoice) {
this.p.parseWhitespaces = false;
let correct = false;
if (isSingleChoice)
this.p.terminal("(");
else
this.p.terminal("[");
if (this.p.is("x")) {
this.p.next();
correct = true;
}
else if (!this.p.is("]") && !this.p.is(")") && !this.p.is(' ')) {
this.p.codeParser.parseExpr();
let v = this.p.q.stack.pop();
if (v.type != symtype.T_BOOL)
this.p.err("expression must be boolean");
correct = v.value;
}
if (isSingleChoice)
this.p.terminal(")");
else
this.p.terminal("]");
this.p.parseWhitespaces = true;
let prefix = isSingleChoice ? "_sc_" : "_mc_";
let sym = new SellSymbol(symtype.T_BOOL, correct);
let symId = prefix + this.p.createUniqueID();
this.p.q.symbols[symId] = sym;
this.p.q.solutionSymbols[symId] = sym;
let input = new SellInput();
input.htmlElementId = "sellquiz_input_" + symId;
input.solutionVariableId = symId;
input.htmlElementId_feedback = "sellquiz_feedback_" + symId;
input.htmlElementInputType = SellInputElementType.CHECKBOX;
this.p.q.inputs.push(input);
let inputType = isSingleChoice ? "radio" : "checkbox";
let checked = "";
this.p.q.html += '\n§[';
this.p.q.html += '<input id="' + input.htmlElementId + '" type="' + inputType + '" name="sell_input" ' + checked + '/> ';
this.p.singleMultipleChoiceFeedbackHTML = ' <span id="' + input.htmlElementId_feedback + '"></span>\n';
}
// inline_listing =
// "`" { MISC } "`";
parseInlineListing() {
let html = '';
this.p.terminal('`');
html += '<code class="text-primary">';
while (!this.p.is('`') && !this.p.is('§END')) {
html += this.p.tk;
this.p.next();
}
this.p.terminal('`');
html += '</code>';
return html;
}
// listing =
// "```" { MISC } "```";
parseListing() {
let code = '';
this.p.terminal('```');
while (!this.p.is('```') && !this.p.is('§END')) {
if (this.p.is("§EOL")) {
code += '\n';
this.p.next();
while (this.p.is(" ")) {
code += ' ';
this.p.next();
}
}
else if (this.p.is("\t")) {
code += ' ';
this.p.next();
}
else {
code += this.p.tk;
this.p.next();
}
}
code = code.replaceAll('<', '<');
code = code.replaceAll('>', '>');
code = code.replaceAll('\n', '<br/>');
this.p.terminal('```');
let html = '';
html += '<hr class="mt-2 mb-0"/>';
html += '<code class="text-primary">';
html += code;
html += '</code>';
html += '<hr class="mt-0 mb-0"/>';
return html;
}
}
//# sourceMappingURL=parse-text.js.map