From e79998a8684467e940dfd00ce577c6d183c0a796 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" Date: Sun, 9 Oct 2022 11:57:27 +0200 Subject: [PATCH 001/120] moved code back to where it belongs --- .../tutor/lang/rascal/tutor/Compiler.rsc | 651 ++++++ .../tutor/lang/rascal/tutor/Includer.rsc | 49 + .../tutor/lang/rascal/tutor/Indexer.rsc | 186 ++ .../tutor/lang/rascal/tutor/Names.rsc | 48 + .../tutor/lang/rascal/tutor/Output.rsc | 13 + .../rascal/tutor/apidoc/DeclarationInfo.rsc | 22 + .../lang/rascal/tutor/apidoc/ExtractInfo.rsc | 178 ++ .../rascal/tutor/apidoc/GenerateMarkdown.rsc | 149 ++ .../lang/rascal/tutor/conversions/ADtoMD.rsc | 118 ++ .../tutor/conversions/ConvertSections.rsc | 131 ++ .../rascal/tutor/conversions/Includes.rsc | 68 + .../Test/CallAnalysis/CallAnalysis.md | 119 ++ .../examples/Test/CallAnalysis/calls.png | Bin 0 -> 8677 bytes .../lang/rascal/tutor/examples/Test/If/If.md | 43 + .../Test/Libraries/Boolean/Boolean.remote | 1 + .../examples/Test/Libraries/Libraries.md | 2 + .../Test/Questions/Questions.questions | 66 + .../lang/rascal/tutor/examples/Test/Test.md | 48 + .../rascal/tutor/examples/Test/Test.quest | 1 + .../lang/rascal/tutor/examples/Test/t1.png | Bin 0 -> 312 bytes .../tutor/questions/ConvertQuestions.rsc | 71 + .../lang/rascal/tutor/questions/Feedback.java | 131 ++ .../rascal/tutor/questions/ParseQuestions.rsc | 8 + .../tutor/questions/QuestionCompiler.java | 81 + .../tutor/questions/QuestionCompiler.rsc | 530 +++++ .../lang/rascal/tutor/questions/Questions.rsc | 253 +++ .../rascal/tutor/questions/SavedQuestions.txt | 1769 +++++++++++++++++ .../rascal/tutor/questions/ValueGenerator.rsc | 333 ++++ .../rascal/tutor/questions/tutor-prelude.js | 434 ++++ .../tutor/repl/TutorCommandExecutor.java | 167 ++ .../tutor/repl/TutorCommandExecutor.rsc | 68 + .../repl/TutorCommandExecutorCreator.java | 96 + 32 files changed, 5834 insertions(+) create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/Includer.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/ADtoMD.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/ConvertSections.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/Includes.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/CallAnalysis.md create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/calls.png create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/If/If.md create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Libraries/Boolean/Boolean.remote create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Libraries/Libraries.md create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Questions/Questions.questions create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Test.md create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Test.quest create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/t1.png create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ConvertQuestions.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/questions/Feedback.java create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ParseQuestions.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/questions/Questions.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/questions/SavedQuestions.txt create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ValueGenerator.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/questions/tutor-prelude.js create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutorCreator.java diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc new file mode 100644 index 00000000000..23593398883 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -0,0 +1,651 @@ +@bootstrapParser +@synopsis{compiles .rsc and .md files to markdown by executing Rascal-specific code and inlining its output} +@description{ + This compiler collects .rsc files and .md files from a PathConfig's srcs folders. + + Every .rsc file is compiled to a .md file with an outline of the declarations contained + in the file and the contents of the @synopsis, @description, @pitfalls, @benefits, @examples + tags with those declarations. @doc is also supported for backward compatibility's purposes. + The resulting markdown is processed by the rest of the compiler, as if written by hand. + + Every .md file is scanned for rascal-shell between triple backticks elements. The contents between the backticks are + executed by a private Rascal REPL and the output is captured in different ways. Normal IO + via stderr and stdout is literally printed back and HTML or image output is inlined into + the document. + + For (nested) folders in the srcs folders, which do not contain an `index.md` file, or + a `.md` file where the name is equal to the name of the current folder, a fresh index.md + file is generated. +} +module lang::rascal::tutor::Compiler + +import Message; +import Exception; +import IO; +import String; +import Node; +import List; +import Relation; +import Location; +import ParseTree; +import util::Reflective; +import util::FileSystem; +import ValueIO; + +import lang::xml::IO; +import lang::yaml::Model; +import lang::rascal::tutor::repl::TutorCommandExecutor; +import lang::rascal::tutor::apidoc::GenerateMarkdown; +import lang::rascal::tutor::apidoc::ExtractInfo; +import lang::rascal::tutor::Indexer; +import lang::rascal::tutor::Names; +import lang::rascal::tutor::Output; +import lang::rascal::tutor::Includer; +import lang::rascal::\syntax::Rascal; + + + + +public PathConfig defaultConfig + = pathConfig( + bin=|target://rascal/doc|, + srcs=[ + |project://rascal/src/org/rascalmpl/courses/Rascalopedia|, + |project://rascal/src/org/rascalmpl/courses/CompileTimeErrors|, + |project://rascal/src/org/rascalmpl/courses/RascalConcepts|, + |project://rascal/src/org/rascalmpl/courses/Recipes|, + |project://rascal/src/org/rascalmpl/courses/Tutor|, + |project://rascal/src/org/rascalmpl/courses/GettingStarted|, + |project://rascal/src/org/rascalmpl/courses/GettingHelp|, + |project://rascal/src/org/rascalmpl/courses/WhyRascal|, + |project://rascal/src/org/rascalmpl/courses/Rascal|, + |project://rascal/src/org/rascalmpl/courses/RascalShell|, + |project://rascal/src/org/rascalmpl/courses/RunTimeErrors|, + |project://rascal/src/org/rascalmpl/courses/Developers|, + |project://rascal/src/org/rascalmpl/library| + ]); + +public PathConfig onlyAPIconfig + = defaultConfig[srcs=[|project://rascal/src/org/rascalmpl/library|]]; + +public list[Message] lastErrors = []; + +public void defaultCompile() { + remove(defaultConfig.bin, recursive=true); + errors = compile(defaultConfig); + + for (e <- errors) { + println(": + ' <}>"); + } + + lastErrors = errors; +} + +@synopsis{compiles each pcfg.srcs folder as a course root} +list[Message] compile(PathConfig pcfg, CommandExecutor exec = createExecutor(pcfg)) { + ind = createConceptIndex(pcfg); + + return [*compileCourse(dropSlash(src), pcfg[currentRoot=src], exec, ind) | src <- pcfg.srcs]; +} + +loc dropSlash(loc src) = src.file == "" ? src[path=src.path[..-1]] : src; + +list[Message] compileCourse(loc root, PathConfig pcfg, CommandExecutor exec, Index ind) + = compileDirectory(root, pcfg[currentRoot=root], exec, ind); + +list[Message] compile(loc src, PathConfig pcfg, CommandExecutor exec, Index ind, int sidebar_position=-1) { + println("\rcompiling "); + + // new concept, new execution environment: + exec.reset(); + + if (isDirectory(src), src.file != "internal") { + return compileDirectory(src, pcfg, exec, ind, sidebar_position=sidebar_position); + } + else if (src.extension == "rsc") { + return compileRascalFile(src, pcfg[currentFile=src], exec, ind); + } + else if (src.extension in {"md"}) { + return compileMarkdownFile(src, pcfg, exec, ind, sidebar_position=sidebar_position); + } + else if (src.extension in {"png","jpg","svg","jpeg", "html", "js"}) { + try { + copy(src, pcfg.bin + "assets" + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, src).path); + + return []; + } + catch IO(str message): { + return [error(message, src)]; + } + } + else { + return []; + } +} + +list[Message] compileDirectory(loc d, PathConfig pcfg, CommandExecutor exec, Index ind, int sidebar_position=-1) { + indexFiles = {(d + "")[extension="md"], (d + "index.md")}; + + if (!exists(d)) { + return [error("Course does not exist ", d)]; + } + + output = []; + nestedDtls = []; + + if (i <- indexFiles && exists(i)) { + // this can only be a markdown file (see above) + output = compileMarkdown(i, pcfg[currentFile=i], exec, ind, sidebar_position=sidebar_position); + + i.file = (i.file == i.parent[extension="md"].file) ? "index.md" : i.file; + + writeFile(pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, i)[extension="md"].path, + " + '<}>" + ); + + if (details(list[str] xxx) <- output) { + // here we give the details list declared in `details` header + // on to compute the right sidebar_positions down for the nested + // concepts + nestedDtls = xxx; + } + } + else { + generateIndexFile(d, pcfg, sidebar_position=sidebar_position); + } + + return [ + *[e | err(e) <- output], + *[*compile(s, pcfg, exec, ind, sidebar_position=sp) + | s <- d.ls + , !(s in indexFiles) + , isDirectory(s) || s.extension in {"md","rsc","png","jpg","svg","jpeg", "html", "js"} + , int sp := indexOf(nestedDtls, capitalize(s[extension=""].file)) + ] + ]; +} + +list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) { + try { + p2r = pathToRoot(pcfg.currentRoot, d); + title = replaceAll(relativize(pcfg.currentRoot, d).path[1..], "/", "::"); + writeFile(pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, d).path + "index.md", + "--- + 'title: <} else {><}> + '<if (sidebar_position != -1) {>sidebar_position: <sidebar_position> + '<}>--- + ' + '<for (e <- d.ls, isDirectory(e) || e.extension in {"rsc", "md"}, e.file != "internal") {> + '* [<e[extension=""].file>](<p2r>/<capitalize(pcfg.currentRoot.file)><relativize(pcfg.currentRoot, e)[extension=isDirectory(e)?"":"md"].path>)<}>"); + return []; + } catch IO(msg): { + return [error(msg, d)]; + } +} + +@synopsis{Translates Rascal source files to docusaurus markdown.} +list[Message] compileRascalFile(loc m, PathConfig pcfg, CommandExecutor exec, Index ind) { + list[Output] output = generateAPIMarkdown(relativize(pcfg.currentRoot, m).parent.path, m, pcfg, exec, ind); + + writeFile(pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, m)[extension="md"].path, + "<for (out(x) <- output) {><x> + '<}>" + ); + + return [e | err(e) <- output]; +} + +@synopsis{This uses another nested directory listing to construct information for the TOC embedded in the current document.} +list[str] createDetailsList(loc m, PathConfig pcfg) + = sort([ "<capitalize(pcfg.currentRoot.file)>:<if (isDirectory(d), !exists(d + "index.md"), !exists((d + d.file)[extension="md"])) {>package:<}><if (d.extension == "rsc") {>module:<}><replaceAll(relativize(pcfg.currentRoot, d)[extension=""].path[1..], "/", "-")>" | d <- m.parent.ls, m != d, d.file != "index.md", isDirectory(d) || d.extension in {"rsc", "md"}]); + +list[Message] compileMarkdownFile(loc m, PathConfig pcfg, CommandExecutor exec, Index ind, int sidebar_position=-1) { + order = createDetailsList(m, pcfg); + + list[Output] output = compileMarkdown(m, pcfg[currentFile=m], exec, ind, order, sidebar_position=sidebar_position) + [Output::empty()]; + + // turn A/B/B.md into A/B/index.md for better URLs in the end result (`A/B/`` is better than `A/B/B.html`) + m.file = (m.file == m.parent[extension="md"].file) ? "index.md" : m.file; + + writeFile(pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, m)[extension="md"].path, + "<for (out(x) <- output) {><x> + '<}>" + ); + + return [e | err(e) <- output]; +} + +list[Output] compileMarkdown(loc m, PathConfig pcfg, CommandExecutor exec, Index ind, int sidebar_position=-1) { + order = createDetailsList(m, pcfg); + + return compileMarkdown(readFileLines(m), 1, 0, pcfg[currentFile=m], exec, ind, order, sidebar_position=sidebar_position) + [Output::empty()]; +} + +@synopsis{Skip double quoted blocks} +list[Output] compileMarkdown([str first:/^\s*``````/, *block, str second:/^``````/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) + = [ + out(first), + *[out(b) | b <-block], + out(second), + *compileMarkdown(rest, line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + ]; + +@synopsis{Include Rascal code from Rascal source files} +list[Output] compileMarkdown([str first:/^\s*```rascal-include<rest1:.*>$/, *str components, /^\s*```/, *str rest2], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) { + return[ + Output::empty(), // must have an empty line + out("```rascal <rest1>"), + *[*prepareModuleForInclusion(item, /includeHeaders/ := rest1, /includeTests/ := rest1, pcfg) | item <- components], + Output::empty(), + out("```"), + *compileMarkdown(rest2, line + 1 + size(components) + 1, offset + length(first) + length(components), pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + ]; +} + +@synopsis{execute _rascal-shell_ blocks on the REPL} +list[Output] compileMarkdown([str first:/^\s*```rascal-shell<rest1:.*>$/, *block, /^\s*```/, *str rest2], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) + = [ Output::empty(), // must have an empty line + out("```rascal-shell <rest1>"), + *compileRascalShell(block, /error/ := rest1, /continue/ := rest1, line+1, offset + size(first) + 1, pcfg, exec, ind), + out("```"), + *compileMarkdown(rest2, line + 1 + size(block) + 1, offset + size(first) + length(block), pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + ]; + +@synopsis{execute _rascal-shell-prepare_ blocks on the REPL} +list[Output] compileMarkdown([str first:/^\s*```rascal-prepare<rest1:.*>$/, *block, /^\s*```/, *str rest2], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) + = [ + *compileRascalShellPrepare(block, /continue/ := rest1, line+1, offset + size(first) + 1, pcfg, exec, ind), + *compileMarkdown(rest2, line + 1 + size(block) + 1, offset + size(first) + length(block), pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + ]; + +@synopsis{inline an itemized list of details (collected from the details YAML section in the header)} +list[Output] compileMarkdown([str first:/^\s*\(\(\(\s*TOC\s*\)\)\)\s*$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) + = [ + *[*compileMarkdown(["* ((<d>))"], line, offset, pcfg, exec, ind, []) | d <- dtls], + *compileMarkdown(rest, line + 1, offset + size(first), pcfg, exec, ind, [], sidebar_position=sidebar_position) + ] + + + [ + err(warning("TOC is empty. details section is missing from header?", pcfg.currentFile(offset, 1, <line, 0>, <line, 1>))) + | dtls == [] + ]; + +@synopsis{inline an itemized list of details (collected from the details YAML section in the header)} +list[Output] compileMarkdown([str first:/^\s*\(\(\(\s*TODO<msg:[^\)]*>\s*\)\)\)\s*$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) + = [ + out(":::caution"), + out("There is a \"TODO\" in the documentation source:"), + out("\t<msg>"), + out(first), + out(":::"), + err(warning("TODO: <trim(msg)>", pcfg.currentFile(offset, 1, <line, 0>, <line, 1>))), + *compileMarkdown(rest, line + 1, offset + size(first), pcfg, exec, ind, [], sidebar_position=sidebar_position) + ]; + +@synopsis{Inline example files literally, in Rascal loc notation, but do not compile further from there. Works only if positioned on a line by itself.} +list[Output] compileMarkdown([str first:/^\s*\(\(\|<url:[^\|]+>\|\)\)\s*$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) { + try { + return [ + *[out(l) | str l <- split("\n", readFile(readTextValueString(#loc, "|<url>|")))], + *compileMarkdown(rest, line + 1, offset + size(first), pcfg, exec, ind, [], sidebar_position=sidebar_position) + ]; + } + catch value x: { + return [ + err(error("Could not read <url> for inclusion: <x>", pcfg.currentFile(offset, 1, <line, 1>, <line, 2>))), + *compileMarkdown(rest, line + 1, offset + size(first), pcfg, exec, ind, [], sidebar_position=sidebar_position) + ]; + } +} + +@synopsis{implement subscript syntax for [aeh-pr-vx] (the subscript alphabet is incomplete in unicode)} +list[Output] compileMarkdown([/^<prefix:.*>~<digits:[aeh-pr-vx0-9\(\)+\-]+>~<postfix:.*>$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) + = compileMarkdown(["<prefix><for (ch <- chars(digits)) {><subscripts["<char(ch)>"]><}><postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position); + +@synopsis{detect unsupported subscripts} +list[Output] compileMarkdown([/^<prefix:.*>~<digits:[^~]*[^aeh-pr-vx0-9]+[^~]*>~<postfix:.*>$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) + = [ + err(error("Unsupported subscript character in <digits>", pcfg.currentFile(offset, 1, <line, 1>, <line, 2>))), + *compileMarkdown(["<prefix><digits><postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + ]; + +@synopsis{Resolve [labeled]((links))} +list[Output] compileMarkdown([/^<prefix:.*>\[<title:[^\]]+>\]\(\(<link:[A-Za-z0-9\-\ \t\.\:]+>\)\)<postfix:.*>$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) { + resolution = ind[removeSpaces(link)]; + p2r = pathToRoot(pcfg.currentRoot, pcfg.currentFile); + + switch (resolution) { + case {str u}: { + u = /^\/assets/ := u ? u : "<p2r><u>"; + return compileMarkdown(["<prefix>[<title>](<u>)<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position); + } + case { }: { + if (/^<firstWord:[A-Za-z0-9\-\.\:]+>\s+<secondWord:[A-Za-z0-9\-\.\:]+>/ := link) { + // give this a second chance, in reverse + return compileMarkdown(["<prefix>[<title>]((<secondWord>-<firstWord>))<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position); + } + + return [ + err(error("Broken concept link: <link>", pcfg.currentFile(offset, 1, <line,0>,<line,1>))), + *compileMarkdown(["<prefix>_(<title>) <link> (broken link)_<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + ]; + } + case {_, _, *_}: { + // ambiguous resolution, first try and resolve within the current course: + if ({str u} := ind["<capitalize(pcfg.currentRoot.file)>:<removeSpaces(link)>"]) { + u = /^\/assets/ := u ? u : "<p2r><u>"; + return compileMarkdown(["<prefix>[<title>](<u>)<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position); + } + else if ({str u} := ind["<capitalize(pcfg.currentRoot.file)>-<removeSpaces(link)>"]) { + u = /^\/assets/ := u ? u : "<p2r><u>"; + return compileMarkdown(["<prefix>[<title>](<u>)<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position); + } + // or we check if its one of the details of the current concept + else if ({str u} := ind["<capitalize(pcfg.currentRoot.file)>:<fragment(pcfg.currentRoot, pcfg.currentFile)>-<removeSpaces(link)>"]) { + u = /^\/assets/ := u ? u : "<p2r><u>"; + return compileMarkdown(["<prefix>[<title>](<u>)<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position); + } + + return [ + err(error("Ambiguous concept link: <removeSpaces(link)> resolves to all of these: <for (r <- resolution) {><r> <}>", pcfg.currentFile(offset, 1, <line,0>,<line,1>), + cause="Please choose from the following options to disambiguate: <for (<str k, str v> <- rangeR(ind, ind[removeSpaces(link)]), {_} := ind[k]) {> + ' <k> resolves to <v><}>")), + *compileMarkdown(["<prefix> **broken:<link> (ambiguous)** <postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + ]; + } + } + + return [err(error("Unexpected state of link resolution for <link>: <resolution>", pcfg.currentFile(offset, 1, <line,0>,<line,1>)))]; +} + +@synopsis{Resolve unlabeled links} +default list[Output] compileMarkdown([/^<prefix:.*>\(\(<link:[A-Za-z0-9\-\ \t\.\:]+>\)\)<postfix:.*>$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) { + resolution = ind[removeSpaces(link)]; + p2r = pathToRoot(pcfg.currentRoot, pcfg.currentFile); + + switch (resolution) { + case {u}: { + u = /^\/assets/ := u ? u : "<p2r><u>"; + return compileMarkdown(["<prefix>[<addSpaces(link)>](<u>)<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position); + } + case { }: { + if (/^<firstWord:[A-Za-z0-9\-\.\:]+>\s+<secondWord:[A-Za-z0-9\-\.\:]+>/ := link) { + // give this a second chance, in reverse + return compileMarkdown(["<prefix>((<secondWord>-<firstWord>))<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position); + } + + return [ + err(error("Broken concept link: <link>", pcfg.currentFile(offset, 1, <line,0>,<line,1>))), + *compileMarkdown(["<prefix>_<link> (broken link)_<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + ]; + } + case {str plink, /<qlink:.*>\/index\.md/}: + if (plink == qlink) { + return compileMarkdown(["<prefix>[<addSpaces(link)>](<p2r><plink>/)<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position); + } + else { + fail; + } + + case {_, _, *_}: { + // ambiguous resolution, first try and resolve within the current course: + if ({u} := ind["<capitalize(pcfg.currentRoot.file)>:<removeSpaces(link)>"]) { + u = /^\/assets/ := u ? u : "<p2r><u>"; + return compileMarkdown(["<prefix>[./<addSpaces(link)>](<u>)<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position); + } + else if ({u} := ind["<capitalize(pcfg.currentRoot.file)>-<removeSpaces(link)>"]) { + u = /^\/assets/ := u ? u : "<p2r><u>"; + return compileMarkdown(["<prefix>[<addSpaces(link)>](<u>)<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position); + } + // or we check if its one of the details of the current concept + else if ({u} := ind["<capitalize(pcfg.currentRoot.file)>:<capitalize(pcfg.currentFile[extension=""].file)>-<removeSpaces(link)>"]) { + u = /^\/assets/ := u ? u : "<p2r><u>"; + return compileMarkdown(["<prefix>[<addSpaces(link)>](<u>)<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position); + } + + return [ + err(error("Ambiguous concept link: <removeSpaces(link)> resolves to all of these: <for (r <- resolution) {><r> <}>", pcfg.currentFile(offset, 1, <line,0>,<line,1>), + cause="Please choose from the following options to disambiguate: <for (<str k, str v> <- rangeR(ind, ind[removeSpaces(link)]), {_} := ind[k]) {> + ' <k> resolves to <v><}>")), + *compileMarkdown(["<prefix> **broken:<link> (ambiguous)** <postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + ]; + } + } + + return [err(error("Unexpected state of link resolution for <link>: <resolution>", pcfg.currentFile(offset, 1, <line,0>,<line,1>)))]; +} + +@synopsis{extract what's needed from the header and print it back, also set sidebar_position} +list[Output] compileMarkdown([a:/^\-\-\-\s*$/, *str header, b:/^\-\-\-\s*$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) { + try { + model = unsetRec(loadYAML(trim(intercalate("\n", header)))); + dtls = [dtl | mapping(m) := model, scalar(str dtl) <- (m[scalar("details")]?sequence([])).\list]; + + if (dtls == []) { + dtls = createDetailsList(pcfg.currentFile, pcfg); + } + + return [ + details(dtls), + out("---"), + *[out(l) | l <- header], + *[out("sidebar_position: <sidebar_position>") | sidebar_position != -1], + out("---"), + *compileMarkdown(rest, line + 2 + size(header), offset + size(a) + size(b) + length(header), pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + ]; + } + catch value e: { + switch(e) { + case IllegalTypeArgument(str x, str y) : e = "<x>, <y>"; + case IllegalArgument(value i) : e = "<i>"; + case IO(str msg) : e = "<msg>"; + case Java(str class, str msg) : e = "<class>: <msg>"; + case Java(str class, str msg, value cause) : e = "<class>: <msg>, caused by: <cause>"; + } + + return [ + err(error("Could not process YAML header: <e>", pcfg.currentFile)), + out("---"), + *[out(l) | l <- header], + out("---"), + *compileMarkdown(rest, line + 2 + size(header), offset + size(a) + size(b) + length(header), pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + ]; + } +} + +@synopsis{Removes empty sections in the middle of a document} +list[Output] compileMarkdown([str first:/^\s*#+\s+<title:.*>$/, *str emptySection, nextSection:/^\s*#+\s+.*$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) + = compileMarkdown([nextSection, *rest], line + 1 + size(emptySection), offset + size(first) + length(emptySection), pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + when !(/\S/ <- emptySection); + +@synopsis{Divide the work over sections to avoid stackoverflows} +list[Output] compileMarkdown([str first:/^\s*#+\s+<title:.*>$/, *str body, nextSection:/^\s*#+\s+.*$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) + = [ + *compileMarkdown([first, *body], line + 1, offset + length(first) + 1, pcfg, exec, ind, dtls, sidebar_position=sidebar_position), + *compileMarkdown([nextSection, *rest], line + 1 + size(body), offset + length(first) + 1 + length(body), pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + ] when /\S/ <- body; + +@synopsis{Removes empty sections at the end of a document} +list[Output] compileMarkdown([str first:/^\s*#+\s+<title:.*>$/, *str emptySection, /^\s*$/], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) + = [] when !(/\S/ <- emptySection); + +@synopsis{this is when we have processed all the input lines} +list[Output] compileMarkdown([], int _/*line*/, int _/*offset*/, PathConfig _, CommandExecutor _, Index _, list[str] _) = []; + +@synopsis{all other lines are simply copied to the output stream} +default list[Output] compileMarkdown([str head, *str tail], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) + = [ + out(head), + *compileMarkdown(tail, line + 1, offset + size(head) + 1, pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + ]; + +list[Output] compileRascalShell(list[str] block, bool allowErrors, bool isContinued, int lineOffset, int offset, PathConfig pcfg, CommandExecutor exec, Index _) { + if (!isContinued) { + exec.reset(); + } + + lineOffsetHere = 0; + return OUT:for (str line <- block) { + if (/^\s*\/\/<comment:.*>$/ := line) { // comment line + append OUT : out("```"); + append OUT : out(trim(comment)); + append OUT : out("```rascal-shell"); + continue OUT; + } + append out("<exec.prompt()><line>"); + + output = exec.eval(line); + result = output["text/plain"]?""; + stderr = output["application/rascal+stderr"]?""; + stdout = output["application/rascal+stdout"]?""; + html = output["text/html"]?""; + + if (filterErrors(stderr) != "" && /cancelled/ !:= stderr) { + for (allowErrors, str errLine <- split("\n", stderr)) { + append OUT : out(errLine); + } + + if (!allowErrors) { + append OUT : err(error("Code execution failed", pcfg.currentFile(offset, 1, <lineOffset + lineOffsetHere, 0>, <lineOffset + lineOffsetHere, 1>), cause=stderr)); + append OUT : out("```"); + append OUT : out(":::danger"); + append OUT : out("Rascal code execution failed (unexpectedly) during compilation of this documentation."); + append OUT : out(":::"); + append OUT : out("```rascal-shell"); + for (errLine <- split("\n", stderr)) { + append OUT : out(errLine); + } + } + } + + if (stdout != "") { + for (outLine <- split("\n", stdout)[..500]) { + append OUT : out("<outLine>"); + } + } + + if (html != "") { + // unwrap an iframe if this is an iframe + xml = readXML(html); + + if ("iframe"(_, src=/\"http:\/\/localhost:<port:[0-9]+>\"/) := xml) { + html = readFile(|http://localhost:<port>/index.html|); + } + + // otherwise just inline the html + append(out("\<div class=\"rascal-html-output\"\>")); + for (htmlLine <- split("\n", html)) { + append OUT : out(" <htmlLine>"); + } + append OUT : out("\</div\>"); + } + else if (result != "") { + for (str resultLine <- split("\n", result)) { + append OUT : out(resultLine); + } + } + + lineOffsetHere +=1; + } +} + +@synopsis{Prepare blocks run the REPL but show no input or output} +list[Output] compileRascalShellPrepare(list[str] block, bool isContinued, int lineOffset, int offset, PathConfig pcfg, CommandExecutor exec, Index _) { + if (!isContinued) { + exec.reset(); + } + + lineOffsetHere = 0; + return OUT:for (str line <- block) { + output = exec.eval(line); + result = output["text/plain"]?""; + stderr = output["application/rascal+stderr"]?""; + stdout = output["application/rascal+stdout"]?""; + html = output["text/html"]?""; + + if (filterErrors(stderr) != "" && /cancelled/ !:= stderr) { + for (errLine <- split("\n", stderr)) { + append OUT : out(errLine); + } + + append out(":::danger"); + append OUT : out("Rascal code execution failed (unexpectedly) during compilation of this documentation."); + append OUT : out("\<pre\>"); + for (errLine <- split("\n", stderr)) { + append OUT : out(errLine); + } + append OUT : out("\</pre\>"); + append OUT : err(error("Code execution failed in prepare block", pcfg.currentFile(offset, 1, <lineOffset + lineOffsetHere, 0>, <lineOffset + lineOffsetHere, 1>), cause=stderr)); + } + + lineOffsetHere +=1; + } +} + +list[str] skipEmpty([/^s*$/, *str rest]) = skipEmpty(rest); +default list[str] skipEmpty(list[str] lst) = lst; + +private str filterErrors(str errorStream) = intercalate("\n", filterErrors(split("\n", errorStream))); + +private list[str] filterErrors([/^warning, ambiguity/, *str rest]) = filterErrors(rest); +private list[str] filterErrors([/^Generating parser/, *str rest]) = filterErrors(rest); +private default list[str] filterErrors([str head, *str tail]) = [head, *filterErrors(tail)]; +private list[str] filterErrors([]) = []; + +private int length(list[str] lines) = (0 | it + size(l) | str l <- lines); +private int length(str line) = size(line); + +private map[str, str] subscripts + = ( + "0" : "\u2080", + "1" : "\u2081", + "2" : "\u2082", + "3" : "\u2083", + "4" : "\u2084", + "5" : "\u2085", + "6" : "\u2086", + "7" : "\u2087", + "8" : "\u2088", + "9" : "\u2089", + "+" : "\u208A", + "-" : "\u208B", + "(" : "\u208C", + ")" : "\u208D", + "a" : "\u2090", + "e" : "\u2091", + "h" : "\u2095", + "i" : "\u1d62", + "j" : "\u2c7c", + "k" : "\u2096", + "l" : "\u2097", + "m" : "\u2098", + "n" : "\u2099", + "o" : "\u2092", + "p" : "\u209a", + "r" : "\u1d63", + "s" : "\u209b", + "t" : "\u209c", + "u" : "\u1d64", + "v" : "\u1d65", + "x" : "\u2093", + "A" : "\u2090", + "E" : "\u2091", + "H" : "\u2095", + "I" : "\u1d62", + "J" : "\u2c7c", + "K" : "\u2096", + "L" : "\u2097", + "M" : "\u2098", + "N" : "\u2099", + "O" : "\u2092", + "P" : "\u209a", + "R" : "\u1d63", + "S" : "\u209b", + "T" : "\u209c", + "U" : "\u1d64", + "V" : "\u1d65", + "X" : "\u2093" + ); + \ No newline at end of file diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Includer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Includer.rsc new file mode 100644 index 00000000000..d62904bf1c6 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Includer.rsc @@ -0,0 +1,49 @@ + +@bootstrapParser +module lang::rascal::tutor::Includer + +import lang::rascal::\syntax::Rascal; +import lang::rascal::tutor::Output; +import lang::rascal::tutor::Names; +import util::Reflective; +import String; +import Message; + + +list[Output] prepareModuleForInclusion(str moduleName, bool includeHeaders, bool includeTests, PathConfig pcfg) { + try { + moduleLoc = getModuleLocation(trim(moduleName), pcfg); + start[Module] moduleTree = parseModuleWithSpaces(moduleLoc); + + if (!includeTests) { + moduleTree = visit(moduleTree) { + case (Module) `<Header h> <Body toplevels>` => (Module) `<Header h> + ' + '<Body filteredToplevels>` + when filteredToplevels:= removeTests(toplevels) + } + } + + if (!includeHeaders) { + moduleTree = visit(moduleTree) { + // TODO: this filters tags of everything, not just the top. + case Tags _ => (Tags) `` + } + } + + return [out(l) | str l <- split("\n", "<moduleTree>")]; + } + catch str notFound: { + return [err(error(notFound, pcfg.currentFile))]; + } + catch ParseError(loc f): { + return [err(error("parse error in included module", f))]; + } +} + +Body removeTests((Body) `<Toplevel* begin> <Toplevel elem> <Toplevel* end>`) + = (Body) `<Toplevel* begin> + '<Toplevel* end>` when /(FunctionModifier) `test` := elem + ; + +default Body removeTests(Body b) = b; \ No newline at end of file diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc new file mode 100644 index 00000000000..0b65565fb02 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -0,0 +1,186 @@ +module lang::rascal::tutor::Indexer + +import util::Reflective; +import ValueIO; +import String; +import util::FileSystem; +import IO; +import Location; + +import lang::rascal::tutor::apidoc::DeclarationInfo; +import lang::rascal::tutor::apidoc::ExtractInfo; +import lang::rascal::tutor::Names; + +public alias Index = rel[str reference, str url]; + +Index readConceptIndex(PathConfig pcfg) { + return readBinaryValueFile(#Index, pcfg.bin + "index.value"); +} + +Index createConceptIndex(PathConfig pcfg) { + ind = createConceptIndex(pcfg.srcs); + + // store index for later usage by depending documentation projects + writeBinaryValueFile(pcfg.bin + "index.value", ind); + + // read indices from projects we depend on, if present + ind += {*readBinaryValueFile(#rel[str,str], inx) | l <- pcfg.libs, inx := l + "doc" + "index.value", exists(inx)}; + + return ind; +} + +rel[str, str] createConceptIndex(list[loc] srcs) + = {*createConceptIndex(src) | src <- srcs}; + +@synopsis{creates a lookup table for concepts nested in a folder} +rel[str, str] createConceptIndex(loc src) + = // first we collect index entries for concept names, each file is one concept which + // can be linked to in many different ways ranging from very short (handy but inexact) to very long (guaranteed to be exact.) + + // First we handle the root concept + { + <capitalize(src.file), "/<capitalize(src.file)>/index.md"> + } + + + // Then we handle the cases where the concept name is the same as the folder it is nested in: + { + // `((StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <cf.file , fr>, + + // `((Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <"<f.parent.parent.file>-<cf.file>", fr>, + + // `((Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <replaceAll(capitalize(relativize(src, f.parent).path)[1..], "/", "-"), fr>, + + // `((Rascal:StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <"<capitalize(src.file)>:<capitalize(cf.file)>", fr>, + + // `((Rascal:Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <"<capitalize(src.file)>:<f.parent.parent.file>-<cf.file>", fr>, + + // `((Rascal:Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <"<capitalize(src.file)>:<replaceAll(capitalize(relativize(src, f.parent).path)[1..], "/", "-")>", fr> + + | loc f <- find(src, isConceptFile) + , f.parent? + , f.parent.path != "/" + , f.parent != src + , f.parent.file == f[extension=""].file + , fr := "/<capitalize(src.file)>/<fragment(src, f)>" + , cf := f[extension=""] + } + + + // Then we handle the extra markdown files, that don't keep to the Concept/Concept.md rule + { + // `((StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <cf.file , fr>, + + // `((Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <"<f.parent.file>-<cf.file>", fr>, + + // `((Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <replaceAll(capitalize(relativize(src, cf).path)[1..], "/", "-"), fr>, + + // `((Rascal:StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <"<capitalize(src.file)>:<capitalize(cf.file)>", fr>, + + // `((Rascal:Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <"<capitalize(src.file)>:<f.parent.file>-<cf.file>", fr>, + + // `((Rascal:Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <"<capitalize(src.file)>:<replaceAll(capitalize(relativize(src, cf).path)[1..], "/", "-")>", fr> + + | loc f <- find(src, isConceptFile) + , f.parent? + , f.parent.path != "/" + , f.parent != src + , f.parent.file != f[extension=""].file + , fr := "/<capitalize(src.file)>/<fragment(src, f)>" + , cf := f[extension=""] + } + + + // Then we handle all folders. We assume all folders have an index.md (generated or manually provided) + // This may generate some links exactly the same as above, and add some new ones. + + + { + // `((StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <f.file , fr>, + + // `((Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <"<f.parent.file>-<f.file>", fr>, + + // `((Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <replaceAll(capitalize(relativize(src, f).path)[1..], "/", "-"), fr>, + + // `((Rascal:StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <"<capitalize(src.file)>:<capitalize(f.file)>", fr>, + + // `((Rascal:Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <"<capitalize(src.file)>:<f.parent.file>-<f.file>", fr>, + + // `((Rascal:Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` + <"<capitalize(src.file)>:<replaceAll(capitalize(relativize(src, f).path)[1..], "/", "-")>", fr> + | loc f <- find(src, isDirectory) + , fr := "/<capitalize(src.file)>/<fragment(src, f)>" + , f != src + } + + + + + // Now follow the index entries for image files: + { <"<f.parent.file>-<f.file>", "/assets/<capitalize(src.file)><relativize(src, f).path>">, + <f.file, "/assets/<capitalize(src.file)><relativize(src, f).path>">, + <"<capitalize(src.file)>:<f.file>", "/assets/<capitalize(src.file)><relativize(src, f).path>"> + | loc f <- find(src, isImageFile) + } + + { // these are links to packages/folders/directories via module path prefixes, like `analysis::m3` + <"<replaceAll(relativize(src, f).path[1..], "/", "::")>", fr>, + <"<capitalize(src.file)>:<replaceAll(relativize(src, f).path[1..], "/", "::")>", fr>, + <"<capitalize(src.file)>:<replaceAll(relativize(src, f).path[1..], "/", "-")>", fr>, + <"<capitalize(src.file)>:<capitalize(replaceAll(relativize(src, f).path[1..], "/", "-"))>", fr>, + <"<capitalize(src.file)>:package:<replaceAll(relativize(src, f).path[1..], "/", "::")>", fr>, + <"<capitalize(src.file)>:<capitalize(replaceAll(relativize(src, f).path[1..], "/", "::"))>", fr> + | loc f <- find(src, isDirectory) + , /\/internal\// !:= f.path + , f != src + , fr := "/<capitalize(src.file)>/<fragment(src, f)>" + } + + // Finally, the index entries for Rascal modules and declarations, as extracted from the source code: + { // `((getDefaultPathConfig))` -> `Libary/util/Reflective#getDefaultPathConfig` + *{<"<item.kind>:<item.name>","/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>">, + <item.name, "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>" > | item.name?}, + + // `((Library:getDefaultPathConfig))` -> `/Library/util/Reflective#getDefaultPathConfig` + *{<"<capitalize(src.file)>:<item.name>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>" >, + <"<capitalize(src.file)>:<item.kind>:<item.name>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>" > | item.name?}, + + // `((util::Reflective::getDefaultPathConfig))` -> `/Library/util/Reflective#getDefaultPathConfig` + *{<"<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>" >, + <"<item.kind>:<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>" > | item.name?, sep <- {"::", "/", "-"}}, + + // ((Library:util::Reflective::getDefaultPathConfig))` -> `/Library/util/Reflective#getDefaultPathConfig` + *{<"<capitalize(src.file)>:<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>" >, + <"<capitalize(src.file)>:<item.kind>:<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>" > | item.name?, sep <- {"::", "/", "-"}}, + + // ((Set)) -> `/Library/Set` + *{<item.moduleName, "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md" >, <"module:<item.moduleName>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md" > | item is moduleInfo}, + + // `((Library:Set))` -> `/Library/Set` + *{<"<capitalize(src.file)>:<item.moduleName>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md" >, + <"<capitalize(src.file)>:module:<item.moduleName>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md" > | item is moduleInfo} + + | loc f <- find(src, "rsc"), list[DeclarationInfo] inf := safeExtract(f), item <- inf + } + ; + +private bool isConceptFile(loc f) = f.extension in {"md"}; +private bool isImageFile(loc f) = f.extension in {"png", "jpg", "svg", "jpeg"}; + +@synopsis{ignores extracting errors because they will be found later} +private list[DeclarationInfo] safeExtract(loc f) { + try { + return extractInfo(f); + } + catch Java(_,_): return []; + catch ParseError(_): return []; +} \ No newline at end of file diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc new file mode 100644 index 00000000000..e7b29b957d5 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc @@ -0,0 +1,48 @@ +module lang::rascal::tutor::Names + +import String; +import Location; +import List; +import IO; + +data PathConfig(loc currentRoot = |unknown:///|, loc currentFile = |unknown:///|); +data Message(str cause=""); + + +default str fragment(loc root, loc concept) = capitalize(relativize(root, concept).path)[1..]; + +str fragment(loc root, loc concept) = fragment(root, concept + "index.md") + when isDirectory(concept) || root == concept; + +str fragment(loc root, loc concept) = fragment(root, concept.parent + "index.md") + when concept.parent?, concept.parent.file == concept[extension=""].file; + +str moduleFragment(str moduleName) = "<replaceAll(moduleName, "::", "/")>"; + +str removeSpaces(/^<prefix:.*><spaces:\s+><postfix:.*>$/) + = removeSpaces("<prefix><capitalize(postfix)>"); + +default str removeSpaces(str s) = s; + +// remove Course:module: prefixes +str addSpaces(/^<prefix:[^:]+>:<postfix:[^:].*>$/) + = addSpaces(postfix); + +// select final function name if present +str addSpaces(/^<prefix:.+>::<name:[^:]+>$/) + = name; // no recursion to avoid splitting function names + +// split and uncapitalize CamelCase +str addSpaces(/^<prefix:[A-Za-z0-9\ ]+[a-z0-9]><postfix:[A-Z].+>/) = + addSpaces("<uncapitalize(prefix)> <uncapitalize(postfix)>"); + +default str addSpaces(str s) = split("-", s)[-1]; + +@synopsis{produces `"../../.."` for pathToRoot(|aap:///a/b|, |aap:///a/b/c/d|) } +str pathToRoot(loc root, loc src) + = "..<for (e <- split("/", relativize(root, src).path), e != "") {>/..<}>" + when isDirectory(src); + +str pathToRoot(loc root, loc src) + = pathToRoot(root, src.parent) + when isFile(src); diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc new file mode 100644 index 00000000000..1d2b9cb5730 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc @@ -0,0 +1,13 @@ +module lang::rascal::tutor::Output + +extend Message; + +data Output + = out(str content) + | err(Message message) + | details(list[str] order) + | search(list[str] contents, str fragment) + | \docTag(str tagName, list[Output] output) + ; + +Output empty() = out(""); diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc new file mode 100644 index 00000000000..c9f0ca6a416 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc @@ -0,0 +1,22 @@ +module lang::rascal::tutor::apidoc::DeclarationInfo + +@doc{Representation of documentation-related information extracted from a module.} +data DeclarationInfo( + str moduleName="", + str name=moduleName, + loc src = |unknown:///|, + str synopsis="", + str signature="", + list[DocTag] docs = [], + loc docSrc = src) + = moduleInfo (str kind="module") + | functionInfo (str kind="function") + | constructorInfo (str kind="constructor") + | dataInfo (str kind="data", list[str] overloads=[]) + | aliasInfo (str kind="alias") + | varInfo (str kind="variable") + ; + +data DocTag(str label="", loc src=|unknown:///|, str content="") + = docTag(); + diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc new file mode 100644 index 00000000000..72b2ee213df --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc @@ -0,0 +1,178 @@ +@bootstrapParser +module lang::rascal::tutor::apidoc::ExtractInfo + +import IO; +import String; +import lang::rascal::\syntax::Rascal; +import ParseTree; +import util::Reflective; + +import lang::rascal::tutor::apidoc::DeclarationInfo; + +@synopsis{Extract declaration information from a Rascal module at given location.} +list[DeclarationInfo] extractInfo(loc moduleLoc) + = doExtractInfo(moduleLoc, lastModified(moduleLoc)); + +@memo +private list[DeclarationInfo] doExtractInfo(loc moduleLoc, datetime _/*lastModified*/){ + M = parseModuleWithSpaces(moduleLoc).top; + return extractModule(M); +} + +list[DeclarationInfo] extractModule(m: (Module) `<Header header> <Body body>`) { + moduleName = "<header.name>"; + tags = getTagContents(header.tags); + + tls = [ *extractTopLevel(moduleName, tl) | tl <- body.toplevels ]; + + synopsis = getSynopsis(tags); + + return moduleInfo(moduleName=moduleName, src=m@\loc, synopsis=synopsis, docs=sortedDocTags(tags)) + tls; +} + +/********************************************************************/ +/* Process declarations in a module */ +/********************************************************************/ + +list[DeclarationInfo] extractTopLevel(str moduleName, (Toplevel) `<Declaration decl>`) = extractDecl(moduleName, decl); + +// -- variable declaration ------------------------------------------ + +list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<Tags tags> <Visibility visibility> <Type tp> <{Variable ","}+ variables> ;`) + = []; + +// -- miscellaneous declarations ------------------------------------ + +list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<Tags tags> <Visibility visibility> anno <Type annoType> <Type onType>@<Name name> ;`) + = []; + +list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<Tags tags> <Visibility visibility> alias <UserType user> = <Type base> ;`) { + dtags = getTagContents(tags); + return [ aliasInfo(moduleName=moduleName, name="<user>", signature="<base>", src=d@\loc, synopsis=getSynopsis(dtags), docs=sortedDocTags(dtags))]; +} + +list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<Tags tags> <Visibility visibility> tag <Kind kind> <Name name> on <{Type ","}+ types> ;`) + = [ ]; + +list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<Tags tags> <Visibility visibility> data <UserType user> ;`) + = [ ]; + +str align({Variant "|"}+ variants){ + res = ""; + sep = "\n = "; + for(v <- variants){ + res += sep + trim("<v>"); + sep = "\n | "; + } + return res + "\n ;"; +} + +list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<Tags tags> <Visibility visibility> data <UserType user> <CommonKeywordParameters commonKeywordParameters> ;`) { + dtags = getTagContents(tags); + adtName = "<user.name>"; + + return [dataInfo(moduleName=moduleName, name=adtName, signature="data <user> <commonKeywordParameters>", + src=d@\loc, synopsis=getSynopsis(dtags), docs=sortedDocTags(dtags))]; +} + +list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<Tags tags> <Visibility visibility> data <UserType user> <CommonKeywordParameters commonKeywordParameters> = <{Variant "|"}+ variants> ;`) { + dtags = getTagContents(tags); + adtName = "<user.name>"; + infoVariants = [ genVariant(moduleName, variant) | variant <- variants ]; + + return dataInfo(moduleName=moduleName, name=adtName, signature="data <user> <commonKeywordParameters> <align(variants)>", + src=d@\loc, synopsis=getSynopsis(dtags), docs=sortedDocTags(dtags)) + infoVariants; +} + +DeclarationInfo genVariant(str moduleName, v: (Variant) `<Name name>(<{TypeArg ","}* _> <KeywordFormals _>)`) { + signature = "<v>"; + return constructorInfo(moduleName=moduleName, name="<name>", signature="<v>", src=v@\loc); +} + +list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<FunctionDeclaration functionDeclaration>`) + = [ extractFunDecl(moduleName, functionDeclaration) ]; + +// -- function declaration ------------------------------------------ + +DeclarationInfo extractFunDecl(str moduleName, fd: (FunctionDeclaration) `<Tags tags> <Visibility visibility> <Signature signature> ;`) + = extractFunctionDeclaration(moduleName, fd); + +DeclarationInfo extractFunDecl(str moduleName, fd: (FunctionDeclaration) `<Tags tags> <Visibility visibility> <Signature signature> = <Expression expression> ;`) + = extractFunctionDeclaration(moduleName, fd); + +DeclarationInfo extractFunDecl(str moduleName, fd: (FunctionDeclaration) `<Tags tags> <Visibility visibility> <Signature signature> = <Expression expression> when <{Expression ","}+ conditions>;`) + = extractFunctionDeclaration(moduleName, fd); + + +DeclarationInfo extractFunDecl(str moduleName, fd: (FunctionDeclaration) `<Tags tags> <Visibility visibility> <Signature signature> <FunctionBody body>`) + = extractFunctionDeclaration(moduleName, fd); + +private DeclarationInfo extractFunctionDeclaration(str moduleName, FunctionDeclaration fd) { + fname = "<fd.signature.name>"; + + signature = "<fd.signature>"; + if(startsWith(signature, "java")){ + signature = signature[size("java")+1 .. ]; + } + + tags = getTagContents(fd.tags); + + return functionInfo(moduleName=moduleName, name=fname, signature=signature, src=fd@\loc, synopsis=getSynopsis(tags), docs=sortedDocTags(tags)); +} + + +str getSynopsis(rel[str, DocTag] tags) { + if (docTag(content=str docContents) <- tags["doc"]) { + if ([*_, /^.Synopsis\s+<rest:.*>$/, *str cont, /^.[A-Za-z].*$/, *str _] := split("\n", docContents)) { + return intercalate(" ", [rest, *cont]); + } + else if ([*_, /^#+\s*Synopsis\s+<rest:.*>$/, *str cont, /^.[A-Za-z].*$/, *str _] := split("\n", docContents)) { + return intercalate(" ", [rest, *cont]); + } + } + + if (docTag(content=str docContents) <- tags["synopsis"]) { + return trim(intercalate(" ", split("\n", docContents))); + } + else { + return ""; + } +} + + +bool isTutorTag(str label) = label in {"doc", "synopsis", "syntax", "types", "details", "description", "examples", "benefits", "pitfalls"}; + +@synopsis{extracts the contents of _all_ tags from a declaration syntax tree and stores origin information} +rel[str, DocTag] getTagContents(Tags tags){ + m = {}; + for (tg <- tags.tags){ + str name = "<tg.name>"; + if (!isTutorTag(name)) { + continue; + } + + if (tg is \default) { + cont = "<tg.contents>"[1 .. -1]; + m += <name, docTag(label=name, content=cont, src=tg.src)>; + } else if (tg is empty) { + m += <name, docTag(label=name, content="", src=tg.src)>; + } else { + m += <name, docTag(label=name, content="<tg.expression>"[1 .. -1], src=tg.src)>; + } + } + + return m; +} + +@synopsis{lists the supported documentation tags in the prescribed order} +list[DocTag] sortedDocTags(rel[str, DocTag] tags) + = [ *tags["doc"], + *tags["synopsis"], + *tags["syntax"], + *tags["types"], + *tags["details"], + *tags["description"], + *tags["examples"], + *tags["benefits"], + *tags["pitfalls"] + ]; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc new file mode 100644 index 00000000000..a5c0c7edd97 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -0,0 +1,149 @@ +module lang::rascal::tutor::apidoc::GenerateMarkdown + +import List; +import String; +import util::Reflective; + +import lang::rascal::tutor::apidoc::DeclarationInfo; +import lang::rascal::tutor::apidoc::ExtractInfo; +import lang::rascal::tutor::Output; +import lang::rascal::tutor::Indexer; +import lang::rascal::tutor::Compiler; +import lang::rascal::tutor::repl::TutorCommandExecutor; +import lang::rascal::tutor::Names; + +@synopsis{Generate markdown documentation from the declarations extracted from a Rascal module.} +@description{ + This function takes Rascal files as input, first extracts all declarations including their + embedded (markdown) documentation tags, and then generates on-the-fly the output markdown + as a list of lines and error messages. + + This generator reuses the markdown compiler + to implement Rascal shell execution and concept linking, etc. This compilation is applied inside of the + documentation tags that are written by the author of the Rascal code. The trick is to track the + current line number inside those documentation tags to provide valuable feedback to the user + of the tutor compiler. +} +list[Output] generateAPIMarkdown(str parent, loc moduleLoc, PathConfig pcfg, CommandExecutor exec, Index ind) { + try { + dinfo = extractInfo(moduleLoc); + + dtls = sort(dup(["<capitalize(pcfg.currentRoot.file)>:<i.kind>:<i.moduleName>::<i.name>" | DeclarationInfo i <- dinfo, !(i is moduleInfo)])); + + // TODO: this overloading collection should happen in ExtractInfo + res = []; + int i = 0; + while (i < size(dinfo)) { + j = i + 1; + list[str] overloads = []; + + if (dinfo[i] has name) { + overloads = [dinfo[i].signature]; + + // TODO: this only collects consecutive overloads. if a utility function interupts the flow, + // then we do not get to see the other overloads with the current group. Rewrite to use a "group-by" query. + // Also this looses any additional documentation tags for anything but the first overloaded declaration + + while (j < size(dinfo) && dinfo[i].name == dinfo[j].name) { + // this loops eats the other declarations with the same name (if consecutive!) + overloads += dinfo[j].signature; + j += 1; + } + } + + res += declInfo2Doc(parent, dinfo[i], overloads, pcfg, exec, ind, dinfo[i] is moduleInfo? dtls : []); + i = j; + } + + return res; + } + catch Java(_,_): + return [err(error("parse error in source file", moduleLoc))]; + catch ParseError(loc l): + return [err(error("parse error in source file", l))]; +} + +private map[str,str] escapes = ("\\": "\\\\", "\"": "\\\""); + +list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = + [ + out("---"), + out("title: \"module <escape(d.moduleName, escapes)>\""), + out("---"), + Output::empty(), + out("#### Usage"), + Output::empty(), + out("`import <replaceAll(d.name, "/", "::")>;`"), + Output::empty(), + *tags2Markdown(d.docs, pcfg, exec, ind, dtls), + out("") + ]; + +list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = + [ + out("## function <d.name> {<fragment(d.moduleName)>-<d.name>}"), + Output::empty(), + *[out("* ``<removeNewlines(ov)>``") | ov <- overloads], + Output::empty(), + *tags2Markdown(d.docs, pcfg, exec, ind, dtls) + ]; + + + list[Output] declInfo2Doc(str parent, constructorInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = + []; + + list[Output] declInfo2Doc(str parent, d:dataInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = + [ + out("## data <d.name> {<fragment(d.moduleName)>-<d.name>}"), + empty(), + *[ + out("```rascal"), + *[ + out(defLine) + | str defLine <- split("\n", ov) + ], + out("```"), + empty() + | ov <- overloads + ], + *tags2Markdown(d.docs, pcfg, exec, ind, dtls) + ]; + +list[Output] declInfo2Doc(str parent, d:aliasInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = + [ + out("## alias <d.name> {<fragment(d.moduleName)>-<d.name>}"), + empty(), + *[out("* `<removeNewlines(ov)>`") | ov <- overloads], + empty(), + *tags2Markdown(d.docs, pcfg, exec, ind, dtls) + ]; + +default list[Output] declInfo2Doc(str parent, DeclarationInfo d, list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) + = [err(info("No content generated for <d>", d.src))]; + +list[Output] tags2Markdown(list[DocTag] tags, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) + = [ + // every doc tag has its own header title, except the "doc" tag which may contain them all (backward compatibility) + *(l != "doc" ? [out("#### <capitalize(l)>"), empty()] : []), + + // here is where we get the origin information into the right place for error reporting: + *compileMarkdown(split("\n", c), s.begin.line, s.offset, pcfg, exec, ind, dtls), + + empty() + + // this assumes that the doc tags have been ordered correctly already by the extraction stage + | docTag(label=str l, src=s, content=str c) <- tags + ]; + +public str basename(str cn){ + return (/^.*::<base:[A-Za-z0-9\-\_]+>$/ := cn) ? base : cn; +} + +private str fragment(str moduleName) = "#<replaceAll(moduleName, "::", "-")>"; + +str removeNewlines(str x) = visit(x) { + case /\n/ => " " +}; + + + diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/ADtoMD.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/ADtoMD.rsc new file mode 100644 index 00000000000..9eaf539fc7d --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/ADtoMD.rsc @@ -0,0 +1,118 @@ +@synopsis{Temporary utility conversions for evolving the tutor syntax from AsciiDoc to Docusaurus Markdown} +module lang::rascal::tutor::conversions::ADtoMD + +import util::FileSystem; +import IO; +import String; + +void ad2md() { + ad2md(); + ad2md(); +} + +void ad2md(loc root) { + for (f <- find(root, isSourceFile)) + convertFile(f); +} + +bool isSourceFile(loc f) = f.extension in {"md", "rsc"}; + +void convertFile(loc file) { + println("converting: <file>"); + result=for (l <- readFileLines(file)) { + append convertLine(l); + } + + writeFileLines(file, result); +} + +// [map functions]((Library:Map)) +str convertLine(/<prefix:.*>link:\/<course:[A-Za-z0-9]+>#<concept:[A-Za-z0-9\-]+>\[<title:[^\]]*>\]<postfix:.*$>/) + = convertLine("<prefix>[<title>]((<trim(course)>:<trim(concept)>))<postfix>"); + +// [Why Rascal]((WhyRascal)) +str convertLine(/<prefix:.*>link:\/<course:[A-Za-z0-9]+>\[<title:[^\]]*>\]<postfix:.*$>/) + = convertLine("<prefix>[<title>]((<trim(course)>))<postfix>"); + +// ((Hello)) +str convertLine(/<prefix:.*>\<\<<concept:[A-Za-z0-9\-]+>\>\><postfix:.*$>/) + = convertLine("<prefix>((<trim(concept)>))<postfix>"); + +// [[Extraction-Workflow]] +// ![Extraction Workflow]((define-extraction.png)) +// statement-parts.png[width="500px" style="float: right;" ,alt="Statement Types"] +str convertLine(/^<prefix:.*>image:[:]*<filename:[A-Za-z\-0-9]+>\.<ext:png|jpeg|jpg|svg>\[<properties:[^\]]*>\]<postfix:.*$>/) + = convertLine("<prefix>![<extractTitle(properties)>]((<filename>.<ext>))<postfix>"); + +// ((String-GreaterThan)) +str convertLine(/^<prefix:.*>\<\<<concept:[A-Za-z\-0-9\ ]+>,<title:[A-Za-z\-0-9\ ]+>\>\><postfix:.*$>/) + = convertLine("<prefix>((<concept>))<postfix>"); + +// ((Pattern Matching)) +str convertLine(/^<prefix:.*>\<\<<concept:[A-Za-z\-0-9\ ]+>\>\><postfix:.*$>/) + = convertLine("<prefix>((<concept>))<postfix>"); + +str convertLine(/^<prefix:.*>loctoc::\[[0-9]+\]<postfix:.*$>/) + = convertLine("<prefix>(((TOC)))<postfix>"); + +str convertLine(/^<prefix:.*>kbd:\[<keys:.*?>\]<postfix:.*$>/) + = convertLine("<prefix>`<keys>`<postfix>"); + +str convertLine(/^\ \ \ \ \*\*<postfix:.*$>/) + = convertLine(" *<postfix>"); + +// ((Library:Libraries +str convertLine(/^<prefix:.*>\(\(Libraries:<postfix:.*$>/) + = convertLine("<prefix>((Library:<postfix>"); + +str convertLine(/^<prefix:.*>\(\(Library:Libraries-<postfix:.*$>/) + = convertLine("<prefix>((Library:<postfix>"); + +// Library:Prelude- +str convertLine(/^<prefix:.*>\(\(Library:Prelude-<postfix:.*$>/) + = convertLine("<prefix>((Library:<postfix>"); + +str convertLine(/^<prefix:.*>\(\(<pre:[^\)]+>-Prelude-<lst:[^\)\-]+>\)\)<postfix:.*>$/) + = convertLine("<prefix>((<pre>-<lst>))<postfix>"); + +str convertLine(/^<prefix:.*>\(\(<pre:[^\)]+>-<fst:[^\)\-]+>-<lst:[^\)\-]+>\)\)<postfix:.*>$/) + = convertLine("<prefix>((<pre>-<fst>))<postfix>") when lst == fst, fst != "Prelude"; + +// Rascal:Concepts- +str convertLine(/^<prefix:.*>\(\(Rascal:Concepts-<rest:[^)]+>\)\)<postfix:.*>$/) + = convertLine("<prefix>((RascalConcepts:<rest>))<postfix>"); + +// italics within backquotes is not supported anymore. removing underscores for readability's sake +str convertLine(/^<prefix:.*>`<prequote:[^`]*>_<italics:[A-Za-z0-9~]+>_<postquote:[^`]*>`<postfix:.*$>/) + = convertLine("<prefix>`<prequote><italics><postquote>`<postfix>"); + +// http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_sec_4.1.3[procedure call] +str convertLine(/^<prefix:.*>http\:\/\/<url:[^\[\(\)]+>\[<label:[^\]\(\)]+>\]<postfix:.*$>/) + = convertLine("<prefix>[<label>](http://<url>)<postfix>"); + +str convertLine(/^keywords: \"<stuff:.*>\"\s*$/) + = "keywords: + '<for (k <- split(",", stuff)) {> - <k> + '<}>"; + +str convertLine(/^details: <stuff:.*>\s*$/) + = "details: + '<for (k <- split(",", stuff)) {> - <k> + '<}>"; + +str convertLine(/^title: \"<stuff:.*>\"\s*$/) + = "title: <stuff>"; + +str convertLine(/^\ \ \-\ \"\"<thing:[^A-Za-z0-9\-\_\ \t\"]+>\"\"\s*$/) + = " - \"<thing>\""; + +str convertLine(/^\ \ \-\ true\s*$/) + = " - \"true\""; + +str convertLine(/^\ \ \-\ false\s*$/) + = " - \"false\""; + +default str convertLine(str line) = line; + +str extractTitle(/title=\"<t:[^\"]+>\"/) = t; +default str extractTitle(str x) = ""; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/ConvertSections.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/ConvertSections.rsc new file mode 100644 index 00000000000..d90db59263b --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/ConvertSections.rsc @@ -0,0 +1,131 @@ +module lang::rascal::tutor::conversions::ConvertSections + +import IO; +import String; +import List; +import util::FileSystem; + +void convertAllSections(loc dir) { + set[loc] files = find(dir, isConceptFile); + + for (loc f <- files) { + writeFile(f, "<for (l <- convertSections(f)) {><l> + '<}>"); + } +} + +bool isConceptFile(loc f) = (f.extension) in {"md", "concept", "rsc"}; + +bool isImageFile(loc f) = f.extension in {"png", "jpg", "svg", "jpeg"}; + +list[str] convertSections(loc file) { + return convertSections(readFileLines(file)); +} + +list[str] convertSections([str first:/^\s*\[source,rascal<rest1:.*>]\s*$/, /---/, *str block, /----*<postfix:[^\-]*>/, *str rest2]) + = [ + "```rascal<removeQuotesThing(rest1)>", + *block, + "```<postfix>", + *convertSections(rest2) + ]; + +/* +``` +image::_File_[] +image::_File_[_AlternateName_, _Width_, _Height_, link=_URI_] +``` +*/ +list[str] convertSections([str first:/^\s*\[source[^\]]*\]\s*$/, /---/, *str block, /----*<postfix:[^\-]*>/, *str rest2]) + = [ + "```", + *block, + "```<postfix>", + *convertSections(rest2) + ]; + +list[str] convertSections([str first:/^#\s+<title:.*>$/, *str rest2, /^\s*\.Index/, *str indexLines, str nextHeader:/^\s*\.[A-Z][a-z]*/, *str rest3]) + = [ + "---", + "title: \"<title>\"", + "keywords: \"<intercalate(",", words(indexLines))>\"", + "---", + *convertSections(rest2), + nextHeader, + *rest3 + ]; + +list[str] convertSections([str first:/^#\s+<title:.*>$/, *str rest2, str nextHeader:/^\s*\.[A-Z][a-z]*/, *str rest3]) + = [ + "---", + "title: <title>", + "---", + *convertSections(rest2), + nextHeader, + *rest3 + ]; + +list[str] words(list[str] input) = [ *words(line) | line <- input]; +list[str] words(str input) = [w | /<w:\S+>/ := input]; + +list[str] convertSections(["---", *str headers, "---", *str otherStuff, /^\s*\.Details/, *str detailsLines, str nextHeader:/^\s*\.[A-Z][a-z]*/, *str moreStuff]) + = [ + "---", + *headers, + *(words(detailsLines) != [] ? ["details: <intercalate(",", words(detailsLines))>"] :[]), + "---", + *otherStuff, + nextHeader, + *moreStuff + ]; + +list[str] convertSections([/^\.<headerTitle:[A-Z][A-Za-z]+>\s*$/, *str otherStuff]) + = [ + "#### <headerTitle>", + *(([str firstLine, *_] := otherStuff && trim(firstLine) != "") ? [""] : []), + *convertSections(otherStuff) + ]; + +/* + +| | | +| --- | --- | +| *What* | The pocket calculator language Calc; we already covered it ((A simple pocket calculator language)) | +| *Illustrates* | fact, define, use, requireEqual, calculate, getType, report | +| *Source* | https://github.com/cwi-swat/typepal/tree/master/src/examples/calc | + +*/ +list[str] convertSections([ + str before, + /^\s*\|====*\s*$/, + str firstLine, + *str body, + /^\s*\|====*\s*$/, + *str rest + ]) + = [ /^\s*\[[^\]*]*\]\s*$/ := before ? "" : before, + // emptyHeader(firstLine), + completeBodyLine(firstLine), + columnsLine(emptyHeader(firstLine)), + *[completeBodyLine(b) | b <- body, trim(b) != ""], + "", + *convertSections(rest) + ] when [*_, /\|====*/, *_] !:= body; + +str emptyHeader(str firstLine) = visit(completeBodyLine(firstLine)) { case /[^\|]/ => " "}; + +str completeBodyLine(str body) = /\|\s*$/ := body ? body : "<body> |"; + +str columnsLine(/\|\s*\|<postfix:.*>$/) = "| --- <columnsLine("|<postfix>")>"; +str columnsLine("|") = "|"; + +list[str] convertSections([]) + = []; + +default list[str] convertSections([str first, *str last]) + = [first, *convertSections(last)]; + +str removeQuotesThing(/^<prefix:.*>,subs=\"quotes\"<postfix:.*>/) + = "<prefix><postfix>"; + +default str removeQuotesThing(str x) = x; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/Includes.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/Includes.rsc new file mode 100644 index 00000000000..1c0285c700d --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/Includes.rsc @@ -0,0 +1,68 @@ +module lang::rascal::tutor::conversions::Includes + +import IO; +import List; +import util::FileSystem; +import String; + +list[loc] roots = [|project://rascal/src/org/rascalmpl/library|, |project://rascal/src/org/rascalmpl/courses|]; + +list[loc] findModuleIncludes() { + for (r <- roots, f <- find(r, "md"), l:/include::\{LibDir\}<path:[^\]]+>\[tags=module\]/ <- readFileLines(f)) { + println("found: <path>"); + + theInc = |project://rascal/src/org/rascalmpl/library| + path; + + + if (!exists(theInc)) { + println("WARNING: In <f> this include does not exist: <theInc>"); + continue; + } + + theIncLines = readFileLines(theInc); + + if ([*str prelude, + /^\s*\/\/\s+tag::module\[\]\s*$/, + *str moduleContent, + /^\s*\/\/\s+end::module\[\]\s*$/, + *str rest + ] := theIncLines) { + println("<f> - <theInc>"); + } + else { + println("WARNING: <theInc> did not match any tags"); + } + } + + return []; +} + +void fixModuleIncludes() { + for (r <- roots, f <- find(r, "md")) { + fixModuleIncludes(f); + } +} + +void fixModuleIncludes(loc f) { + lines = readFileLines(f); + + writeFile(f, intercalate("\n", fixIncludes(readFileLines(f) + [""]))); +} + +// l:/include::\{LibDir\}<path:[^\]]+>\[tags=module\]/ <- readFileLines(f)) { + +list[str] fixIncludes([/include::\{LibDir\}<path:[^\]]+>\[\]/, *str tail]) + = [ *["((<lo>))" | lo := |project://rascal/src/org/rascalmpl/library/| + path], + *fixIncludes(tail) + ]; + +list[str] fixIncludes(["```rascal", l:/include::\{LibDir\}<path:[^\]]+>\.rsc\[tags=module\]/, "```", *str tail]) + = ["```rascal-include", + replaceAll(path, "/", "::"), + "```", + *fixIncludes(tail) + ]; + +default list[str] fixIncludes([str head, *str tail]) = [head, *fixIncludes(tail)]; +list[str] fixIncludes([]) = []; + diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/CallAnalysis.md b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/CallAnalysis.md new file mode 100644 index 00000000000..5ff9bbdc791 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/CallAnalysis.md @@ -0,0 +1,119 @@ +# Synopsis +Analyzing the call structure of an application. + +# Description + +Suppose a mystery box ends up on your desk. When you open it, it contains a huge software system with several questions attached to it: + +* How many procedure calls occur in this system? +* How many procedures does it contains? +* What are the entry points for this system, i.e., procedures that call others but are not called themselves? +* What are the leaves of this application, i.e., procedures that are called but do not make any calls themselves? +* Which procedures call each other indirectly? +* Which procedures are called directly or indirectly from each entry point? +* Which procedures are called from all entry points? + +Let's see how these questions can be answered using Rascal. + +Examples: +Consider the following call graph (a box represents a procedure and an arrow represents a call from one procedure to another procedure): + +![calls]((calls.png)) + + + +Rascal supports basic data types like integers and strings which are sufficient to formulate and answer the questions at hand. However, we +can gain readability by introducing separately named types for the items we are describing. +First, we introduce therefore a new type `Proc` (an alias for strings) to denote procedures: +```rascal-shell +alias Proc = str; +``` +Next, we have to represent the call relation as a Rascal datatype, and the relation is the most appropriate for it. +As preparation, we also import the libraries [$Rascal:Prelude/Set], [$Rascal:Prelude/Relation] and [$Rascal:Prelude/Graph] that will come in handy. +```rascal-shell-continue +import Set; +import Relation; +import analysis::graphs::Graph; +rel[Proc, Proc] Calls = {<"a", "b">, <"b", "c">, <"b", "d">, <"d", "c">, <"d", "e">, <"f", "e">, <"f", "g">, <"g", "e">}; +``` +Now we are in a good position to start asking some questions. + +__How many calls occur in this system?__ +We use the function [Rascal:Set/size] to determine the number of elements in a set or relation. +Since each tuple in the `Calls` relation represents a call between procedures, the number of tuples is equal +to the number of calls. +```rascal-shell-continue +size(Calls); +``` +__How many procedures occur in this system?__ This question is more subtle, since a procedure may call (or be called) by +several others and the number of tuples is therefore not indicative. What we need are the set of procedures that +occur (as first or second element) in _any_ tuple. This is precisely what the function [$Rascal:carrier] gives us: +```rascal-shell-continue +carrier(Calls) +``` +and computing the number of procedures is now easy: +```rascal-shell-continue +size(carrier(Calls)); +``` +As an aside, functions [$Rascal:Prelude/Relation/domain] and [$Rascal:Prelude/Relation/range] do the same for the first, respectively, second element of the pairs in a relation: +```rascal-shell-continue +domain(Calls); +range(Calls); +``` +__What are the entry points for this system?__ +The next step in the analysis is to determine which entry points this application has, i.e., procedures which call others but are +not called themselves. Entry points are useful since they define the external interface of a system and may also be used as guidance to +split a system in parts. The top of a relation contains those left-hand sides of tuples in a relation that do not occur in any +right-hand side. When a relation is viewed as a graph, its top corresponds to the root nodes of that graph. Similarly, the bottom of a +relation corresponds to the leaf nodes of the graph. See the section called ?Graph? for more details. Using this knowledge, the entry +points can be computed by determining the top of the Calls relation: +```rascal-shell-continue +top(Calls); +``` +__What are the leaves of this application?__ + +In a similar spirit, we can determine the leaves of this application, i.e., procedures that are being called but do not make any calls +themselves: +```rascal-shell-continue +bottom(Calls); +``` +__Which procedures call each other indirectly?__ + +We can also determine the indirect calls between procedures, by taking the transitive closure of the Calls relation, written as `Calls+`. +Observe that the transitive closure will contain both the direct and the indirect calls. +```rascal-shell-continue +rel[Proc,Proc] closureCalls = Calls+; +``` +__Which procedures are called directly or indirectly from each entry point?__ + +We now know the entry points for this application ("a" and "f") and the indirect call relations. Combining this information, +we can determine which procedures are called from each entry point. This is done by indexing closureCalls with appropriate procedure name. +The index operator yields all right-hand sides of tuples that have a given value as left-hand side. This gives the following: +```rascal-shell-continue +set[Proc] calledFromA = closureCalls["a"]; +set[Proc] calledFromF = closureCalls["f"]; +``` +__Which procedures are called from all entry points?__ +Finally, we can determine which procedures are called from both entry points by taking the intersection of the two sets + `calledFromA` and `calledFromF`: +```rascal-shell-continue +calledFromA & calledFromF +``` +or if your prefer to write all of the above as a one-liner using a [$Rascal:Expressions/Reducer] expression: +```rascal-shell-continue +(carrier(Calls) | it & (Calls+)[p] | p <- top(Calls)); +``` +The reducer is initialized with all procedures (`carrier(Calls)`) and iterates over all entry points (`p <- top(Calls)`). +At each iteration the current value of the reducer (`it`) is intersected (`&`) with the procedures called directly or indirectly +from that entry point (`(Calls+)[p]`). + +# Benefits +* In small examples, the above results can be easily obtained by a visual inspection of the call graph. +Such a visual inspection does _not_ scale very well to large graphs and this makes the above form of analysis particularly suited for studying large systems. + +# Pitfalls +* We discuss call analysis in a, intentionally, simplistic fashion that does not take into account how the call relation + is extracted from actual source code. + The above principles are, however, applicable to real cases as well. + + # Questions diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/calls.png b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/calls.png new file mode 100644 index 0000000000000000000000000000000000000000..d651f3ef3b1bdd9f4340cd737bbdc586fc234946 GIT binary patch literal 8677 zcmY*;WmFtNv-aWy0t9z=2oPL@yOZGV?z*@KcY;f>;10nh!5tP0wz%8k4tMjO^WE>h zKjw5zSMN?u&C^v+RY$2R%b=kUp#T5?G&xzxF8}~cBUIi;hJ{M@8|2u~n|IE#I&J{K z2Uh4M40HObNeKW@rprl+sRMIPbKm))?Y|DHB&USSmJNamana-|Z<DPDz-d34y3`d+ zZ5*^*+8oN4^%U&hnJ&g?%O4v$Rz{1|tAAu^{v}(DnMyU69LYEG_T7a(h2L#&d^~eE zmw)QcO_-f9#VcT+cS`7-W6ZAQT~dBNTdUz$KR-WxDN{D>tXeNgR%hHO1LxDf^=2be zGet6X1W9(fEqQGweWH#u;cDP^4>m5Y;l#StF9fuoDI229F@?99Ostft_8`PDvNUuf zv{wmgrHz~+NjQ7S85NY4tl@tc%F&XH*<=eEx9@l;@D_zeEr9osMk{&(;%MZALK@1~ zeGw!&mDipw--xBy8p>S8x!r811yb%KK{yG_f=Hv++ZBS)KhB|Y`UZ7|L|=p0G2c<@ z-%Y!LKTT`j@ySr>rH^*UR`s}ozj&gRe_SUcqiy5ffi3t>HD7*p%gT!vC24YQzxij* zu#2dkSV3fEXTkOvO!#}T#mNvXWSPmARM{|Wi9V!%0rD2tZ#k3g@Z`=kUCIYBVp-^~ zUY#^V1dL#tuKeO|9Y6^P{H+G}jY=)x?d9nUmGKn|X%cc6&siUKS+365%A^KWZVS$m zw&qCZ=@NpaueZN!8>w<bS5=$=E~{VsSMDqeuUq=xW)*NXR4#|9sa)K$GzsD~lU|UG zNZn>8BH~1f1aWm^hIW&XLH>93EpCmgKa9zEs9S#vHAsgRCjIXFz{u}Sm=ju1ztY<W zzpdf9zu+ns6I$quTD|hYF14`c58SEq&cnk4$JnEK5Z1gA0Tpq+u&jt+Tsgp^ErNZ4 z<=DNr0v6|6`|?1h3{sX&YR72LBrb-NSAsZBLeNvs<6=sK3VyZBc#aok3v4R;uYO1U zX(!V+)xlm|?5N~3+y_pPo`BfzOa$7=9-erIhAwN_gc$hzzWrmnWJ`IUQ}9`i2AOpr zwHeA4J%K&g^n;(T6bU`MobjZjM~Zu29JOk}bRx9|9oTr9e+))2Fq-<3dK~n!L?d09 zMGseM_3oS%W1>_{M*;%;*L`a-I|f<pPQ(_Blj*57RxLVKe^Y8mC!0I4-Q3bFXKiI| znKFRA8~@}Cml^#H74`0uKn)3bT&y+nUhj6hJDb?6v2xpTB~V{bZQiw6_y~GPV`Y67 zA`{9`D^)I`l+dK7^8|5xs{B1gEl2ebMRWN9zi0x7I__uh<O1K8hlm5IgVN?NMM=<~ z3AqBYt~$K5kp8BPPEDj>q3DErfEfH`<a?&x`8YMd&I+S4KsT9G4XIA8QQ6j~+A*KK zKjFl<%)&oMg&D#|eneYoCkZL%v?e{D%$SWVRNEXLV$UxlX_V!OeWUVzzTei|DLO;9 zmY`0T36v&HZR#uU(i;#>y+-xcqpIf7c;uYa(`9RPbSoTj_}!^+QIUFxr{uMB3mYsN z`17C{BExVr^|21OEt2j&F;eN!Ed3dze<CZl8R9Rr-^9>4i6A-s<Wu0*^L6%&POo*? z0KX!2YHB+a$=kDItOI39=G%L}8u2n+=1z;=GOcjP6+^OQ_JM>F*2T_(ycUv*{8^JQ z1R~=x-DLDvPkuyxrp`H`#tlX#F-s=qMx{RqsG^*t7k=oCkN25j_}!$P2UKxvkduVY zAIPjuAg>#?Kc%u&v%d%LliFXRU=-;@kt0)-$H&PgbqTz4oCKBl`d$NP%B;r~l7t2a zm4%&+!?d_v<_cxW<ggqNs_Ap)^ozOZ)uzheF3Zq|iv<4WA}L$@M&x1+lPQLftx>3H z3>OiARFs0B7=`?%=Q%PqJTR`Q@6tZ(s1dZ~3Ivd4{_41KoSq?qO2j&el5PJ!;*J_A zyx|@du!qorZMzihK#eqdXXZhvTa-%bl7YkTcw!N?gyLCWUvZqq(h!XTGk}axE}5qL z8ie2B>2EG6`!g(mXr<*mJPba`^S(;Qi5M$U4l@Iuf}!jUL*%_ZSO`>>F)o`V#POZ` z{JWeeinU0^Kl5Q1+khJl={T#$m~yEh$|-U>WJ^<MjoFk1`l*YY_E?OHWf$iinh*}g zF?zL8>=8}7JL>{TMkg!W?#YtTGY{X8+z!FhTUx`0Z$9Q2@4^u)1eUw$m0PZD{L6~R zdAE-|kc4&&_d#Xan}6ng@xz=SSQ*}fZEs@LC=!l-th>3O;{Qy}Kap&<#bu5cX6u>0 zY2!;4gR^s}W~KrIfdQHeFB==ZCnu|>1JAiFc==}oxcvMWuyEvO!;3AoTgTf})iY;n z=^u4KxgXVQgq~O1-gO4$DmbI^4(qj?g?~d_S~XZ@OCBmKo3XLk9u6_a!u{HL|Aao& zH<n2B8JwEa8R2Iyo@si;a0S6%SWNhQi$Qmv#*&Zw5K84O<YM}<h_pe$)T+yONFbu5 z*ZME2u&1vG9UVfX`DRgq;mh+C@9>{^(jMk}H<z>Z{JwF~R*Ny0qzT|%$^9pK3LR(V zLeuA$mkfjfIh+VNssgzb+Y0>2$t3MYON69}=`)w9Aec(aiDl21N3JA?M43ApS`Rw% z3y0##N!mN6IO%~=CVh64kn~3~nl@|oD)ew>X7c@J0jTdNJiwo$jEN8{sa(j=M@rru z!<TaZKpw_a4SRHTcWm9_$8-u(@4h*TvjV@-Ac*BgjwGG@kpHd%n&mVzqZStrS*R5e zuvWb~oL&Dyz*fh4Vh!Js_?=tf_jdq0fAYd{Cg-+R)xG!Z&-LNU{-|0E@>io_Og;%8 z{{GIbt>o27+K=%WB+ME_x5u?b9iDM?<d$ejMf8Gjq9pA6KTB1vua5PgQ4Nb0_Ul&_ z403Q*RcwM04yKyt^}#P6DP%=}8p4D3Jp{or7_yuGZhYX2PUK5)En8sOVdK>D*cuE3 zF^%Q8T%Moc00sw!A2zy9(5UiaCt4scvY9Dl@ft^A;Jb2hJ9VBhl0eWX5iqP>n3h83 zx1A60g?r<-kWf(#(?+}~Pwa>0)|3yf@C`F(X_|Mg=J_nMkNy~d)^zGSf5vI5X&m*1 zAS#*>OzcvCf1QFK&Cq}!xgX>J4+8&BK_TN5QDK>pZDsNN{5aw-he@`^fw~-L$t(>L zol2yv+#g@UBzwIFm3$`VDy#BW7u93Hp$hfo2Y2VDWc-ctvqu2M#*#GKYSapNFxik3 ztCHJ>4{sR8k29ka4yVh>5w4cchOBi&*b?HZl8^pJ)!psCSarBkAaUB+`TSF<bhRyo z`envZ6z=B-s?hBHs<sybBcpusz-QH`#30)Y*~s?<2lX6uSX6L3$PBOvjxf=xv!oY5 ze8`9Iy9b?!un3@;U#G*fRA=-^TeAMis<=7fvhz2-#(ao(Ro=Q`{?Qv~r8TiKE%wW( z2)B%pI?|1w(@{>h;~h_DlYK0DQgy=0@Hp~liYXaGj+*|4#;>wj)Hv<5sTT{kmsGh| z7w~)<I9TEW!D5*G>}t4W`nUv144<QaM_V|S5_U#zohsp^p@2Q4w1Y*yXg>W=pri<k z)QN64nv~<cShL3Oe(<M2f{;AmK6+*oP_FHU#rLH#l@S}%U@=!&JNY4<8f}JY(C2qY z(smrNPKg2rI&z(ijFzy^^_TK51FZV3N!fy0kA_Pf4DQwkIeab;OI9)>(EN_|NvGTW z5ViV_Au=SGe$Qoskn<Oh?X{w_^TuFYPZ#9R?9(k;`u+Y%7fgFe)=(PDkde2AG?Y=r zlRi;%UOBqE1GTl^xl#<q+R#@*;fzjn?Qy#Ny%kOS^7GT35u;&Yr5><Vf*=}G?s|+( zC7<gM&9TX9$}r^%9h)((lh~HOMN$pmN=Wdkb?=R5^6uu8`Hs>{A~G(w6X?RrqmJe6 z3<;{L(m{#FN-63~k`Z3zrs;9p>!da;+;FhuIaSf=SWCxesV8I%97g?n^*DK>S{KU> z0}hqnsx$hXINI)hxbd~LMp`n}V$s}cpNUysF}~5VODQ(}dSt|<TCWA9B9c2c;XxwA zSEyMX-Edo=vQ}@>*W1*@OtV~}G4}ZML@E-%Pis+EZ5+VSycd=n8FGH?Jbt?1aM55P zGB7Be%A}Dq=f4w)P6%=xP3^J4ENJ(8I$fWgt!;E`?!J)D)vT^yW*${qNkTRnn4znV ziv2hgvw-tGx4IWKCB-)}VJ4Vi=dhIAiZeew-Kpj7t7beml21_Z{o|;$B_xOhMk?b% zPW;v4vkL42J~#E^10_v6T`-&|5`~N?W;+yHm_Jc720D{C%gB00&klB5j&%q$SS#w0 z*9|NS*`<sr1P<V<rA1MvseT|>V@|_1AzO=Cu%MuiX8S{6Wli=czba)Z+Wf7P_q=^6 zl3}wn_cQGFE31q{@PIXfKbrpKkle?<m{LG)jak-ry24MEM)7mN<2b&_Nu*T2Eiog5 z$;L)2p8TzEGh_d_axa6v4iJhI$m#DBU2SZN?`Z+$>1||;KL<QdZC#y|RvPyedjgpE zrp}o-)s0;;Y!)LwG*=hZ8g{)5;AcbQZN6WFc?w<9{*)CH7#pw^X#@QjaymM9>1=^_ zZToqxo~M)GE-MdIR3N3Pu8{kIxTYr7cKhyV_%A0TJ8&nd4+3H*Iy=kWj*xMBW20MW zC{pL+k=t?vd?yY&OTe=WGl=4Jy5M$iV(9Af@@zGe5R+Z}O-2M}_oG3(*VUexIVGhD zyOjk7_tVVwkQW8{8BviTF|0KmCFM6iAuonl4>;W*JQx<ym>|3#Qvd7JkhSqzBp9E< zHsy^NQ6-^FZxUVjCIAcU|E{fqRInQ(hX_!T;txCqv;BX?4=D=V>Qc)HykMFpyh5dD zyfzh<4cuXH6uNlRZ~S1rYs^A4UHGj7I>~GtmSk1^DHYVJ*u4MfnBDRI=|T?JB(o)1 z3O#R`-a{96BLfL&CG~>w@XK^5-pY}UsovxKo7IlR{kQu4H`EzK7Fc3Z@g}jDUg-(G zC<T_s$6H!TrVK(DfJN&Zm72-%0_QGX1pnV4Q&kx_UFX_?+0xoy4uZ!gOLl7=J$zng zf@KZC(ycRFtuDt}HO~)_O|{_Q+iJa8Xy|{rS-eh6r0@@cGYltYp`$<N_kZLZM9t<+ z=ybEPjKYFz!)}E6f#=xN>b4)TD&GdY0K{#>fMbYR_IB0SFX203cy;YT<|ukH*k@~F z^TIS6C9N~2OAVA$A|kib1xc^30Ue&X@o~9++tP)?Nia@*FCOW0bF)*QdH109<qGyd zHh$}mTcC4^pR}Vkejp}xx!fuB8G!4=*Y~QYOI`J$Mzx*^$xw^rv7YhCQ2#fWW@!Bz z<TG?5dB%M$2*+8W4mHBbA0JLBj}Kzf7LDZ5II`A^{7^!vEfLA%u@V#ep-m)sL0dYK zhvkvAPHwF(`GZ5&ZIKS8?njGM<F2YB_Md$}$9)btQ4}n{fJp4~6srcp<;cOV$CPO} zg%gSOjx|cl6!Mwc8j(C^wH-gs(qfvir3Sb~Bfn5BD901CG^n&6e9bQuvoRD(N5SnL zhC`D-?~lV<8Zoe!wubd+iGt`eaXl7t)=1iDY+p~hO_AuDW8#zy#FMy%=$t6Xi5-#H zH|>VwV}xV+#2<F2Mtot<WOLo@@}DBQX}(J5c;Zctk7~Al2sM9T8-{bMQws~0z_Z<y z+o8LaQZO~d^@$(ZGiJ6Hnb4|j5^g8;WlX}ir+9Hb5L(OK6ZmiZilQ#e74{{0o%|(_ zK(}x|C5n4zGafRkTWo^<3rZ{fUv2^#KAB0Q%oN@aPQqkc4Dww`JGvEMqduBiTdUR@ z#tr|;;zo*%M;Pu6(Y>}*^nY27!|QN=Sky9ld49f9K}1}}AQ{%~__e;=jFkTIkkub) ze1-a2PPNNt8-s*zy-cmY#-J1mu<N_KS(DO9hE@e?K|x+FcVlN~<3`J*;*zJQbFFwX zQY8@U#S~hglj6fA`QGj>ucN-lL{_NC^Ct>c)^de(#IdxSRE@|CymcQtG380PGaS&( zv5NI_Q&y_$&6GkMk=eR`%M4qO-=Ak_QUN=z;T@B};p^b1Lu1Tp2QM?l4~X;q)7{v( zB2K2yjK;U-gUYDD@MzqK8tn_m7(BJ{S`!nA;ak^mYwX3Xqj@Bf__o#a+wBDMvt<wY zPRZXS<;fHmvCp?F<e`Y%R)I>gk%wUas8myLn92aWwwE)BZ&lz{7YeP&u%;#zLHCT5 zWRCuMBbV(R)?*_XqxNh3^6<Y#v$OQy!rp&TjqkiQtv7vT(GzCwM!Q>6K9MKIQg6H5 z?kt+jE8l|j?|VC?5+k1TbBIByZB|C>fVaP-q)wEtc{gm-Y9dTkX+G=38k>y`1G7wm zm*C-7qkKWH+^2gClc!rQ(p%=pDz}6tOsmP0=cCH0!%m}SS(T|B%RK%MD5IFOrcbxc z0$zr#wi;}nygjcz<(3mJuKOqR^kPtYm&4;QCi3>cadmt&-@`{Tc+IdIu+HxWu3ve& z*u$YV2qycSMniw6DkyMcGyUTd%J`F$1vmxDzwBaL>_*xoU1te;=A0j|v^cJny1J1{ zMxHLu)3C@|_=2)Rk!0q|ua=O=p9OhtO+?!D+f=u>-o1-aNJY*OY>Anfi4vm$LTd-A zAy7%o(4gkT@UW0GMUi4AR)-e`w*Z&i$(mr5&1|_O{dnz8M_BmFS22Faql5X%CkBQ& zhDee)bTP_=&zh@qoc&JghLV0el3!Gj2it>3-;+I09L${JJX1Cy&jn{{r+<X*g3k`R zG6=MyF`}eGPG<ivU3ij{bNh}8ytk_)uFK0{o!56q2A>IiQn>O9+KWz$*#NZagtQzc zd(X}MS9<wR&W45&F0&eIZweqQMswL#cE_C5)Ljq^KuBhd7p5ZSqlj1xCIE2X3k~Bz z7&-qXijqcP7-g_i({kU;g`@JGt@(byF=zWqw^Lp!FC+E+e7(m8%GsLGgFLNLfnPhy zo%jHNx}E*~p6APJXjo5UJ-vVy9>T)mY|CAN>n?|&iP7)mTKcW-A_Z&E$AJ;+2eb^m ze?j-qD!y*=Zm*~>5SSs%4%~@FmjR!xS@GEO$p_@|x$tvdu|~Z{(aZB#tG3A`;On&n z7*;dD02V9{L0#Fr<Ys}NU3W~yLwcX?<d``qdQ~Fs*qPm>K@Dp?BK|wWPDX)U%C&~| z`Th&RPiY#L)!+bMlWd;GfO`|sO3ir<6kLF@4i=W_@Q~&P3$y04ex;@qD4WMwUMhT2 zma4|6=W;Oa&n)9y*~QN9Q0kBnu!{ULiWNK{1AW(Fu*2&-f#71@`zm03#G9fO&1I@n zNN5EK@QEBH*JZBkd@x~MRJW(aY090On?~>f+78Y@4WHz*0S%(y%C}`Lq&G3(vnpzk z;Q#;x(c)J64aG8k&xH`kW~Js$bUb<sZmWr>=NT0AevgNboK`g&wXb0nF0g>000#F# zRs*6$75UMqC~V&1pLwI><4igY+7ns0TU)lpKLgaEe5`sSSA>}Nb+D7PN3AF_S5QQq z(-{yHP9SujeK`B|uThi-GWw8(#RLjU!>sQjx&$=fP`Ryqeahki032xOJgLZwiWuOh zdZF!Waz1JR020gzeTDxc=0{8puWG{q0C{F;BJ~QHma1FFWH123_<kFO!xo(HzO!a1 zsx69upheTv5@RU7jD?!GD>h;Q0BXW3ky!L%WM^qW5c~9D)cbz|c={yjeR7W4V@}CV zrD@1lKoB#uRegz?*xIF*7Q#af5XIQ>DXjm8)@4&x74smdIb6ZsJynN7NidVh098Ux z9xQL*QS4+b>3$8nhb;i$S|3Eu0iB^i5Evf;0Dh2+0jWY!x@@%etTRbq0FreX^c?>T z`|&}+hbmNH@?h1z$-Dsoc#n50i8#L;tMAt2+zo)ZemswQf1m^hpxLC&8~*W=lVd`f zO^yTr4%PCe3N^U#b(~|Fu0%%!G;N0G4MXZCGV(>%4?cy}52_0SGB9gzBoSkw#zt@K z<_!a&=n;a=8-}%l?_FWPQE=gcokR}03z05lHWxG-ON+v11Jj@q67n`QoC%V+ar+mA z^%Him55NK%g6N~ER|s3|3GJD!fkOkd4$JO}p@{$h5v)ootj{a}hzgNB1il7Huf!5E z_I;~v3tYz}A^Cu-0bXA}-x(39HR@4#<rNn8S*U7(M!0@K5|~dPfQ9-(uF}cYB0b&J zx*9#64n*}L;5Ts3+Yh}KT3UtIBE6Q3ftU{825LYM$41mBWSCG6v_EkS>hP-CcteE$ zt5xGyD-}})?n+N*B^C0pl~K7x03;#^&0q_iD&3ttB;O;hwR6)<wg$YYL1FX=P`M^i z$-OXolRup;tsh1lOMjCL7#x^~ILiP4Exp+De`Z|<yZyEN?$$P)x(`W7#>uVxAQZJ) zwIw4XzT4YHrAiW^m;kh3`idBPD;nWepBpywO~@wdj`3<If0s)BE+u8?a8_cX=-%@6 z;jC7zew)%l=c~`@@nY$BCJn`#Rv5s0h$KtmX>4tmW{h8}oo0<+g5MnKI{?KyV4>%R zvJ+q&Nl48O3e*flPaaHQ;Q|zMpJ1$Mj^0DDQV~o~1PdMIq2sWGRllS<Ko$D7&L>|Q z*e-7B%}^j*RDezbAQkQOe70pAE;?!_8jwmu^(eN}$RxG*PcJrO7^~IJrhOzt-k5eH zYEHbFQ((J7EsC%qs4a;8OZ-SqQiR)BQm!UU7Mwe*3Ks%&PngEU-luLl@LK(@Z;U0z zL<fZTVk?EUt;BxWc!wD$|3d^KIcwr?wG%8&JB<x2l)DhHaz}*D3ji78mx4Jj#fq?i zutZS{y<e@&^|U8@bfrnG)nOT--VQ<363bTOsUi9v@`lg}11!9O%n?Qr0THKa?&{33 z3tH8p0@|aDBhYiTwva1O(P|3Cvu7UcfNiQfJWQJj*L@_^XGtOSw$Yn4A>bymR$FzH zn*I@K;*dT=KE(xTQmY*zA)IYIty}~|^JFG{QpWqD1%hLyie|#hT9|?|<jK!{nB~3p zd|b<BKPrFlI=tsAKNy0<0D48|+@&KCwGkD-nRe2#^4$iVJ;r>N__Ux(k&1o&8(5Z1 zPsA}~uwmVeA4{lOJU1P;q|Z&na?Z-fTu(&X6gK870_au)2tz{)U<2;|id-<zGZT81 z*QG+Kt?$e&P)=~-LrzW}VaB?v1qbtqx=C;wdLUL}Z+SnipC#T|tJ~~KNSN}MyPtV` zE1AbZV9#Xa3&`$tl?WE(hy!{F^ZfVcFKWLD@6J_>e%YTx8<zR}Zl`B!If;osh`5>D zPqfe1yo{I(g*L+R>E^1R=Kh3V<ekfs#C*0;?2hb9db?SK9>yG%<_8LUZA)uHOBc4T zYejMKJ{|{KpdOO$6fbI`!3SD;`g1us{-iX|hrNlDUeTwoD&OOy-UVJw_*Cm@-QC>G zxWk8LO63Zsw_B*Qpg&|u?l)RF8Fk!lL}8~xOIK>@v94R)>brF(>HaDA<P7yhvwEwk zPfDR`2eCB5g&CF;KgsxA>vfwOilm!!KNY}Th)J_Uv+-nZscw@jO)1v{HxG~aK%`D9 zXp=RYG<juvh>h2VWB2i<<KkjwwzNm1*;dE$r9>f6DX+8K|A}8sZC4@fJ%_*-xasR$ zf3LHhCYuz=V!al}5|{1e1d^q}_4R}oBh&QRQYV}B0M96_;A~!u8lyKGjprG8kj8ud z_?4D7aK*f$h)DjFNTAy}*aQX!qdF>M`x{!%LbaeUw3+m9S-?Z;M6GoY`*d3})UQZo zb#l7G84?QT{dT;+ztUvW{Mq*#^!!fEmB?*(_nl#3fmDp=-BELu*-$$)&2HuSnzxCE zf9Yo!DfK$HFbZfJ>!n~6lPVGdekwBTx<DgRpB%|_Xn7LoF#OJB!fEB1$G%;yBK*LH z`IgEUNX$`Ovb6@o8WqI|ZEQLvPmQFkLrE>ylpMz4pKv);ypxj^ukbudM)$#BIIX1o zm0xI-r;=JLiB5lO3?@1|y-_5tG=(1zL<$PTn6+-PmMmYNYoNs{osz;@VIeujO2W%i zpj5!Oh=`k$%#Yo)Hp73($U?|9T%`9O!3w$n{lRzxd_2t%b)Jwng`8rQ{J|K!Qz|r$ zHStU9oTQ{kM_|-9+3#{#%&(q$u(N9rU3GFFUkF9QvwrKRJomIU$4=9Mj-#_&)FRv3 zb{>N&?Ovlmi}jwjplddRk=NT(lFt%ffSyXZ%j`U!t}%E~5$JIrHOGz26)fQkT(&EU zW2)_qsOl!IX$fiRJdYL}<VuxMHEYkdM~NfQ4cwspXa8*uEK*jn!~p$9%h~0Y{4Vb+ z<7CfO+6sE8E6-NFO|JH4e>uJNI__KLk@$nY3kN)7K%aRK%hs*EJ0()eWyizK2NbQ= z#T|-wSgg?SSU5iiPb@X;_HPYzKp`N$@SbdyiteX;z3Gu)5$)kJce_|9WH63?<@9B1 zyGSP99vk!G0A+k2hPPCCO~`Fe6J%VaepnwHODv6KN1DY|?zubKH=0)rHB2`7RyvNV zb>7>t#_a9&8jU!>pxJi4{HvC~zkeFr`BIykKb+aG_oRXg)kbfPgK?$s@XuXpMONR? z9i)=U39Ln2ceYNJR?=9W%9V5F>+3Pg%S$~_<q9~ldQefBU9J!M(w6f+Tcv$m`fUGq zxzVaN>DKuW^n1QiWT{GL2j8f(&C@|-uFI8@Fedh+*TbbbsE3+<6I#yPo(iUCW=i>Z zbK9yPOc!`sTK+48_4PpySDDa`_Q-952$|o_k$Mpv?`MnAFcN-_=sD4$_?LRq0Tn0> z;P<|q%I0m13L8s9i;HH$CnW_p?7DT@E%drYe(US&o6OIbeMDUORpS42T2NeEZT`|Y zB|OvNwY{@LX8y37UYfEj+mfhGP-*+O4&?!8h`u4-B(B_Ze@}3UxoX_^Em$C8hc<CM zrNaMD*kT-d|1pHIRi#NPhT(Fv#_9}nwSN1KPO0JuFIFpI3l3l%n%TdyIXpHw`EoU- zS1h5_Uc4R{*yOk-f4qn>&GH^f5rU|aQ|^8ilhC6=dqfQu5>(V<Q<+zjE$e9@Z+&zq z)B6#5wnm(sFr)U53--SI4>_$@Xp7L?QkYO5f=y_p=z65S_~l$|fNHJWT{T`bfeZln z{Ue^eNYJ1Emq^23^c{R5MJZcLNpXrO9(egio-ns6;a=LM1tq=!IVoky8gb*0{{hB; Bwow28 literal 0 HcmV?d00001 diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/If/If.md b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/If/If.md new file mode 100644 index 00000000000..d33ffc29803 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/If/If.md @@ -0,0 +1,43 @@ +## If + +#### Synopsis + +Conditional statement. + +#### Syntax + +* `if ( Exp ) Statement;` +* `if ( Exp ) Statement~1~ else Statement~2~;` + +#### Types + +| `Exp` | `if ( Exp ) Statement;` | +| --- | --- | +| `bool` | `void` | + + +| `Exp` | Statement~1~ | Statement~2~ | `if ( Exp ) Statement~1~ else Statement~2~;` | +| --- | --- | --- | --- | +| `bool` | T~1~ | T~2~ | `lub(T~1~, T~2~)` | + + + +#### Description + +The test _Exp_ is evaluated and its outcome determines the statement to be executed: +_Statement~1~_ if _Exp_ yields `true` and _Statement~2~_ otherwise. +The value of an if-then statement is equal to _Statement_ when its test is true. Otherwise it is void. +The value of an if-then-else statement is the value of the statement that was executed. + +#### Examples + +```rascal-shell +if( 3 > 2 ) 30; else 40; +x = if( 3 > 2 ) 30; else 40; +if( 3 > 2 ) 30; +``` +An if-then statement yields `void` when its test is false +(demonstrated by the __ok__ that is printed by the Rascal system): +```rascal-shell-continue +if( 2 > 3 ) 30; +``` diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Libraries/Boolean/Boolean.remote b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Libraries/Boolean/Boolean.remote new file mode 100644 index 00000000000..8816436b0ab --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Libraries/Boolean/Boolean.remote @@ -0,0 +1 @@ +|std:///Boolean.rsc| \ No newline at end of file diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Libraries/Libraries.md b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Libraries/Libraries.md new file mode 100644 index 00000000000..27e5bad4c8f --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Libraries/Libraries.md @@ -0,0 +1,2 @@ +## Synopsis +Library functions diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Questions/Questions.questions b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Questions/Questions.questions new file mode 100644 index 00000000000..175e89bc169 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Questions/Questions.questions @@ -0,0 +1,66 @@ +question Which means of transportation is faster: + choice n ||| Apache Helicopter ||| The speed of an Apache is 293 km/hour + choice y ||| High-speed train ||| The speed of a high-speed train is 570 km/hour + choice n ||| Ferrari F430 ||| The speed of a Ferrari is 315 km/hour + choice n ||| Hovercraft ||| The speed of a Hovercraft is 137 km/hour +end + +question Replace the text box by the result of the multiplication and make the test true: + expr multiplication $gen(int[2,7],A) * $gen(int[2,7],B) +end + +question Replace the text box by the result of the intersection and make the test true: + expr setIntersection $eval($gen(set[int]) + $gen(set[int],B)) & $eval($gen(set[int]) + $use(B)) +end + +question Replace the text box by a function name and make the test true: + prep import List; + expr listFunction $answer(size)($gen(list[int][1,10])) +end + +question Create a function to print squares by placing all code fragments in the grey box in the right order with the right indentation: +movable +void squares(int n){ +--------- + println("Squares from 1 to " + n); +--------- + for(int i <- [1 .. n + 1]) +--------- + println(i + " squared = " + (i * i)); +--------- +} +end + +question Create a function to print squares by placing all code fragments in the grey box in the right order with the right indentation (and avoid decoys!): +movable +void squares(int n){ +--------- + println("Squares from 1 to " + n); +--------- + for(int i <- [1 .. n + 1]) +--------- + println(i + " squared = " + (i * i)); +--------- +} +decoy +i = 0; +--------- +i += 1; +--------- +println(i + " squared = " + (i + i)); +end + +question Click on all identifiers in this code fragment: +clickable +$click(x) = 1; +if($click(x)){ + $click(y) = $click(x) + 2; +} +end + +question Reorder the following items and make all statements true: + fact [1,2,3] ||| is a list[int] + fact {1,2,3} ||| is a set[int] + fact 123 ||| is an int + fact "abc" ||| is a str +end \ No newline at end of file diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Test.md b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Test.md new file mode 100644 index 00000000000..aa7c0946a8d --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Test.md @@ -0,0 +1,48 @@ +# Synopsis +This is a test synopsis. + +# Description +See examples below!!! + +# Examples + +```rascal-shell +import Content; +html("this is some \<strong\>HTML\</strong\> output") +file(|https://www.rascal-mpl.org/assets/ico/favicon.png|) +1 + 1 == 2 +int count = 1; +content("counter", Response (Request _) { count += 1; return response("count: <count>"); }) +count; +count = 66; +content("counter", Response (Request _) { count += 1; return response("count: <count>"); }) +count; +``` + +* _emphasis_ +* *bold* +* [Rascal Web site](http:///rascal-mpl.org) +* ((CallAnalysis)) +* Table: + + | Module | LOC | + |--------|-----| + | A | 10 | + | B | 20 | + + +| Operator | Description | +|------------|------------| +| `$A$ \| $B$` | alternative | +| `\|\|` | or | + +Horizontal rule: + +--- + +* `code` +* `in code: italics` + +# Benefits + +# Pitfalls diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Test.quest b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Test.quest new file mode 100644 index 00000000000..96128c7bd7b --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Test.quest @@ -0,0 +1 @@ +[tvQuestion("Test","1",valueOfExpr(),details(" Fill in the missing function name!\n",["import List;"],"","","","(\<L\>) == \<H\>",false,true,[<"L",list(arb(0,[int(-20,20),str()]),0,5)>],[<"H","reverse(\<L\>) ">],void(),"reverse"))] \ No newline at end of file diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/t1.png b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/t1.png new file mode 100644 index 0000000000000000000000000000000000000000..7a113ac8fa29c83cf9af47b4972d627bd1d6f7bc GIT binary patch literal 312 zcmV-80muG{P)<h;3K|Lk000e1NJLTq001HY000;W0ssI25X87%00030Nkl<Zc%1E% zJqm*`7={&52NxF);UrSGo<Q&d-oi6Dy0|)c0_&ukqi#Bg2u?b9g$_y)X#>HJw1xhG z4#j5*Z^-+EFNC^o0<~*)&Gwpc9H|AK=QT|W!w_~kj-&30<M=tJY5F5g+qP)k_q}b~ zk|co%)#Z79CYYuP=UJAK`4<|D>$-}ffTn4h=L(dUW%*)a?=?kHuq->zGp<9{(?S$Q zpm7`ra`O16ny%{@hCw;5Q&si2U>F8yWLZ8e1VQk%CQVaR0d;E)&9W@hG*{FN!{B+I zD2jDmLtRx>7Xo;*Uo;p&5OiHnl7zhV!@u!;{};`A?3!KkSDG6SbE$a&9ONMY0000< KMNUMnLSTaGse;%5 literal 0 HcmV?d00001 diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ConvertQuestions.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ConvertQuestions.rsc new file mode 100644 index 00000000000..284847a7fba --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ConvertQuestions.rsc @@ -0,0 +1,71 @@ +module lang::rascal::tutor::questions::ConvertQuestions + +import IO; +import List; +import String; +import util::FileSystem; +/* + +Name: => # the_name +the_name + +Synopsis: => .Synopsis +enz. + +<listing> => ```rascal PM from file +</listing> => ``` + +<screen> => ```rascal-shell PM errors en continue +</screen> => ``` + +$VarName$ => _VarName_ +$VarName_index$ => _VarName~index~_ +$VarName^index$ => _VarName^index^_ + +<warning> => WARNING: +</warning> => "" + +[ConceptName] => +[$ConceptName] => +$OtherCourse:ConceptName] => + + +In code: + +*/ + +str getAnchor(loc src){ + parts = split("/", src.path); + return "<parts[-3]>-<parts[-2]>"; +} + +str convert(loc src){ + return convert(getAnchor(src), readFileLines(src)); +} + +str hashes(int n) = "<for(int _ <-[0..n]){>#<}>"; + +str convert(str _/*anchor; why is this not used?*/, list[str] lines){ + result = ""; //"[[<anchor>]]\n"; + int i = 0; + + while (i < size(lines)){ + switch(lines[i]){ + + + case /^Questions:/: + { i += 1; + while(i < size(lines)){ + result += lines[i] + "\n"; + i += 1; + } + return result; + } + // anything else + default: { + i += 1; + } + } + } + return result; +} diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/Feedback.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/Feedback.java new file mode 100644 index 00000000000..eeacc4bbf5c --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/Feedback.java @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2016, paulklint, Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.rascalmpl.library.lang.rascal.tutor.questions; + +import java.util.Random; + +public class Feedback { + + static Random random = new Random(); + + private static String[] positiveFeedback = { + "Good!", + "Go on like this!", + "I knew you could make this one!", + "You are making good progress!", + "Well done!", + "Yes!", + "More kudos", + "Correct!", + "You are becoming a pro!", + "You are becoming an expert!", + "You are becoming a specialist!", + "Excellent!", + "Better and better!", + "Another one down!", + "You are earning a place in the top ten!", + "Learning is fun, right?", + "Each drop of rain makes a hole in the stone.", + "A first step of a great journey.", + "It is the journey that counts.", + "The whole moon and the entire sky are reflected in one dewdrop on the grass.", + "There is no beginning to practice nor end to enlightenment; There is no beginning to enlightenment nor end to practice.", + "A journey of a thousand miles begins with a single step.", + "When you get to the top of the mountain, keep climbing.", + "No snowflake ever falls in the wrong place.", + "Sitting quietly, doing nothing, spring comes, and the grass grows by itself.", + "To follow the path, look to the master, follow the master, walk with the master, see through the master, become the master.", + "When you try to stay on the surface of the water, you sink; but when you try to sink, you float.", + "If you realize that you have enough, you are truly rich.", + "Experience this moment to its fullest.", + "Many paths lead from the foot of the mountain, but at the peak we all gaze at the full moon.", + "As a solid rock is not shaken by the wind, even so the wise are not ruffled by praise or blame.", + "When you get there, there isn't any there there.", + "At the still-point in the center of the circle one can see the infinite in all things.", + "The whole moon and the entire sky are reflected in one dewdrop on the grass.", + "When the question is sand in a bowl of boiled rice, the answer is a stick in the soft mud." + }; + + private static String[] negativeFeedback = { + "A pity!", + "A shame!", + "Try another question!", + "I know you can do better.", + "Keep trying.", + "I am suffering with you :-(", + "Give it another try!", + "With some more practice you will do better!", + "Other people mastered this, and you can do even better!", + "It is the journey that counts!", + "Learning is fun, right?", + "After climbing the hill, the view will be excellent.", + "Hard work will be rewarded!", + "There\'s no meaning to a flower unless it blooms.", + "Not the wind, not the flag; mind is moving.", + "If you understand, things are just as they are; if you do not understand, things are just as they are.", + "Knock on the sky and listen to the sound.", + "The ten thousand questions are one question. If you cut through the one question, then the ten thousand questions disappear.", + "To do a certain kind of thing, you have to be a certain kind of person.", + "When the pupil is ready to learn, a teacher will appear.", + "If the problem has a solution, worrying is pointless, in the end the problem will be solved. If the problem has no solution, there is no reason to worry, because it can\'t be solved.", + "And the end of all our exploring will be to arrive where we started and know the place for the first time.", + "It is better to practice a little than talk a lot.", + "Water which is too pure has no fish.", + "All of the significant battles are waged within the self.", + "No snowflake ever falls in the wrong place.", + "It takes a wise man to learn from his mistakes, but an even wiser man to learn from others.", + "Only when you can be extremely pliable and soft can you be extremely hard and strong.", + "Sitting quietly, doing nothing, spring comes, and the grass grows by itself.", + "The obstacle is the path.", + "To know and not do is not yet to know.", + "The tighter you squeeze, the less you have.", + "When you try to stay on the surface of the water, you sink; but when you try to sink, you float.", + "Where there is will, there is a way.", + "The grass is always greener on the other side.", + "Change how you see and see how you change.", + "If you understand, things are just as they are. If you do not understand, things are just as they are.", + "The wild geese do not intend to cast their reflections.The water has no mind to receive their images.", + "No matter how hard the past, you can always begin again.", + "You can't stop the waves, but you can learn how to surf.", + "Scratch first. Itch later ... ", + "It takes a wise man to learn from his mistakes, but an even wiser man to learn from others.", + "Nature does not hurry, yet everything is accomplished.", + "One gains by losing and loses by gaining.", + "Relearn everything. Let every moment be a new beginning.", + "The world is won by those who let it go.", + "Normally, we do not so much look at things as we overlook them.", + "If you want a certain thing, first be a certain person. Then obtaining that certain thing will no longer be a concern.", + "Nothing ever goes away, until it has taught us what we need to know.", + "Don't get stuck anywhere, keep flowing like a river.", + "The birds always find their way to their nests. The river always finds its way to the ocean.", + "Do not seek the truth, only cease to cherish your opinions.", + "The resistance to the unpleasant situation is the root of suffering.", + "For things to reveal themselves to us, we need to be ready to abandon our views about them.", + "To deny the reality of things is to miss their reality", + "View all problems as challenges." + }; + + private static String quote(String s){ + return "\"" + s + "\""; + } + public static String give(boolean ok) { + if(random.nextInt(10) == 5){ + if(ok){ + return quote(positiveFeedback[random.nextInt(positiveFeedback.length)]); + } else { + return quote(negativeFeedback[random.nextInt(negativeFeedback.length)]); + } + } + return quote(""); + } +} diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ParseQuestions.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ParseQuestions.rsc new file mode 100644 index 00000000000..5e44d95951a --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ParseQuestions.rsc @@ -0,0 +1,8 @@ +module lang::rascal::tutor::questions::ParseQuestions + +import lang::rascal::tutor::questions::Questions; +import ParseTree; + +public Questions parse(str src, loc l) = parse(#start[Questions], src, l).top; +public Questions parse(str src) = parse(#start[Questions], src).top; +public Questions parse(loc l) = parse(#start[Questions], l).top; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java new file mode 100644 index 00000000000..b41ed71614c --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2016, paulklint, Centrum Wiskunde & Informatica (CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.rascalmpl.library.lang.rascal.tutor.questions; + +import java.io.File; + +import org.rascalmpl.interpreter.Evaluator; +import org.rascalmpl.interpreter.env.GlobalEnvironment; +import org.rascalmpl.interpreter.env.ModuleEnvironment; +import org.rascalmpl.library.util.PathConfig; +import org.rascalmpl.library.util.RunTests; +import org.rascalmpl.uri.URIUtil; +import org.rascalmpl.values.IRascalValueFactory; + +import io.usethesource.vallang.IList; +import io.usethesource.vallang.ISourceLocation; +import io.usethesource.vallang.IString; +import io.usethesource.vallang.IValue; +import io.usethesource.vallang.IValueFactory; + +public class QuestionCompiler { + private final IValueFactory vf = IRascalValueFactory.getInstance(); + private final Evaluator eval; + + public QuestionCompiler(PathConfig pcfg) { + final GlobalEnvironment heap = new GlobalEnvironment(); + final ModuleEnvironment top = new ModuleEnvironment("***question compiler***", heap); + eval = new Evaluator(vf, System.in, System.err, System.out, top, heap); + eval.addRascalSearchPath(URIUtil.rootLocation("std")); + eval.addRascalSearchPath(URIUtil.rootLocation("test-modules")); + eval.getConfiguration().setRascalJavaClassPathProperty(javaCompilerPathAsString(pcfg.getJavaCompilerPath())); + + } + + /** + * Compile a .questions file to .adoc + */ + public IString compileQuestions(ISourceLocation qmodule, PathConfig pcfg) { + if (!eval.getHeap().existsModule("lang::rascal::tutor::QuestionCompiler")) { + eval.doImport(null, "lang::rascal::tutor::QuestionCompiler"); + } + + return (IString) eval.call("compileQuestions", qmodule, pcfg.asConstructor()); + } + + public IList checkQuestions(String questionModule) { + return RunTests.runTests(questionModule, eval); + } + + private String javaCompilerPathAsString(IList javaCompilerPath) { + StringBuilder b = new StringBuilder(); + + for (IValue elem : javaCompilerPath) { + ISourceLocation loc = (ISourceLocation) elem; + + if (b.length() != 0) { + b.append(File.pathSeparatorChar); + } + + assert loc.getScheme().equals("file"); + String path = loc.getPath(); + if (path.startsWith("/") && path.contains(":\\")) { + // a windows path should drop the leading / + path = path.substring(1); + } + b.append(path); + } + + return b.toString(); + } +} diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.rsc new file mode 100644 index 00000000000..d0a26fe262f --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.rsc @@ -0,0 +1,530 @@ +module lang::rascal::tutor::questions::QuestionCompiler + +import IO; +import String; +import util::Math; +import List; +import Set; +import String; + +import IO; +import util::Reflective; +import util::Eval; +import DateTime; +import ParseTree; + +import lang::rascal::tutor::Questions; +import lang::rascal::tutor::ParseQuestions; +import lang::rascal::tutor::ValueGenerator; + +int countGenAndUse((Cmd) `<EvalCmd c>`){ + n = 0; + visit(c){ case GenCmd _: n += 1; + case UseCmd _: n += 1; + } + return n; +} + +str holeMarkup(int n) = "+++\<div class=\"hole\" id=\"hole<n>\"/\>+++"; +str clickMarkup(int n, str text) = "+++\<span class=\"clickable\" id=\"clickable<n>\" clicked=\"false\" onclick=\"handleClick(\'clickable<n>\')\"\><text>\</span\>+++"; + +tuple[str quoted, str execute, bool hasHoles, map[str,str] bindings] preprocessCode(int questionId, + TokenOrCmdList q, + str setup, + map[str,str] initialEnv, + PathConfig pcfg){ + nholes = 0; + map[str,str] env = initialEnv; + + + tuple[str quote, str execute] handleCmd((Cmd) `<GenCmd gen>`){ + switch(gen){ + case (GenCmd) `$gen(<Type tp>,<Name name>)`: { + str v = generateValue(generateType(tp)); + if(env["<name>"]?){ + throw "Double declaration for <name> in <gen>"; + } + env["<name>"] = v; + return <v, v>; + } + case (GenCmd) `$gen(<Type tp>)`: { + Type tp1 = generateType(tp); + v = generateValue(generateType(tp)); + return <v, v>; + } + default: + throw "Unhandled $gen <gen>"; + } + } + + tuple[str quote, str execute] handleCmd((Cmd) `<UseCmd use>`){ + if(!env["<use.name>"]?){ + throw "Undeclared <use.name> used in <use>"; + } + v = env["<use.name>"]; + return <v, v>; + } + + tuple[str quote, str execute] handleCmd((Cmd) `<EvalCmd c>`){ + <qt, expr> = handle(c.elements); + v = "<eval(questionId, expr, setup, pcfg)>"; + return <v, v>; + } + + tuple[str quote, str execute] handleCmd((Cmd) `<AnswerCmd ans>`){ + nholes += 1; + txt = "<ans.elements>"; + qt = holeMarkup(nholes); + try { + gen = [Cmd] "$gen(<txt>)"; + <qt1, ex> = handleCmd(gen); + return <qt, ex>; + } catch: { + try { + c = [Cmd] "$eval(<txt>)"; + if(countGenAndUse(c) == 0) throw ""; + <qt1, ex1> = handleCmd(c); + return <qt, ex1>; + } catch : { + v = "<txt>"; + return <qt, v>; + } + } + } + + tuple[str quote, str execute] handle(TokenOrCmd toc){ + if(toc is aCmd){ + return handleCmd(toc.aCmd); + } else { + v = "<toc>"; + return <v, v>; + } + } + + tuple[str quote, str execute] handle(TokenOrCmdList tocList){ + if(appl(_,list[Tree] args) := tocList) { + qt = ""; + ex = ""; + if(tocList is parens){ + qt += "(<args[1]>"; ex += "(<args[1]>"; + <qt1, ex1> = handle(tocList.tocList); + qt += "<qt1><args[3]>)"; ex += "<ex1><args[3]>)"; + if(TokenOrCmdList toc2 <- tocList.optTocList){ + qt += "<args[5]>"; ex += "<args[5]>"; + <qt2, ex2> = handle(toc2); + qt += qt2; ex += ex2; + } + } else { + <qt, ex> = handle(tocList.toc); + if(TokenOrCmdList toc2 <- tocList.optTocList){ + qt += "<args[1]>"; ex += "<args[1]>"; + <qt1, ex1> = handle(toc2); + qt += qt1; ex += ex1; + } + } + return <qt, ex>; + } else { + throw "Cannot match parse tree: <tocList>"; + } + } + + <qt, ex> = handle(q); + return <qt, ex, nholes > 0, env>; +} + +str preprocessClick(int _, TokenOrCmdList q){ + nholes = 0; + + tuple[str quote, str execute] handleCmd((Cmd) `<ClickCmd cc>`){ + nholes += 1; + txt = "<cc.elements>"; + qt = clickMarkup(nholes, txt); + return <qt, qt>; + } + + tuple[str quote, str execute] handle(TokenOrCmd toc){ + if(toc is aCmd){ + return handleCmd(toc.aCmd); + } else { + v = "<toc>"; + return <v, v>; + } + } + + tuple[str quote, str execute] handle(TokenOrCmdList tocList){ + if(appl(_,list[Tree] args) := tocList) { + qt = ""; + ex = ""; + if(tocList is parens){ + qt += "(<args[1]>"; ex += "(<args[1]>"; + <qt1, ex1> = handle(tocList.tocList); + qt += "<qt1><args[3]>)"; ex += "<ex1><args[3]>)"; + if(TokenOrCmdList toc2 <- tocList.optTocList){ + qt += "<args[5]>"; ex += "<args[5]>"; + <qt2, ex2> = handle(toc2); + qt += qt2; ex += ex2; + } + } else { + <qt, ex> = handle(tocList.toc); + if(TokenOrCmdList toc2 <- tocList.optTocList){ + qt += "<args[1]>"; ex += "<args[1]>"; + <qt1, ex1> = handle(toc2); + qt += qt1; ex += ex1; + } + } + return <qt, ex>; + } else { + throw "Cannot match parse tree: <tocList>"; + } + } + + <qt, ex> = handle(q); + return qt; +} + +int questionId = 0; + +str removeComments(Intro? intro){ + res = ""; + for(line <- split("\n", "<intro>")){ + if(!startsWith(line, "//")){ + res += line + "\n"; + } + } + + return res; +} + +public str compileQuestions(loc qloc, PathConfig pcfg) { + pcfg = pathConfig(srcs=[|test-modules:///|]+pcfg.srcs,libs=pcfg.libs,bin=pcfg.bin,courses=pcfg.courses); + return process(qloc, pcfg); +} + +str process(loc qloc, PathConfig pcfg){ + iqs = parse(qloc); + questionId = 0; + + bn = qloc.file; + if(/^<b:.*>\..*$/ := qloc.file) bn = b; + + res = "# <bn> + ' + '++++ + '\<script src=\"http:///code.jquery.com/jquery-3.1.1.js\"\>\</script\> + '\<script type=\"text/javascript\" src=\"https://code.jquery.com/ui/1.11.4/jquery-ui.min.js\"\>\</script\> + '++++ + '"; + for (iq <- iqs.introAndQuestions) { + intro = removeComments(iq.intro); + res += (intro + "\n" + process("<iq.description>", iq.question, pcfg) +"\n"); + } + res += " + '++++ + '\<script src=\"tutor-prelude.js\"\>\</script\> + '++++ + '"; + return res; +} + +// ---- CodeQuestion + +str process(str text, (Question) `<CodeQuestion q>`, PathConfig pcfg){ + prep_quoted = prep_executed = ""; + prep_holes = false; + env1 = (); + questionId += 1; + + if(Prep p <- q.prep){ + <prep_quoted, prep_executed, prep_holes, env1> = preprocessCode(questionId, p.text, "", (), pcfg); + } + + expr_quoted = expr_executed = ""; + expr_holes = false; + env2 = env1; + if(Expr e <- q.expr){ + <expr_quoted, expr_executed, expr_holes, env2> = preprocessCode(questionId, e.text, prep_executed, env1, pcfg); + return codeQuestionMarkup(questionId, text, + "module Question<questionId> + '<prep_quoted><"<prep_quoted>" == "" ? "" : "\n"> + 'test bool <e.name>() = + ' <expr_quoted> == <expr_holes ? eval(questionId, expr_executed, prep_executed, pcfg) : holeMarkup(1)>; + '"); + } + if(prep_holes){ + runTests(questionId, prep_executed, pcfg); + return codeQuestionMarkup(questionId, text, "module Question<questionId> + '<prep_quoted> + "); + } + throw "Incorrect question: no expr given and no holes in prep code"; +} + +str replaceHoles(str code){ + return replaceAll(visit(code){ + case /^\+\+\+[^\+]+\+\+\+/ => "_" + }, "\n", "\\n"); +} + +str escape(str code){ + return replaceAll(code, "\"", """); +} + +str removeSpacesAroundHoles(str code){ + return visit(code){ + case /^[ ]+\+\+\+/ => " +++" + case /^\+\+\+[ ]+/ => "+++ " + }; +} + +str codeQuestionMarkup(int n, str text, str code){ + return ".Question <n> + '<text> + '++++ + '\<div id=\"Question<n>\" + ' class=\"code-question\" + ' listing=\"<escape(replaceHoles(code))>\"\> + '++++ + '[source,rascal,subs=\"normal\"] + '---- + '<removeSpacesAroundHoles(code)> + '---- + '++++ + '\</div\> + '++++"; +} + +// ---- ChoiceQuestion + +str process(str text, (Question) `<ChoiceQuestion q>`, PathConfig pcfg){ + questionId += 1; + return choiceQuestionMarkup(questionId, text, q.choices); +} + +str choiceQuestionMarkup(int n, str explanation, Choice* choices){ + return ".Question <n> + '<explanation> + '++++ + '\<div id=\"Question<n>\" + ' class=\"choice-question\"\> + '<for(ch <- choices){> + ' \<input type=\"radio\" class=\"choice-input\" name=\"Question<n>\" value=\"<ch.correct>\" feedback=\"<trim("<ch.feedback>")>\"\> + ' \<div class=\"choice-description\"\> <ch.description>\</div\> + '<}> + '\</div\> + '++++ + '"; +} + +// ---- ClickQuestion + +str process(str explanation, (Question) `<ClickQuestion q>`, PathConfig pcfg){ + questionId += 1; + qt = preprocessClick(questionId, q.text); + + return clickQuestionMarkup(questionId, explanation, qt); +} + +str clickQuestionMarkup(int n, str explanation, str code){ + return ".Question <n> + '<explanation> + '++++ + '\<div id=\"Question<n>\" + ' class=\"click-question\"\> + '++++ + '[source,rascal,subs=\"normal\"] + '---- + '<code> + '---- + '++++ + '\</div\> + '++++ + '"; +} + +// ---- MoveQuestion + +str process(str explanation, (Question) `<MoveQuestion q>`, PathConfig pcfg){ + questionId += 1; + if(Decoy d <- q.decoy){ + return moveQuestionMarkup(questionId, explanation, "<q.text>", "<d.text>"); + } else { + return moveQuestionMarkup(questionId, explanation, "<q.text>", ""); + } +} + +data Fragment = fragment(int index, list[str] lines, int pre, int post); + +int indent(str s) = /^<a:[ ]*>/ := s ? size(a) : 0; + +list[list[str]] makeSegments(list[str] lines){ + list[list[str]] segments = []; + if(any(str line <- lines, startsWith(line, "---"))){ + cur = []; + for(str line <- lines){ + if(startsWith(line, "---")){ + segments += [cur]; + cur = []; + } else { + cur += [line]; + } + } + if(size(cur) > 0){ + segments += [cur]; + } + } else { + for(int i <- index(lines)){ + if(i + 2 < size(lines)){ + segments += [lines[i .. i + 2]]; + } else { + segments += [lines[i ..]]; + } + } + } + return segments; +} + +str moveQuestionMarkup(int n, str explanation, str code, str decoy){ + code_lines = split("\n", code); + segments = makeSegments(code_lines); + fragments = [fragment(i, segments[i], indent(segments[i][0]), indent(segments[i][-1])) | i <- index(segments)]; + + decoy_fragments = []; + decoy_lines = split("\n", decoy); + if(size(decoy) > 0){ + decoy_segments = makeSegments(decoy_lines); + decoy_fragments = [fragment(-1, decoy_segments[i], 0, 0) | i <- index(decoy_segments)]; + } + + gcode = ""; + ftop = -260; + initialIndent = fragments[0].pre; + + for(f <- shuffle(fragments + decoy_fragments)){ + + minIndent = min(f.pre, f.post); + flines = ""; + for(line <- f.lines){ + flines += line[minIndent..] + "\n"; + } + gcode += "\<div id=\"box-<f.index>\" class=\"movable-code\" index=\"<f.index>\" indent=\"<(f.pre - initialIndent)/2>\" style=\"position:relative;top:<ftop>px;\"\> + '\<pre\>\<code\>" + + + "<flines>\</code\>\</pre\>\</div\>\n"; + ftop += size(f.lines) * 9; + } + id = "Question<n>"; + return + ".Question <n> + '<explanation> + '++++ + '\<div id=\"<id>\" + ' class=\"move-question\"\> + '\<div id=\"movable-code-src-<id>\" class=\"movable-code-src\" \"lines=\"<size(code_lines + decoy_lines)>\"\> + '\<div id=\"movable-code-target-<id>\" class=\"movable-code-target\"\> + '\</div\> + " + + gcode + + + "\</div\> + '\</div\> + '++++ + '"; + + // '\<form id=\"movable-code-form-<id>\" class=\"movable-code-form\"\> + //'\<input type=\"submit\" value=\"Submit Answer\"\> + // '\</form\> +} + +// ---- FactQuestion +str process(str explanation, (Question) `<FactQuestion q>`, PathConfig pcfg){ + questionId += 1; + return factQuestionMarkup(questionId, explanation, q.facts); +} + +str factQuestionMarkup(int n, str explanation, Fact+ facts){ + s1 = []; + s2 = []; + + afacts = [f | f <- facts]; + + for(int i <- index(afacts)){ + fact = afacts[i]; + s1 += ["\<li index=\"<i>\" class=\"fact-item\"\>\<tt\><trim("<fact.leftText>")>\</tt\>\</li\>\n"]; + s2 += ["\<li index=\"<i>\" class=\"fact-item\"\>\<tt\><trim("<fact.rightText>")>\</tt\>\</li\>\n"]; + } + + id = "Question<n>"; + return + ".Question <n> + '<explanation> + '++++ + '\<div id=\"<id>\" class=\"fact-question\"\> + '\<ul id=\"sortable1-<id>\" class=\"sortableLeft\"\> + '<intercalate("\n", shuffle(s1))> + '\</ul\> + '\<ul id=\"sortable2-<id>\" class=\"sortableRight\"\> + '<intercalate("\n", shuffle(s2))> + '\</ul\> + '\</div\> + '++++ + '"; +} + +// ---- + + +loc makeQuestion(int questionId, PathConfig pcfg){ + for(f <- pcfg.bin.ls){ + if(/Question/ := "<f>"){ + remove(f); + } + } + mloc = |test-modules:///| + "Question<questionId>.rsc"; + return mloc; +} + +value eval(int questionId, str exp, str setup, PathConfig pcfg) { + Q = makeQuestion(questionId, pcfg); + msrc = "module Question<questionId> + ' + '<setup> + ' + 'value main() { + ' return <exp>; + '}"; + + writeFile(Q, msrc); + + try { + if (result(value res) := eval(#value, ["import Question<questionId>;", "main();"])) { + return res; + } + else { + throw "evaluation of <exp> failed"; + } + } catch e:{ + println("*** While evaluating <exp> in + ' <msrc> + '*** the following error occurred: + ' <e>"); + throw "Error while evaluating expression <exp>"; + } +} + +void runTests(int questionId, str mbody, PathConfig pcfg){ + Q = makeQuestion(questionId, pcfg); + msrc = "module Question<questionId> <mbody>"; + writeFile(Q, msrc); + try { + if (result(false) == eval(#bool, ["import Question<questionId>;", ":test"])) { + throw "some test failed"; + } + } catch value e: { + println("*** While running tests for + ' <msrc> + '*** the following error occurred: + ' <e>"); + throw "Error while running tests"; + } +} + diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/Questions.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/Questions.rsc new file mode 100644 index 00000000000..d3f0408338a --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/Questions.rsc @@ -0,0 +1,253 @@ +module lang::rascal::tutor::questions::Questions + +import ParseTree; + +// Syntax of the Question language + +lexical LAYOUT = [\ \t \n]; + +layout Layout = LAYOUT* !>> [\ \t \n]; + +start syntax Questions = IntroAndQuestion+ introAndQuestions; + +syntax IntroAndQuestion + = Intro? intro "question" Tokens description Question question "end"; + +syntax Question + = CodeQuestion + | ChoiceQuestion + | ClickQuestion + | MoveQuestion + | FactQuestion + ; + +syntax CodeQuestion + = Prep? prep Expr? expr; + +syntax Prep + = "prep" TokenOrCmdList text; + +syntax Expr + = "expr" Name name TokenOrCmdList text; + +syntax ChoiceQuestion + = Choice+ choices; + +syntax Choice + = "choice" YesOrNo correct "|||" Tokens description "|||" Tokens feedback + ; + +lexical YesOrNo = "y" | "n"; + +syntax ClickQuestion + = "clickable" TokenOrCmdList text; + +syntax MoveQuestion + = "movable" Tokens text Decoy? decoy; + +syntax Decoy + = "decoy" Tokens text; + +syntax FactQuestion + = Fact+ facts; + +syntax Fact + = "fact" Tokens leftText "|||" Tokens rightText; + +keyword Reserved + = "question" + | "prep" + | "expr" + | "end" + | "choice" + | "clickable" + | "movable" + | "decoy" + | "fact" + | "$answer" + | "$gen" + | "$use" + | "$eval" + | "$click" + ; + +lexical Name = [$]? [A-Za-z] [A-Z a-z 0-9 _]* !>> [A-Z a-z 0-9 _]; + +lexical IntCon = "-"?[0-9]+ !>> [0-9]; + +syntax Intro = AnyText+ !>> "question"; + +lexical AnyText = Name \ Reserved | IntCon | ![$ a-z A-Z 0-9 \ \t \n] ; + +lexical AnyButSpecial = Name \ Reserved | IntCon | ![$ ( ) \\ a-z A-Z 0-9 \t \n] ; + +lexical Escaped + = "\\" ( "(" | ")" | "$" | Reserved) + ; + +syntax Cmd + = AnswerCmd + | GenCmd + | UseCmd + | EvalCmd + | ClickCmd + ; +syntax AnswerCmd + = "$answer" "(" TokenOrCmdList elements ")" + ; +syntax GenCmd + = "$gen" "(" Type type ")" + | "$gen" "(" Type type "," Name name ")" + ; +syntax UseCmd + = "$use" "(" Name name ")" + ; +syntax EvalCmd + = "$eval" "(" TokenOrCmdList elements ")" + ; +syntax ClickCmd + = "$click" "(" Tokens elements ")" + ; + +syntax Token + = Escaped + > AnyButSpecial + ; + +syntax Tokens + = "(" Tokens ")" Tokens? + | Token Tokens? + ; + +syntax TokenOrCmd + = aCmd: Cmd aCmd + > aToken: Token aToken + ; + +syntax TokenOrCmdList + = parens: "(" TokenOrCmdList tocList ")" TokenOrCmdList? optTocList + | noparens: TokenOrCmd toc TokenOrCmdList? optTocList + ; + +keyword TypeNames + = "bool" + | "int" + | "real" + | "num" + | "str" + | "loc" + | "datetime" + | "list" + | "set" + | "map" + | "tuple" + | "rel" + | "lrel" + | "value" + | "void" + | "arb" + ; + +syntax Type + = "bool" + | "int" + | "int" Range range + | "real" + | "real" Range range + | "num" + | "num" Range range + | "str" + | "loc" + | "datetime" + | "list" "[" Type elemType "]" + | "list" "[" Type elemType "]" Range range + | "set" "[" Type elemType "]" + | "set" "[" Type elemType "]" Range range + | "map" "[" Type keyType "," Type valType "]" + | "map" "[" Type keyType "," Type valType "]" Range range + | "tuple" "[" {Type ","}+ elemTypes "]" + | "tuple" "[" {Type ","}+ elemTypes "]" Range range + | "rel" "[" {Type ","}+ elemTypes "]" + | "lrel" "[" {Type ","}+ elemTypes "]" + | "value" + | "void" + | "arb" "[" IntCon depth "," {Type ","}+ elemTypes "]" + ; + +syntax Range = "[" IntCon min "," IntCon max "]" range; + +public Questions parse(str src) = parse(#start[Questions], src).top; + +value pmain(){ +/* + +"123, 456 + 'question Replace text box by a function name and make the test true. + 'prep import List; + 'expr listFunction $answer(headTail)($gen(list[int][1,10])) + 'end + + 'question Replace text box by the result of the intersection and make the test true. + 'expr setIntersection $eval($gen(set[int]) + $gen(set[int],B)) & $eval($gen(set[int]) + $use(B)) + 'end + ' $abc ( + 'question Which means of transportation is faster + choice Apache Helicopter + correct no + feedback The speed of an Apache is 293 km/hour + choice High-speed train + correct yes + feedback The speed of high-speed train is 570 km/hour + choice Ferrari F430 + correct no + feedback The speed of a Ferrari is 315 km/hour + choice Hovercraft + correct no + feedback The speed of a Hovercraft is 137 km/hour + end + question Click on all identifiers in this code fragment: + 'clickable + $click(x) = 1; + $click(y) = $click(x) + 2; + 'end" + */ + +return parse("123, 456 + 'question Replace text box by a function name and make the test true. + 'prep import List; + 'expr listFunction $answer(headTail)($gen(list[int][1,10])) + 'end + + 'question Replace text box by the result of the intersection and make the test true. + 'expr setIntersection $eval($gen(set[int]) + $gen(set[int],B)) & $eval($gen(set[int]) + $use(B)) + 'end + ' $abc ( + 'question Which means of transportation is faster + choice Apache Helicopter + correct no + feedback The speed of an Apache is 293 km/hour + choice High-speed train + correct yes + feedback The speed of high-speed train is 570 km/hour + choice Ferrari F430 + correct no + feedback The speed of a Ferrari is 315 km/hour + choice Hovercraft + correct no + feedback The speed of a Hovercraft is 137 km/hour + end + question Click on all identifiers in this code fragment: + 'clickable + $click(x) = 1; + $click(y) = $click(x) + 2; + 'end" +/* + "question[code] Replace text box by a function name and make the test true. + ' prep import List; + ' expr listFunction: $answer[headTail]($gen[list[int][1,10]]) + 'end + 'question[code] Replace text box by the result of the intersection and make the test true. + ' expr setIntersection: $eval[ $gen[set[int]] + $gen[set[int],B] } & $eval[$gen[set[int]] + $use[B]] ] + 'end" + */); +} diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/SavedQuestions.txt b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/SavedQuestions.txt new file mode 100644 index 00000000000..65e54b63b10 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/SavedQuestions.txt @@ -0,0 +1,1769 @@ +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/Concatenation/Concatenation.concept| +QChoice: When you compute the concatenation of two lists, the number of elements in the result is always: +b: Greater than the sum of the number of elements in both lists. +g: Greater than or equal to the sum of the number of elements in both lists. +b: Smaller than the sum of the number of elements in both lists. +g: Equal to the sum of the number of elements in both lists. + +QType: <A:list[arb[int,str]]> + <B:same[A]> + +QValue: <A:list[arb[int,str]]> + <B:same[A]> + +QValue: +make: A = list[arb[int,str]] +make: B = same[A] +expr: C = <A> + <B> +hint: <B> +test: <A> + <?> == <C> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/notin/notin.concept| +QType: <A:arb[int,str,bool]> notin <B:list[same[A]]> + + +QValue: +prep: import Set; +make: ELM = int[0,100] +make: A = set[same[ELM]] +expr: A1 = toList( {<ELM>} + <A>) +expr: C = <ELM> notin <A1> +hint: <C> +test: <ELM> notin <A1> == <?> + +QValue: +prep: import Set; +make: ELM = int[0,10] +make: A = list[same[ELM]] +expr: C = <ELM> notin <A> +hint: <C> +test: <ELM> notin <A> == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/ListRelation/Composition/Composition.concept| +QType: +prep: import List; +make: C = list[int,3,3] +make: A = list[int,3,3] +make: B = list[int,3,3] +expr: S1 = zip(<A>,<C>) +expr: S2 = zip(<C>, <B>) +test: <S1> o <S2> + +QValue: +prep: import List; +make: C = list[int,3,3] +make: A = list[int,3,3] +make: B = list[int,3,3] +expr: S1 = zip(<A>,<C>) +expr: S2 = zip(<C>, <B>) +expr: H = <S1> o <S2> +hint: <H> +test: <S1> o <S2> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Relation/Composition/Composition.concept| +QType: +prep: import List; +make: C = list[int,3,3] +make: A = list[int,3,3] +make: B = list[int,3,3] +expr: S1 = toSet(zip(<A>,<C>)) +expr: S2 = toSet(zip(<C>, <B>)) +test: <S1> o <S2> + +QValue: +prep: import List; +make: C = list[int,3,3] +make: A = list[int,3,3] +make: B = list[int,3,3] +expr: S1 = toSet(zip(<A>,<C>)) +expr: S2 = toSet(zip(<C>, <B>)) +expr: H = <S1> o <S2> +hint: <H> +test: <S1> o <S2> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/SuperList/SuperList.concept| +QType: <A:list[arb]> >= <B:same[A]> + + +QValue: +prep: import Set; +make: DIFF = set[int[0,100]] +make: B = same[DIFF] +expr: A = toList(<B> + <DIFF>) +expr: B1 = toList(<B>) +expr: C = <A> >= <B1> +hint: <C> +test: <A> >= <B1> == <?> + +QValue: +make: A = list[arb[int,str,bool]] +expr: C = <A> >= <A> +hint: <C> +test: <A> >= <A> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Number/Equal/Equal.concept| +QType: <A:int> == <B:int> +QType: <A:real> == <B:real> +QType: <A:num> == <B:num> +QType: <A:num> == <B:num> +QValue: <A:num> == <B:num> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Number/Subtraction/Subtraction.concept| +QType: <A:int> - <B:int> +QType: <A:int> - <B:real> +QType: <A:real> - <B:int> +QValue: <A:num> - <B:num> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Map/Map.concept| +QChoice: A map maps keys to values. In a map: +g: All keys have to be unique. +g: All keys have the same type. +g: All keys should have the same type and all values should have the same type. The type of keys and values may be different. +b: All keys should have the same type and all values should have the same type. The type of keys and values should be the same. +b: All values have to be unique. +b: All keys and values have to be unique. +b: All keys are sorted. + +QValue: +desc: Complete this function that returns the set of keys with the smallest associated value. +list: +import Map; +import Set; +inventory = ("orange" : 20, "apple" : 15, "banana" : 25, "lemon" : 15); +public set[str] lowest(map[str,int] inv){ + m = <?>; // Determine the minimal value in the map + return { s | s <- inv, inv[s] == m }; +} +test: lowest(inventory) == {"apple", "lemon"}; +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Patterns/Abstract/List/List.concept| +QValue: +desc: Complete this function that tests that a list of words consists of two identical sublists: +list: +import List; +public bool isReplicated(list[str] words){ + return [*str L, <?>] := words; +} +test: isReplicated(["a", "b", "a", "b"]) == true; +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Number/Remainder/Remainder.concept| +QType: <A:int> % <B:int> +QValue: <A:int> % <B:int> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Libraries/Prelude/List/List.concept| +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],4,6] +make: I = int[0,3] +expr: E = <L>[<I>] +expr: H = indexOf(<L>, <E>) +hint: indexOf +test: <?>(<L>, <E>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: E = arb[int,str] +make: L = list[same[E],4,6] +make: I = int[0,3] +expr: H = insertAt(<L>, <I>, <E>) +hint: insertAt +test: <?>(<L>, <I>, <E>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[int,0,5] +expr: H = intercalate(";", <L>) +hint: intercalate +test: <?>(";", <L>) == <H> + + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],0,5] +expr: H = index(<L>) +hint: index +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],1,6] +expr: H = head(<L>) +hint: head +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str], 1, 6] +expr: H = last(<L>) +hint: last +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str]] +expr: H = toSet(<L>) +hint: toSet +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[bool,int,str],4,6] +make: N = int[0,3] +expr: H = take(<N>, <L>) +hint: take +test: <?>(<N>, <L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[bool,int,str],4,6] +make: N = int[0,3] +expr: H = tail(<L>,<N>) +hint: tail +test: <?>(<L>,<N>) == <H> + + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],1,5] +expr: H = headTail(<L>) +hint: Use headTail. +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[bool,int,str],4,6] +make: I = int[0,3] +make: J = int[0,3] +expr: L1 = [<L>[<I>], <L>[<J>], *<L>, <L>[<J>], <L>[<I>]] +expr: H = dup(<L1>) +hint: dup +test: <?>(<L1>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[bool,int,str],2,5] +expr: H = getOneFrom(<L>) +hint: getOneFrom +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[bool,int,str],3,4] +make: N = int[0,2] +expr: H = drop(<N>, <L>) +hint: drop +test: <?>(<N>, <L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],0,5] +expr: H = reverse(<L>) +hint: reverse +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],3,4] +make: I = int[0,2] +expr: E = <L>[<I>] +expr: L1 = reverse(<L>) + <L> +expr: H = lastIndexOf(<L1>, <E>) +hint: lastIndexOf +test: <?>(<L1>, <E>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],0,5] +expr: H = index(<L>) +hint: index +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],1,5] +expr: H = pop(<L>) +hint: pop +test: <?>(<L>) == <H> + + +QValue: +prep: import List; +make: L = list[arb[int,str], 1, 6] +expr: H = max(<L>) +hint: max +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[int[-20,20]] +expr: H = takeWhile(<L>, bool(int x){ return x > 0;}) +hint: takeWhile +test: <?>(<L>, bool(int x){ return x > 0;}) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],3,4] +make: I = int[0,2] +expr: C = delete(<L>, <I>) +hint: delete +test: <?>(<L>, <I>) == <C> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[int,2,7] +expr: H = sum(<L>) +hint: sum +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[int, 4, 4] +make: M = list[int, 4, 4] +expr: Z = zip(<L>,<M>) +hint: zip +test: <?>(<L>, <M>) == <Z> + + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],1,5] +expr: H = sort(<L>) +hint: sort +test: <?>(<L>) == <H> + + +QValue: +prep: import List; +make: L = list[int, 1,5] +expr: H = mapper(<L>, int(int n){ return n + 1; }) +hint: mapper +list: +int incr(int x) { return x + 1; } +test: <?>(<L>, incr) == <H> + +QValue: +desc: Complete this function that tests that a list of words forms a palindrome. A palindrome is a word that is symmetrical +and can be read +from left to right and from right to left. +list: +import List; +public bool isPalindrome(list[str] words){ + return words == <?>; +} +test: isPalindrome(["a", "b", "b", "a"]) == true; +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Number/Conditional/Conditional.concept| +QType: (<A:int> > <B:int>) ? <C:int> : <D:int> + +QType: (<A:int> > <B:int>) ? <C:real> : <D:real> + +QType: (<A:int> > <B:int>) ? <C:int> : <D:real> + +QValue: (<A:int> > <B:int>) ? <C:int> : <D:int> + +QValue: (<A:int> > <B:int>) ? <C:int> : <D:real> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/Equal/Equal.concept| +QType: <A:set[arb]> == <B:same[A]> + +QValue: +make: A = set[arb[int[0,10],str]] +expr: C = <A> == <A> +hint: <C> +test: (<A> == <A>) == <?> + +QValue: +make: A = set[int[0,100]] +make: DIFF = int +expr: B = <A> + (<DIFF>) +expr: C = <A> == <B> +hint: <C> +test: (<A> == <B>) == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/Comprehension/Comprehension.concept| +QType: +make: I = int[0,5] +make: J = int[7,12] +test: { N + 1 | int N <- [<I> .. <J>] } + +QValue: +make: I = int[0,5] +make: J = int[7,12] +expr: H = { N + 1 | int N <- [<I> .. <J>] } +hint: <H> +test: { N + 1 | int N <- [<I> .. <J>] } == <?> + +QValue: +make: S = set[int] +expr: H = { 2 * N | int N <- <S> } +hint: <H> +test: { 2 * N | int N <- <S> } == <?> + +QValue: +desc: Complete this comprehension: +make: S = set[int] +expr: H = { N - 1 | int N <- <S> } +hint: { N - 1 | int N <- <S> } +test: { <?> | int N <- <S> } == <H> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Integer/Integer.concept| +QType: <A:int> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Number/LessThanOrEqual/LessThanOrEqual.concept| +QType: <A:int> <= <B:int> +QType: <A:real> <= <B:real> +QType: <A:num> <= <B:num> +QType: <A:num> <= <B:num> +QValue: <A:num> <= <B:num> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/ListRelation/Subscription/Subscription.concept| +QValue: +desc: Using the above example with GDP values: +prep: lrel[str country, int year, int amount] GDP = [<"US", 2008, 14264600>, <"EU", 2008, 18394115>,<"Japan", 2008, 4923761>, <"US", 2007, 13811200>, <"EU", 2007, 13811200>, <"Japan", 2007, 4376705>]; +expr: H = GDP["US"] +hint: <H> +test: GDP["US"] == <?> + +QValue: +desc: Using the above example with GDP values: +prep: lrel[str country, int year, int amount] GDP = [<"US", 2008, 14264600>, <"EU", 2008, 18394115>,<"Japan", 2008, 4923761>, <"US", 2007, 13811200>, <"EU", 2007, 13811200>, <"Japan", 2007, 4376705>]; +expr: H = GDP["US",2008] +hint: <H> +test: GDP["US",2008] == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/ListRelation/FieldSelection/FieldSelection.concept| +QValue: +hint: A list consisting of the first element of each tuple. +list: +lrel[str animal, int nlegs] legs = [<"bird", 2>, <"dog", 4>, <"human", 2>, <"snake", 0>, <"spider", 8>, <"millepede", 1000>, <"crab", 8>, <"cat", 4>]; +test: legs.animal == <?> + +QValue: +hint: A list consisting of the second element of each tuple. +list: +lrel[str animal, int nlegs] legs = [<"bird", 2>, <"dog", 4>, <"human", 2>, <"snake", 0>, <"spider", 8>, <"millepede", 1000>, <"crab", 8>, <"cat", 4>]; +test: legs.nlegs == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/ListRelation/ReflexiveTransitiveClosure/ReflexiveTransitiveClosure.concept| +QType: +prep: S = [<1,2>, <2,3>]; +make: A = list[tuple[int[0,5],int[0,5]],1,2] +expr: B = <A> + S +test: <B>* + +QValue: +prep: S = [<1,2>, <2,3>, <3,4>]; +make: A = list[tuple[int[0,5],int[0,5]],1,2] +expr: B = <A> + S +expr: H = <B>* +hint: <H> +test: <B>* +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/ListRelation/Join/Join.concept| +QType: <A:list[tuple[int,str]]> join <B:list[tuple[str,int]]> +QValue: <A:list[tuple[int,str],2,2]> join <B:list[tuple[str,int],2,2]> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Visit/Visit.concept| +QValue: +desc: Given a data type `ColoredTree`, complete the definition of the function `flipRedChildren` that exchanges the children of all red nodes. +list: +data ColoredTree = leaf(int N) + | red(ColoredTree left, ColoredTree right) + | black(ColoredTree left, ColoredTree right); + +ColoredTree rb = red(black(leaf(1), red(leaf(2),leaf(3))), black(leaf(3), leaf(4))); + +public ColoredTree flipRedChildren(ColoredTree t){ + return visit(t){ + case red(l,r) => <?> + }; +} +test: flipRedChildren(rb) == red( black(leaf(3), leaf(4)), black(leaf(1), red(leaf(3),leaf(2)))); +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/ListRelation/CartesianProduct/CartesianProduct.concept| +QType: +make: S1 = list[int,2,3] +make: S2 = list[int,2,3] +test: <S1> * <S2> + +QValue: +make: S1 = list[int,2,3] +make: S2 = list[int,2,2] +expr: H = <S1> * <S2> +hint: <H> +test: <S1> * <S2> == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Relation/Relation.concept| +QChoice: A relation: +g: Is a set of tuples. +b: Is a list of tuples. +b: Is a tuple of tuples. +b: Has ordered elements. +b: Can contain duplicates. +b: Has a fixed length. +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/ListRelation/ListRelation.concept| +QChoice: A relation: +g: Is a set of tuples. +b: Is a list of tuples. +b: Is a tuple of tuples. +b: Has ordered elements. +b: Can contain duplicates. +b: Has a fixed length. +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/List.concept| +QChoice: The type of a list is determined by: +g: The least upper bound of the type of all elements. +g: The types of all the elements in the list. +b: The type of the element that was first added to the list. +b: The average of the type of the elements with the smallest and the largest type. +b: The least upper bound of the type of two arbitrary elements. +b: The type of two arbitrary elements. + + +QValue: +desc: Fill in the missing operator. +make: B = arb[int[0,100],str] +make: A = list[same[B]] +expr: C = <A> + <B> +hint: Use +. +test: <A> <?> <B> == <C> + +QValue: +desc: Fill in the missing operator. +prep: import Set; +make: DIFF = set[int[0,100]] +make: A = same[DIFF] +make: B = same[DIFF] +expr: A1 = toList(<DIFF> + <A>) +expr: B1 = toList(<B> + <DIFF>) +expr: C = <A1> - <B1> +hint: Use -. +test: <A1> <?> <B1> == <C> + +QValue: +desc: Fill in the missing operator. +prep: import List; +make: A = list[int[0,100]] +expr: B = reverse(<A>) +expr: C = <A> == <B> +hint: Use ==. +test: (<A> <?> <B>) == <C> + +QValue: +desc: Fill in the missing operator. +prep: import Set; +make: DIFF = set[int[0,100],str] +make: A = same[DIFF] +make: B = same[DIFF] +expr: A1 = toList(<DIFF> + <A>) +expr: B1 = toList(<B> + <DIFF>) +expr: C = <A1> & <B1> +hint: Use &. +test: <A1> <?> <B1> == <C> + +QValue: +desc: Fill in the missing operator. +prep: import Set; +make: ELM = int[0,100] +make: A = set[same[ELM]] +expr: A1 = toList( {<ELM>} + <A>) +expr: C = <ELM> in <A1> +hint: Use in. +test: <ELM> <?> <A1> == <C> + +QValue: +desc: Fill in the missing operator. +prep: import Set; +make: DIFF = set[int[0,100]] +make: A = same[DIFF] +expr: A1 = toList(<A>) +expr: B = toList(<A> + <DIFF>) +expr: C = <A1> < <B> +hint: <C> +test: <A1> <?> <B> == <C> + + +QValue: +desc: Fill in the missing operator. +make: A = list[arb[int,str]] +make: B = same[A] +expr: C = <A> + <B> +hint: Use +. +test: <A> <?> <B> == <C> + +QValue: +desc: Fill in the missing operator. +prep: import Set; +make: ELM = int[0,10] +make: A = list[same[ELM]] +expr: C = <ELM> notin <A> +hint: Use notin. +test: <ELM> <?> <A> == <C> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Real/Real.concept| +QType: <A:real> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Tutor/Markup/QuestionMarkup/Text/Text.concept| +QText[Taller]: What is taller, the Eiffel Tower or the Empire State Building? +a: Empire +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Tutor/Markup/QuestionMarkup/Type/Type.concept| +QType: <A:set[int]> + +QType: <A:set[arb[int,str,real]]> + <B:same[A]> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Relation/FieldSelection/FieldSelection.concept| +QValue: +hint: A set consisting of the first element of each tuple. +list: +rel[str animal, int nlegs] legs = {<"bird", 2>, <"dog", 4>, <"human", 2>, <"snake", 0>, <"spider", 8>, <"millepede", 1000>, <"crab", 8>, <"cat", 4>}; +test: legs.animal == <?> + +QValue: +hint: A set consisting of the second element of each tuple. +list: +rel[str animal, int nlegs] legs = {<"bird", 2>, <"dog", 4>, <"human", 2>, <"snake", 0>, <"spider", 8>, <"millepede", 1000>, <"crab", 8>, <"cat", 4>}; +test: legs.nlegs == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Relation/ReflexiveTransitiveClosure/ReflexiveTransitiveClosure.concept| +QType: +prep: S = {<1,2>, <2,3>}; +make: A = set[tuple[int[0,5],int[0,5]],1,2] +expr: B = <A> + S +test: <B>* + +QValue: +prep: S = {<1,2>, <2,3>, <3,4>}; +make: A = set[tuple[int[0,5],int[0,5]],1,2] +expr: B = <A> + S +expr: H = <B>* +hint: <H> +test: <B>* +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Relation/Join/Join.concept| +QType: <A:set[tuple[int,str]]> join <B:set[tuple[str,int]]> +QValue: <A:set[tuple[int,str],2,2]> join <B:set[tuple[str,int],2,2]> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Reducer/Reducer.concept| +QValue: +desc: Return the set of largest words. +list: +import Number; +import String; +text = ["Quote", "from", "Steve", "Jobs", ":", "And", "one", "more", "thing"]; +public list[str] largest(list[str] text){ + mx = ( 0 | max(it, size(s)) | s <- text ); + return + for(s <- text) + if(<?>) + append s; +} +test: largest(text) == ["Quote", "Steve", "thing"]; +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Relation/Subscription/Subscription.concept| +QValue: +desc: Using the above example with GDP values: +prep: rel[str country, int year, int amount] GDP = {<"US", 2008, 14264600>, <"EU", 2008, 18394115>,<"Japan", 2008, 4923761>, <"US", 2007, 13811200>, <"EU", 2007, 13811200>, <"Japan", 2007, 4376705>}; +expr: H = GDP["US"] +hint: <H> +test: GDP["US"] == <?> + +QValue: +desc: Using the above example with GDP values: +prep: rel[str country, int year, int amount] GDP = {<"US", 2008, 14264600>, <"EU", 2008, 18394115>,<"Japan", 2008, 4923761>, <"US", 2007, 13811200>, <"EU", 2007, 13811200>, <"Japan", 2007, 4376705>}; +expr: H = GDP["US",2008] +hint: <H> +test: GDP["US",2008] == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/SubList/SubList.concept| +QType: <A:list[arb]> <= <B:same[A]> + +QValue: +prep: import Set; +make: DIFF = set[int[0,100]] +make: A = same[DIFF] +expr: A1 = toList(<A>) +expr: B = toList(<A> + <DIFF>) +expr: C = <A1> <= <B> +hint: <C> +test: <A1> <= <B> == <?> + +QValue: +make: A = list[arb[int,str,bool]] +expr: C = <A> <= <A> +hint: <C> +test: <A> <= <A> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Tutor/Markup/QuestionMarkup/Choice/Choice.concept| +QChoice[Faster]: Which means of transportation is faster? +b: Apache Helicopter +g: High speed train +b: Ferrari F430 +b: Hovercraft +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Relation/CartesianProduct/CartesianProduct.concept| +QType: +make: S1 = set[int,2,3] +make: S2 = set[int,2,3] +test: <S1> * <S2> + +QValue: +make: S1 = set[int,2,3] +make: S2 = set[int,2,2] +expr: H = <S1> * <S2> +hint: <H> +test: <S1> * <S2> == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/RascalTests/TestSoftwareEvolution/TestSoftwareEvolution.concept| +QChoice: Sets can be used to represent a sequence of values when +b: The values have duplicates. +g: The values have no duplicates and no order. +b: The values are unordered. + +QChoice: The type of a list is determined by: +b: The type of the first element that was first added to the list. +b: The upperbound of the type of two arbitrary elements. +g: The upperbound of the type of all elements. + +QType: <A:set[arb[int,real,str,loc]]> + +QType: <A:list[arb[int,real,str,loc]]> + +QType: <A:map[str,arb]> + + +QType: +make: A = int +type: set[int] +test: {<A>, <?> } +hint: one or more integer values separated by commas + +QType: +make: A = str +type: map[str,int] +test: (<A>: <?>) +hint: a map from strings to integers + +QType: <A:set[arb[int,real,num,str,loc]]> + +QType: {<A:int>, <B:str>, <C:int>} + +QType: <A:rel[str,int,loc]> + +QType: <A:rel[int[0,20],int]> + +QValue: +desc: Determine the number of elements in a list +list: +import List; +text = ["abc", "def", "ghi"]; +test: <?>(text) == 3; + +QValue: +desc: Determine the number of strings that contain "a". +list: +text = ["andra", "moi", "ennepe", "Mousa", "polutropon"]; +public int count(list[str] text){ + n = 0; + for(s <- text) + if(<?> := s) + n +=1; + return n; +} + +test: count(text) == 2; + +QValue: +desc: Return the strings that contain "o". +list: +text = ["andra", "moi", "ennepe", "Mousa", "polutropon"]; +public list[str] find(list[str] text){ + return + for(s <- text) + if(/o/ := s) + <?>; +} +test: find(text) == ["moi", "Mousa", "polutropon"]; + +QValue: +desc: Complete this function that finds duplicates in a list of strings +list: +text = ["the", "jaws", "that", "bite", "the", "claws", "that", "catch"]; +public list[str] duplicates(list[str] text){ + m = {}; + return + for(s <- text) + if(<?>) + append s; + else + m += s; +} +test: duplicates(text) == ["the", "that"]; + +QValue: +desc: Complete this function that tests that a list of words forms a palindrome. A palindrome is a word that is symmetrical +and can be read +from left to right and from right to left. +list: +import List; +public bool isPalindrome(list[str] words){ + return words == <?>; +} +test: isPalindrome(["a", "b", "b", "a"]) == true; +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/Difference/Difference.concept| +QChoice: When you compute the difference of two lists, the number of elements in the result is always: +b: Greater than the number of elements in the first list. +b: Greater than the number of elements in the second list. +b: Greater than or equal to the sum of the number of elements in both lists. +g: Smaller than or equal to the number of elements in the first list. +b: Smaller than or equal to the number of elements in the second list. +b: Equal to the sum of the number of elements in both lists. + +QType: +make: A = list[arb[int[0,100],str]] +make: B = same[A] +test: <A> - <B> + +QValue: +prep: import Set; +make: DIFF = set[int[0,100]] +make: A = same[DIFF] +make: B = same[DIFF] +expr: A1 = toList(<DIFF> + <A>) +expr: B1 = toList(<B> + <DIFF>) +expr: C = <A1> - <B1> +hint: <C> +test: <A1> - <B1> == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/Product/Product.concept| +QChoice: When you compute the product of two lists, the number of elements in the result is always: +b: Smaller than or equal to the number of elements in the first list. +b: Smaller than or equal to the number of elements in the second list. +g: Equal to the product of the number of elements in both lists. +b: Equal to the sum of the number of elements in both lists. +b: Equal to the number of elements in the largest list. + + +QType: <A:list[arb[int,str,bool]]> * <B:same[A]> + +QValue: +make: A = list[int[0,50]] +make: B = int[0,50] +expr: C = <A> * [<B>] +hint: <C> +test: <A> * [<B>] == <?> + +QValue: +make: A = list[int[0,50]] +make: B = int[0,50] +make: C = int[0,50] +expr: D = <A> * [<B>, <C>] +hint: <D> +test: <A> * [<B>, <C>] == <?> + +QValue: +prep: import List; +make: A = list[int[0,50]] +make: B = same[A] +expr: C = size(<A> * <B>) +hint: <C> +test: size(<A> * <B>) == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/NotEqual/NotEqual.concept| +QType: <A:list[arb]> != <B:same[A]> + +QValue: +make: A = list[arb[int[0,10],str]] +expr: C = <A> != <A> +hint: <C> +test: (<A> != <A>) == <?> + +QValue: +make: A = list[int[0,100],str] +make: B = same[A] +expr: C = <A> != <B> +hint: <C> +test: (<A> != <B>) == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/Intersection/Intersection.concept| +QChoice: When you compute the intersection of two lists, the number of elements in the result is always: +b: Greater than the number of elements in the first list. +b: Greater than the number of elements in the second list. +b: Greater than or equal to the sum of the number of elements in both lists. +g: Smaller than or equal to the number of elements in the first list. +b: Smaller than or equal to the number of elements in the second list. +b: Equal to the sum of the number of elements in both lists. + +QType: <A:list[int[0,100],str]> & <B:same[A]> + +QValue: +prep: import Set; +make: DIFF = set[int[0,100],str] +make: A = same[DIFF] +make: B = same[DIFF] +expr: A1 = toList(<DIFF> + <A>) +expr: B1 = toList(<B> + <DIFF>) +expr: C = <A1> & <B1> +hint: <C> +test: <A1> & <B1> == <?> + +QValue: +make: A = list[int[0,10],str] +make: B = same[A] +expr: C = <A> & <B> +hint: <C> +test: <A> & <B> == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/Insert/Insert.concept| +QChoice: When you insert an element in a list, the number of elements in the result is always: +g: Greater than the number of elements in the original list. +g: One larger than the number of elements in the original list. +b: Smaller than the number of elements in the original list. +b: One smaller than the number of elements in the original list. + +QType: +make: A = arb[int[0,100],str] +make: B = list[same[A]] +test: <A> + <B> + +QValue: +make: A = arb[int[0,100],str] +make: B = list[same[A]] +hint: <A> + <B> +test: <A> + <B> == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/Subscription/Subscription.concept| +QChoice: For a list of length $N$, legal index value are: +g: The integers 0, 1, ..., N - 1. +b: Positive integers. +b: The integers 1, 2, ..., N. +b: Even integers. +b: Odd integers. +b: The integers, 0, 2, ..., N. + +QValue: +prep: import List; +make: L = list[arb[bool,int,str],4,6] +make: I = int[0,3] +expr: C = <L>[<I>] +hint: <C> +list: +L = <L>; +test: L[<I>] == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/in/in.concept| +QType: <A:arb[int,str,bool]> in <B:list[same[A]]> + +QValue: +prep: import Set; +make: ELM = int[0,100] +make: A = set[same[ELM]] +expr: A1 = toList( {<ELM>} + <A>) +expr: C = <ELM> in <A1> +hint: <C> +test: <ELM> in <A1> == <?> + +QValue: +prep: import Set; +make: ELM = int[0,10] +make: A = list[same[ELM]] +expr: C = <ELM> in <A> +hint: <C> +test: <ELM> in <A> == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/Append/Append.concept| +QChoice: When you append an element to a list, the number of elements in the result is always: +g: Greater than the number of elements in the original list. +g: One larger than the number of elements in the original list. +b: Smaller than the number of elements in the original list. +b: One smaller than the number of elements in the original list. + +QType: +make: B = arb[int[0,100],str] +make: A = list[same[B]] +test: <A> + <B> + +QValue: +make: B = arb[int[0,100],str] +make: A = list[same[B]] +expr: C = <A> + <B> +hint: <C> +test: <A> + <B> == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Boolean/Match/Match.concept| +QValue: +desc: Determine the number of strings that contain "a". +list: +text = ["andra", "moi", "ennepe", "Mousa", "polutropon"]; +public int count(list[str] text){ + n = 0; + for(s <- text) + if(<?> := s) + n +=1; + return n; +} +test: count(text) == 2; +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Tutor/Markup/QuestionMarkup/Value/Value.concept| +QValue: <A:set[int]> + <B:same[A]> + +QValue: +prep: import List; +test: size(<A:list[int]>) == <?> + +QValue: +make: A = set[arb[int,str]] +make: B = same[A] +expr: C = <A> + <B> +hint: <B> +test: <A> + <?> == <C> + +QValue: +desc: Return the strings that contain "o". +list: +text = ["andra", "moi", "ennepe", "Mousa", "polutropon"]; +public list[str] find(list[str] text){ + return + for(s <- text) + if(/o/ := s) + <?>; +} +test: find(text) == ["moi", "Mousa", "polutropon"]; +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/notin/notin.concept| +QType: <A:arb[int,str,bool]> notin <B:set[same[A]]> + + +QValue: +make: ELM = int[0,100] +make: A = set[same[ELM]] +expr: A1 = {<ELM>, *<A>} +expr: C = <ELM> notin <A1> +hint: <C> +test: <ELM> notin <A1> == <?> + +QValue: +make: ELM = int[0,10] +make: A = set[same[ELM]] +expr: C = <ELM> notin <A> +hint: <C> +test: <ELM> notin <A> == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/StrictSuperList/StrictSuperList.concept| +QType: <A:list[arb]> > <B:same[A]> + +QValue: +prep: import Set; +make: DIFF = set[int[0,100]] +make: B = same[DIFF] +expr: A = toList(<B> + <DIFF>) +expr: B1 = toList(<B>) +expr: C = <A> > <B1> +hint: <C> +test: <A> > <B1> == <?> + +QValue: +make: A = list[arb[int,str,bool]] +expr: C = <A> > <A> +hint: <C> +test: <A> > <A> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/Set.concept| +QChoice: Sets can be used to represent a sequence of values when +b: The values have duplicates. +g: The values have no duplicates and no order. +b: The values are unordered. + +QChoice: The type of a set is determined by: +g: The least upper bound of the type of all elements. +g: The types of all the elements in the set. +b: The type of the element that was first added to the set. +b: The average of the type of the elements with the smallest and the largest type. +b: The least upper bound of the type of two arbitrary elements. +b: The type of two arbitrary elements. + + +QType: {1, <?> } +type: set[int] +hint: one or more integer values separated by commas + +QType: <A:set[arb[int,str]]> + +QType: {<A:int>, <B:str>, <C:int>} + +QValue: +desc: Fill in the missing operator. +make: ELM = int[0,100] +make: A = set[same[ELM]] +expr: A1 = {<ELM>} + <A> +expr: C = <ELM> in <A1> +hint: in +test: <ELM> <?> <A1> == <C> + +QValue: +desc: Fill in the missing operator. +make: A = arb[int[0,100],str] +make: B = set[same[A]] +expr: H = <A> + <B> +hint: + +test: <A> <?> <B> == <H> + +QValue: +desc: Fill in the missing operator. +make: DIFF = set[int[0,100],str] +make: A = same[DIFF] +make: B = same[DIFF] +expr: A1 = <DIFF> + <A> +expr: B1 = <B> + <DIFF> +expr: C = <A1> & <B1> +hint: & +test: <A1> <?> <B1> == <C> + + +QValue: +desc: Fill in the missing operator. +prep: import Set; +make: DIFF = set[int[0,100]] +make: A = same[DIFF] +make: B = same[DIFF] +expr: A1 = <DIFF> + <A> +expr: B1 = <B> + <DIFF> +expr: C = <A1> - <B1> +hint: - +test: <A1> <?> <B1> == <C> + +QValue: +desc: Fill in the missing operator. +make: ELM = int[0,10] +make: A = set[same[ELM]] +expr: C = <ELM> notin <A> +hint: notin +test: <ELM> <?> <A> == <C> + +QValue: +desc: Fill in the missing operator. +make: DIFF = set[int[0,100]] +make: A = same[DIFF] +expr: B = <A> + <DIFF> +expr: C = <A> < <B> +hint: < +test: <A> <?> <B> == <C> + +QValue: +desc: Fill in the missing operator. +make: A = set[arb[int,str]] +make: B = same[A] +expr: C = <A> + <B> +hint: + +test: <A> <?> <B> == <C> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Number/GreaterThan/GreaterThan.concept| +QType: <A:int> > <B:int> +QType: <A:real> > <B:real> +QType: <A:num> > <B:num> +QType: <A:num> > <B:num> +QValue: <A:num> > <B:num> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Patterns/Regular/Regular.concept| +QValue: +desc: Return the strings that contain "o". +list: +text = ["andra", "moi", "ennepe", "Mousa", "polutropon"]; +public list[str] find(list[str] text){ + return + for(s <- text) + if(/o/ := s) + <?>; +} +test: find(text) == ["moi", "Mousa", "polutropon"]; +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/ListRelation/TransitiveClosure/TransitiveClosure.concept| +QType: +prep: S = [<1,2>, <2,3>]; +make: A = list[tuple[int[0,5],int[0,5]],1,2] +expr: B = <A> + S +test: <B>+ + +QValue: +prep: S = [<1,2>, <2,3>, <3,4>]; +make: A = list[tuple[int[0,5],int[0,5]],1,2] +expr: B = <A> + S +expr: H = <B>+ +hint: <H> +test: <B>+ +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/Equal/Equal.concept| +QType: <A:list[arb]> == <B:same[A]> + +QValue: +make: A = list[arb[int[0,10],str]] +expr: C = <A> == <A> +hint: <C> +test: (<A> == <A>) == <?> + +QValue: +prep: import List; +make: A = list[int[0,100]] +expr: B = reverse(<A>) +expr: C = <A> == <B> +hint: <C> +test: (<A> == <B>) == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/Comprehension/Comprehension.concept| +QValue: +desc: Return the strings that contain a given substring. +list: +text = ["An", "honest", "man", "is", "always", "a", "child"]; +public list[str] find(list[str] text, str contains) = [ s | s <- text, <?> ]; +test: find(text, "n") == ["An", "honest", "man"]; +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Number/LessThan/LessThan.concept| +QType: <A:int> < <A:int> +QType: <A:real> < <B:real> +QType: <A:num> < <B:num> +QType: <A:num> < <B:num> +QValue: <A:num> < <B:num> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Number/Negation/Negation.concept| +QType: -<A:int> +QType: -<A:real> +QValue: -<A:num> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Number/Multiplication/Multiplication.concept| +QType: <A:int> * <B:int> +QType: <A:int> * <B:real> +QType: <A:real> * <B:int> +QValue: <A:num> * <B:num> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Statements/If/If.concept| +QType: if( <A:int> > <B:int> ) 10; else 20; + +QType: if( <A:int> > <B:int> ) <C:str>; else <D:str>; + +QValue: if( <A:int> > <B:int> ) 10; else 20; +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Number/Division/Division.concept| +QType: <A:int> / <B:int> +QType: <A:int> / <B:real> +QType: <A:real> / <B:int> +QValue: <A:num> / <B:num> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Statements/Append/Append.concept| +QValue: +desc: Complete this function that finds duplicates in a list of strings +list: +text = ["the", "jaws", "that", "bite", "the", "claws", "that", "catch"]; +public list[str] duplicates(list[str] text){ + m = {}; + return + for(s <- text) + if(<?>) + append s; + else + m += s; +} +test: duplicates(text) == ["the", "that"]; +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Number/Addition/Addition.concept| +QType: <A:int[0]> + <B:int[0]> +desc: Adding integers. + +QValue: <A:int[0]> + <B:int[0]> +desc: Adding integers. + +QType:<A:int[0]> + <B:real[0]> +desc: Adding integers and reals. + +QValue:<A:int[0]> + <B:real[0]> +Adding integers and reals. + +QValue: <A:int> + (<B:int[-20,-1]>) +desc:Use parentheses when addition and negative numbers interact. + +QValue: +desc: Use parentheses when addition and negative numbers interact. +make: A = int +make: B = int[0,10] +expr: C = <A> - <B> +test: <A> + <?> == <C> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Number/GreaterThanOrEqual/GreaterThanOrEqual.concept| +QType: <A:int> >= <B:int> +QType: <A:real> >= <B:real> +QType: <A:num> >= <B:num> +QType: <A:num> >= <B:num> +QValue: <A:num> >= <B:num> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Number/NotEqual/NotEqual.concept| +QType: <A:int> != <B:int> +QType: <A:real> != <B:real> +QType: <A:num> != <B:num> +QType: <A:num> != <B:num> +QValue: <A:num> != <B:num> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/List/StrictSubList/StrictSubList.concept| +QType: <A:list[arb]> < <B:same[A]> + +QValue: +prep: import Set; +make: DIFF = set[int[0,100]] +make: A = same[DIFF] +expr: A1 = toList(<A>) +expr: B = toList(<A> + <DIFF>) +expr: C = <A1> < <B> +hint: <C> +test: <A1> < <B> == <?> + +QValue: +make: A = list[arb[int,str,bool]] +expr: C = <A> < <A> +hint: <C> +test: <A> < <A> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Relation/TransitiveClosure/TransitiveClosure.concept| +QType: +prep: S = {<1,2>, <2,3>}; +make: A = set[tuple[int[0,5],int[0,5]],1,2] +expr: B = <A> + S +test: <B>+ + +QValue: +prep: S = {<1,2>, <2,3>, <3,4>}; +make: A = set[tuple[int[0,5],int[0,5]],1,2] +expr: B = <A> + S +expr: H = <B>+ +hint: <H> +test: <B>+ +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Tuple/Tuple.concept| +QChoice: For a tuple: +g: All elements may have different types. +b: All elements should have the same type. +g: It's type changes with the number of elements. +b: It's type does not change with the number of elements. +g: The order of the elements is relevant. +b: The order of the elements is not relevant. +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/Difference/Difference.concept| +QChoice: When you compute the difference of two sets, the number of elements in the result is always: +b: Greater than the number of elements in both sets. +b: Greater than or equal to the number of elements in the first set. +b: Smaller than the number of elements in both sets. +g: Smaller than or equal to the number of elements in the first set. + +QType: <A:set[arb[int,str]]> - <B:same[A]> + +QType: <A:set[arb[str,int]]> - <A:same[A]> + +QValue: +prep: import Set; +make: DIFF = set[int[0,100]] +make: A = same[DIFF] +make: B = same[DIFF] +expr: A1 = <DIFF> + <A> +expr: B1 = <B> + <DIFF> +expr: C = <A1> - <B1> +hint: <C> +test: <A1> - <B1> == <?> + +QValue: +prep: import Set; +make: DIFF = set[int[0,100]] +make: A = same[DIFF] +make: B = same[DIFF] +expr: A1 = <DIFF> + <A> +expr: B1 = <B> + <DIFF> +expr: C = <A1> - <B1> +hint: <A1> +test: <?> - <B1> == <C> + +QValue: +prep: import Set; +make: DIFF = set[int[0,100]] +make: A = same[DIFF] +make: B = same[DIFF] +expr: A1 = <DIFF> + <A> +expr: B1 = <B> + <DIFF> +expr: C = <A1> - <B1> +hint: <B1> +test: <A1> - <?> == <C> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/NotEqual/NotEqual.concept| +QType: <A:list[arb]> != <B:same[A]> + +QValue: +make: A = set[arb[int[0,10],str]] +expr: C = <A> != <A> +hint: <C> +test: (<A> != <A>) == <?> + +QValue: +make: A = set[int[0,100],str] +make: B = same[A] +expr: C = <A> != <B> +hint: <C> +test: (<A> != <B>) == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/StrictSuperSet/StrictSuperSet.concept| +QType: <A:list[arb]> > <B:same[A]> + +QValue: +make: DIFF = set[int[0,100]] +make: B = same[DIFF] +expr: A = <B> + <DIFF> +expr: C = <A> > <B> +hint: <C> +test: <A> > <B> == <?> + +QValue: +make: A = set[arb[int,str,bool]] +expr: C = <A> > <A> +hint: <C> +test: <A> > <A> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/StrictSubSet/StrictSubSet.concept| +QType: <A:set[arb[int,str]]> < <B:same[A]> + +QValue: +make: DIFF = set[int[0,100]] +make: A = same[DIFF] +expr: B = <A> + <DIFF> +expr: C = <A> < <B> +hint: <C> +test: <A> < <B> == <?> + +QValue: +make: A = set[arb[int,str,bool]] +expr: C = <A> < <A> +hint: <C> +test: <A> < <A> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/SuperSet/SuperSet.concept| +QType: <A:set[arb]> >= <B:same[A]> + + +QValue: +make: DIFF = set[int[0,100]] +make: B = same[DIFF] +expr: A = <B> + <DIFF> +expr: C = <A> >= <B> +hint: <C> +test: <A> >= <B> == <?> + +QValue: +make: A = set[arb[int,str,bool]] +expr: C = <A> >= <A> +hint: <C> +test: <A> >= <A> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/Product/Product.concept| +QType: <A:list[arb[int,str,bool]]> * <B:same[A]> + +QValue: +make: A = set[int[0,50]] +make: B = int[0,50] +expr: C = <A> * {<B>} +hint: <C> +test: <A> * {<B>} == <?> + +QValue: +make: A = set[int[0,50]] +make: B = int[0,50] +make: C = int[0,50] +expr: D = <A> * {<B>, <C>} +hint: <D> +test: <A> * {<B>, <C>} == <?> + +QValue: +prep: import Set; +make: A = set[int[0,50]] +make: B = same[A] +expr: C = size(<A> * <B>) +hint: <C> +test: size(<A> * <B>) == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/Union/Union.concept| +QChoice: When you compute the union of two sets, the number of elements in the result is always: +g: Smaller than or equal to the total number of elements in both sets. +b: Greater than or equal to the number of elements in both sets. +b: Greater than the number of elements in both sets. +b: Smaller than the number of elements in both sets. + +QType: <A:set[arb[int,str,real]]> + <B:same[A]> + +QValue: <A:set[arb[int,str,real]]> + <B:same[A]> + +QValue: +make: A = set[arb[int,str]] +make: B = same[A] +expr: C = <A> + <B> +hint: <B> +test: <A> + <?> == <C> + +QValue: <A:set[arb[0,int,str]]> + <B:same[A]> + +QValue: <A:set[arb[0,int,str]]> + {} +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/SubSet/SubSet.concept| +QType: <A:set[arb[0,int,str,real]]> <= <B:same[A]> + +QValue: +make: DIFF = set[int[0,100]] +make: A = same[DIFF] +expr: B = <A> + <DIFF> +expr: C = <A> <= <B> +hint: <C> +test: <A> <= <B> == <?> + +QValue: +make: A = set[arb[int,str,bool]] +expr: C = <A> <= <A> +hint: <C> +test: <A> <= <A> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/Insert/Insert.concept| +QChoice: When you insert an element in a set, the number of elements in the result is always: +g: Greater than or equal to the number of elements in the original set. +g: One larger than the number of elements in the original set. +b: Smaller than the number of elements in the original set. +b: One smaller than the number of elements in the original set. + +QType: +make: A = arb[int[0,100],str] +make: B = set[same[A]] +test: <A> + <B> + +QValue: +make: A = arb[int[0,100],str] +make: B = set[same[A]] +expr: H = <A> + <B> +hint: <H> +test: <A> + <B> == <?> + +QValue: +make: A = arb[int[0,100],str] +make: B = set[same[A]] +expr: H = <B> + <A> +hint: <H> +test: <B> + <A> == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/Intersection/Intersection.concept| +QChoice: When you compute the intersection of two sets, the number of elements in the result is always: +b: Greater than the number of elements in both sets. +b: Greater than or equal to the number of elements in both sets. +b: Smaller than the number of elements in both sets. +g: Smaller than or equal to the number of elements in the smallest set. + + +QType: <A:list[int[0,100],str]> & <B:same[A]> + +QValue: +make: DIFF = set[int[0,100],str] +make: A = same[DIFF] +make: B = same[DIFF] +expr: A1 = <DIFF> + <A> +expr: B1 = <B> + <DIFF> +expr: C = <A1> & <B1> +hint: <C> +test: <A1> & <B1> == <?> + +QValue: +make: A = set[int[0,10],str] +make: B = same[A] +expr: C = <A> & <B> +hint: <C> +test: <A> & <B> == <?> +conceptFile: |file:///Users/paulklint/git/rascal/src/org/rascalmpl/courses/Rascal/Expressions/Values/Set/in/in.concept| +QType: <A:arb[int,str,bool]> in <B:list[same[A]]> + +QValue: +make: ELM = int[0,100] +make: A = set[same[ELM]] +expr: A1 = {<ELM>} + <A> +expr: C = <ELM> in <A1> +hint: <C> +test: <ELM> in <A1> == <?> + +QValue: +prep: import Set; +make: ELM = int[0,10] +make: A = set[same[ELM]] +expr: C = <ELM> in <A> +hint: <C> +test: <ELM> in <A> == <?> +value: true + +conceptFile: List.rsc: + +Questions: + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],4,6] +make: I = int[0,3] +expr: E = <L>[<I>] +expr: H = indexOf(<L>, <E>) +hint: indexOf +test: <?>(<L>, <E>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: E = arb[int,str] +make: L = list[same[E],4,6] +make: I = int[0,3] +expr: H = insertAt(<L>, <I>, <E>) +hint: insertAt +test: <?>(<L>, <I>, <E>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[int,0,5] +expr: H = intercalate(";", <L>) +hint: intercalate +test: <?>(";", <L>) == <H> + + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],0,5] +expr: H = index(<L>) +hint: index +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],1,6] +expr: H = head(<L>) +hint: head +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str], 1, 6] +expr: H = last(<L>) +hint: last +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str]] +expr: H = toSet(<L>) +hint: toSet +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[bool,int,str],4,6] +make: N = int[0,3] +expr: H = take(<N>, <L>) +hint: take +test: <?>(<N>, <L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[bool,int,str],4,6] +make: N = int[0,3] +expr: H = tail(<L>,<N>) +hint: tail +test: <?>(<L>,<N>) == <H> + + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],1,5] +expr: H = headTail(<L>) +hint: Use headTail. +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[bool,int,str],4,6] +make: I = int[0,3] +make: J = int[0,3] +expr: L1 = [<L>[<I>], <L>[<J>], *<L>, <L>[<J>], <L>[<I>]] +expr: H = dup(<L1>) +hint: dup +test: <?>(<L1>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[bool,int,str],2,5] +expr: H = getOneFrom(<L>) +hint: getOneFrom +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[bool,int,str],3,4] +make: N = int[0,2] +expr: H = drop(<N>, <L>) +hint: drop +test: <?>(<N>, <L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],0,5] +expr: H = reverse(<L>) +hint: reverse +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],3,4] +make: I = int[0,2] +expr: E = <L>[<I>] +expr: L1 = reverse(<L>) + <L> +expr: H = lastIndexOf(<L1>, <E>) +hint: lastIndexOf +test: <?>(<L1>, <E>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],0,5] +expr: H = index(<L>) +hint: index +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],1,5] +expr: H = pop(<L>) +hint: pop +test: <?>(<L>) == <H> + + +QValue: +prep: import List; +make: L = list[arb[int,str], 1, 6] +expr: H = max(<L>) +hint: max +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[int[-20,20]] +expr: H = takeWhile(<L>, bool(int x){ return x > 0;}) +hint: takeWhile +test: <?>(<L>, bool(int x){ return x > 0;}) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],3,4] +make: I = int[0,2] +expr: C = delete(<L>, <I>) +hint: delete +test: <?>(<L>, <I>) == <C> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[int,2,7] +expr: H = sum(<L>) +hint: sum +test: <?>(<L>) == <H> + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[int, 4, 4] +make: M = list[int, 4, 4] +expr: Z = zip(<L>,<M>) +hint: zip +test: <?>(<L>, <M>) == <Z> + + +QValue: +desc: Fill in the missing function name. +prep: import List; +make: L = list[arb[int,str],1,5] +expr: H = sort(<L>) +hint: sort +test: <?>(<L>) == <H> + + +QValue: +prep: import List; +make: L = list[int, 1,5] +expr: H = mapper(<L>, int(int n){ return n + 1; }) +hint: mapper +list: +int incr(int x) { return x + 1; } +test: <?>(<L>, incr) == <H> + +QValue: +desc: Complete this function that tests that a list of words forms a palindrome. A palindrome is a word that is symmetrical +and can be read +from left to right and from right to left. +list: +import List; +public bool isPalindrome(list[str] words){ + return words == <?>; +} +test: isPalindrome(["a", "b", "b", "a"]) == true; + + diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ValueGenerator.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ValueGenerator.rsc new file mode 100644 index 00000000000..98814e2e836 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ValueGenerator.rsc @@ -0,0 +1,333 @@ +module lang::rascal::tutor::questions::ValueGenerator + +import Boolean; +import String; +import util::Math; +import List; +import Set; +import String; +import DateTime; +import lang::rascal::tutor::Questions; +import Type; + +lexical IntCon = [0-9]+ !>> [0-9]; +syntax Type = "arb" "[" IntCon depth "," {Type ","}+ elemTypes "]"; + +private list[Type] baseTypes = [(Type)`bool`, (Type)`int`, (Type)`real`, (Type)`num`, (Type)`str`, (Type)`loc`, (Type)`datetime`]; + +// Type generation + +Type generateType(Type tp){ + return + visit(tp){ + case (Type) `arb[<IntCon depth>,<{Type ","}+ elemTypes>]` => + generateArbType(toInt("<depth>"), elemTypes) + }; +} + +Type generateArbType(int n, {Type ","}+ prefs) = generateArbType(n, [p | p <- prefs]); + +Type generateArbType(int n, list[Type] prefs){ + //println("generateArbType: <n>, <prefs>"); + if(n <= 0){ + return getOneFrom(prefs); + } + + switch(arbInt(6)){ + case 0: { elemType = generateArbType(n-1, prefs); return (Type) `list[<Type elemType>]`; } + case 1: { elemType = generateArbType(n-1, prefs); return (Type) `set[<Type elemType>]`; } + case 2: { keyType = generateArbType(n-1, prefs); + valType = generateArbType(n-1, prefs); + return (Type) `map[<Type keyType>,<Type valType>]`; + } + case 3: return generateArbTupleType(n-1, prefs); + case 4: return generateArbRelType(n-1, prefs); + case 5: return generateArbLRelType(n-1, prefs); + } + return getOneFrom(prefs); +} + +int size({Type ","}+ ets) = size([et | et <- ets]); + +Type makeTupleType({Type ","}+ ets) = [Type] "tuple[<intercalate(",", [et | et <- ets])>]"; + +Type generateArbTupleType(int n, list[Type] prefs){ + arity = 1 + arbInt(5); + return [Type] "tuple[<intercalate(",", [generateArbType(n-1, prefs) | int _ <- [0 .. 1+arbInt(5)] ])>]"; +} + +Type generateArbRelType(int n, list[Type] prefs){ + return [Type] "rel[<intercalate(",", [generateArbType(n - 1, prefs) | int _ <- [0 .. 1+arbInt(5)] ])>]"; +} + +Type generateArbLRelType(int n, list[Type] prefs){ + return [Type] "lrel[<intercalate(",", [generateArbType(n - 1, prefs) | int _ <- [0 .. 1+arbInt(5)] ])>]"; +} + +// Value generation + +public str generateValue(Type tp){ + //println("generateValue(<tp>, <env>)"); + switch(tp){ + case (Type) `bool`: + return generateBool(); + + case (Type) `int`: + return generateInt(-100,100); + + case (Type) `int[<IntCon f>,<IntCon t>]`: + return generateInt(toInt("<f>"), toInt("<t>")); + + case (Type) `real`: + return generateReal(-100,100); + + case (Type) `real[<IntCon f>,<IntCon t>]`: + return generateReal(toInt("<f>"), toInt("<t>")); + + case (Type) `num`: + return generateNumber(-100,100); + + case (Type) `num[<IntCon f>,<IntCon t>]`: + return generateNumber(toInt("<f>"), toInt("<t>")); + + case (Type) `str`: + return generateString(); + + case (Type) `loc`: + return generateLoc(); + + case (Type) `datetime`: + return generateDateTime(); + + case (Type) `list[<Type et>]`: + return generateList(et); + + case (Type) `list[<Type et>][<IntCon f>,<IntCon t>]`: + return generateList(et, from=toInt("<f>"), to=toInt("<t>")); + + case (Type) `set[<Type et>]`: + return generateSet(et); + + case (Type) `set[<Type et>] [<IntCon f>,<IntCon t>]`: + return generateSet(et, from=toInt("<f>"), to=toInt("<t>")); + + case (Type) `map[<Type kt>,<Type vt>]`: + return generateMap(kt, vt); + + case (Type) `map[<Type kt>,<Type vt>][<IntCon f>,<IntCon t>]`: + return generateMap(kt, vt, from=toInt("<f>"), to=toInt("<t>")); + + case (Type) `tuple[<{Type ","}+ ets>]`: + return generateTuple(ets); + + case (Type) `rel[<{Type ","}+ ets>]`: + return generateSet(makeTupleType(ets)); + + case (Type) `lrel[<{Type ","}+ ets>]`: + return generateList(makeTupleType(ets)); + + case (Type) `value`: + return generateArb(0, baseTypes); + } + throw "Unknown type: <tp>"; +} + +set[set[str]] vocabularies = +{ +// Letters: +{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", +"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}, + +// Sesame Street: +{"Bert", "Ernie", "Big Bird", "Cookie Monster", "Grover", "Elmo", +"Slimey the Worm", "Telly Monster", "Count Von Count", "Countess Darling Von Darling", +"Countess Von Backwards", "Guy Smiley", "Barkley the Dog", "Little Bird", +"Forgetful Jones", "Bruno", "Hoots The Owl", "Prince Charming", +"Mumford the Magician", "Two-Headed Monster"}, + +// Star trek + +// Dr Who + +// Star Wars: +{"Jar Jar Binks", "C-3PO", "E-3PO", "Boba Fett", "Isolder", "Jabba the Hut", +"Luke Skywalker", "Obi-Wan Kenobi", "Darth Sidious", "Pincess Leia", +"Emperor Palpatine", "R2-D2", "Admiral Sarn", "Boba Fett", +"Anakin Skywalker", "Sy Snootles", "Han Solo", "Tibor", "Darth Vader", +"Ailyn Vel", "Yoda", "Zekk", "Joh Yowza"}, + +// Game of Thrones: +{"Tyrion", "Cersei", "Daenerys", "Petyr", "Jorah", "Sansa", "Arya", + "Theon", "Bran", "Sandor", "Joffrey", "Catelyn", "Robb", "Ned", + "Viserys", "Khal", "Varys", "Samwell", "Bronn", "Tywin", "Shae", + "Jeor", "Gendry", "Tommen", "Jaqen", "Davos", "Melisandre", + "Margaery", "Stannis", "Ygritte", "Talisa", "Brienne", "Gilly", + "Roose", "Tormund", "Ramsay", "Daario", "Missandei", "Eilaria", + "The High Sparrow"}, + + // World of Warcraft: + + {"Archimonde", "Kil\'jaeden", "Mannoroth", "Ner\'zhul", "Sargeras", + "Balnazzar", "Magtheridon", "Mal\'Ganis", "Tichondrius", "Varimathras", + "Azgalor", "Hakkar", "Kazzak", "Detheroc", "Akama", "Velen", + "Alexstrasza", "Malygos", "Neltharion", "Nozdormu", "Ysera", + "Korialstrasz", "Kalecgos", "Brann", "Muradin"}, + +// Fruits: +{"Blackcurrant", "Redcurrant", "Gooseberry", "Eggplant", "Guava", +"Pomegranate", "Kiwifruit", "Grape", "Cranberry", "Blueberry", "Pumpkin", +"Melon", "Orange", "Lemon", "Lime", "Grapefruit", "Blackberry", + "Raspberry", "Pineapple", "Fig", "Apple", "Strawberry", + "Mandarin", "Coconut", "Date", "Prune", "Lychee", "Mango", "Papaya", + "Watermelon"}, + +// Herbs and spices: +{"Anise", "Basil", "Musterd", "Nutmeg", "Cardamon", "Cayenne", +"Celery", "Pepper", "Cinnamon", "Coriander", "Cumin", +"Curry", "Dill", "Garlic", "Ginger", "Jasmine", +"Lavender", "Mint", "Oregano", "Parsley", "Peppermint", +"Rosemary", "Saffron", "Sage", "Sesame", "Thyme", +"Vanilla", "Wasabi"} + +}; + +public str generateBool(){ + return toString(arbBool()); +} + + public str generateInt(int from, int to){ + return toString(from + arbInt(to - from)); +} + + public str generateReal(int from, int to){ + return toString(from + arbReal() * (to - from)); +} + +public str generateNumber(int from, int to){ + return (arbBool()) ? generateInt(from, to) : generateReal(from, to); +} + +public str generateLoc(){ + return "|file:///home/paulk/pico.trm|(0,1,\<2,3\>,\<4,5\>)"; + /* + scheme = getOneFrom(["http", "file", "stdlib", "cwd"]); + authority = "auth"; + host = "www.cwi.nl"; + port = "80"; + path = "/a/b"; + +uri: the URI of the location. Also subfields of the URI can be accessed: + +scheme: the scheme (or protocol) like http or file. Also supported is cwd: for current working directory (the directory from which Rascal was started). + +authority: the domain where the data are located. + +host: the host where the URI is hosted (part of authority). + +port: port on host (part ofauthority). + +path: path name of file on host. + +extension: file name extension. + +query: query data + +fragment: the fragment name following the path name and query data. + +user: user info (only present in schemes like mailto). + +offset: start of text area. + +length: length of text area + +begin.line, begin.column: begin line and column of text area. + +end.line, end.column end line and column of text area. +*/ +} + +public str generateDateTime(){ + year = 1990 + arbInt(150); + month = 1 + arbInt(11); + day = 1 + arbInt(6); + dt1 = createDate(year, month, day); + if(arbBool()) + return "<dt1>"; + hour = arbInt(24); + minute = arbInt(60); + second = arbInt(60); + milli = arbInt(1000); + dt2 = createTime(hour, minute, second, milli); + return "<joinDateAndTime(dt1, dt2)>"; +} + +public str generateString(){ + vocabulary = getOneFrom(vocabularies); + return "\"<getOneFrom(vocabulary)>\""; +} + +public str generateList(Type et, int from=0, int to=5){ + n = from; + if(from < to) + n = from + arbInt(to - from + 1); + elms = []; + while(size(elms) < n){ + elms += generateValue(et); + } + return "[<intercalate(", ", elms)>]"; +} + +public str generateSet(Type et, int from=0, int to=5){ + n = from; + if(from < to) + n = from + arbInt(to - from + 1); + elms = []; + attempt = 0; + while(size(elms) < n && attempt < 100){ + attempt += 1; + elm = generateValue(et); + if(elm notin elms) + elms += elm; + } + return "{<intercalate(", ", elms)>}"; +} + +public str generateMap(Type kt, Type vt, int from=0, int to=5){ + keys = { generateValue(kt) | int _ <- [from .. arbInt(to)] }; // ensures unique keys + keyList = toList(keys); + return "(<for(i <- index(keyList)){><(i==0)?"":", "><keyList[i]>: <generateValue(vt)><}>)"; +} + +public str generateTuple({Type ","}+ ets){ + return intercalate(", ", [generateValue(et) | et <-ets]); +} + +public str generateTuple(list[Type] ets){ + return intercalate(", ", [generateValue(et) | et <-ets]); +} + +public str generateRel({Type ","}+ ets){ + return "\<<for(int i <- [0 .. size(ets)]){><(i==0)?"":", "><generateValue(ets[i])><}>\>"; +} +public str generateRel(list[Type] ets){ + return "\<<for(int i <- [0 .. size(ets)]){><(i==0)?"":", "><generateValue(ets[i])><}>\>"; +} + +public str generateLRel({Type ","}+ ets){ + return "\<<for(int i <- [0 .. size(ets)]){><(i==0)?"":", "><generateValue(ets[i])><}>\>"; +} + +public str generateArb(int n, list[Type] prefs){ + if(n <= 0) + return generateValue(getOneFrom(prefs)); + + switch(arbInt(5)){ + case 0: return generateList(generateArbType(n-1, prefs)); + case 1: return generateSet(generateArbType(n-1, prefs)); + case 2: return generateMap(generateArbType(n-1, prefs), generateArbType(n-1, prefs)); + case 3: return generateTuple(prefs); + case 4: return generateRel(prefs); + } + + return generateValue(getOneFrom(prefs)); +} diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/tutor-prelude.js b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/tutor-prelude.js new file mode 100644 index 00000000000..03710e023a3 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/tutor-prelude.js @@ -0,0 +1,434 @@ + +/* + * Prelude for Rascal Tutor + */ + +$('.hole').each(visitHole); +$('.code-question').each(visitCodeQuestion); +$('.choice-question').each(visitChoiceQuestion); +$('.click-question').each(visitClickQuestion); +$('.move-question').each(visitMoveQuestion); +$('.fact-question').each(visitFactQuestion); + +//JQuery plugin: +// See http://stackoverflow.com/questions/8100770/auto-scaling-inputtype-text-to-width-of-value +$.fn.textWidth = function(_text, _font){//get width of text with font. usage: $("div").textWidth(); + var fakeEl = $('<span>').hide().appendTo(document.body).text(_text || this.val() || this.text()).css('font', _font || this.css('font')), + width = fakeEl.width(); + fakeEl.remove(); + return width; + }; + +$.fn.autoresize = function(options){//resizes elements based on content size. usage: $('input').autoresize({padding:10,minWidth:0,maxWidth:100}); + options = $.extend({padding:10,minWidth:0,maxWidth:10000}, options||{}); + $(this).on('input', function() { + $(this).css({'width': Math.min(options.maxWidth,Math.max(options.minWidth,$(this).textWidth() + options.padding)), + 'border-radius': '5px'}); + }).trigger('input'); + return this; +} + +//have <input> resize automatically +$(".hole").autoresize({padding:20,minWidth:40,maxWidth:300}); + +// hole + +function visitHole(index){ + var id = $( this ).attr('id'); + var repl = $('<input type="text" class="hole" name="' + id + '">'); + $ (this).replaceWith(repl, $(this).children().html()); +} + +// click + +function handleClick(id){ + $("#" + id).attr('clicked', true); + return false; +} + +// CodeQuestion + +function submitCode(idGood, idBad, idFeedback){ + return "<input type='submit' value='Check It' style='clear:left;'>" + + "<img id ='" + idGood + "' height='25' width='25' src='/images/good.png' style='display:none;'/>" + + "<img id ='" + idBad + "' height='25' width='25' src='/images/bad.png' style='display:none;'/>" + + "<div id ='" + idFeedback + "'> </div>" + ; +} + + +function visitCodeQuestion(index){ + var id = $( this ).attr('id'); + var idGood = id + "-good"; + var idBad = id + "-bad" + var idForm = id + "-form"; + var idFeedback = id + "-feedback"; + var listing = $( this ).attr('listing'); + var content = $( this ).html(); + var $this = $( this ); + + $( this ).html("<form id='" + idForm + "' action='/ValidateCodeQuestion' method='POST'>" + + content + + "<input type='hidden' name='question' value='" + id + "'>" + + "<input type='hidden' name='listing' value='" + listing + "'>" + + submitCode(idGood, idBad, idFeedback) + + "</form><br>"); + $( this ).submit(function(event){ + event.preventDefault(); + $("#" + idBad).hide(100); + $("#" + idGood).hide(100); + $("#" + idFeedback).hide(100); + $("#" + idFeedback).html(""); + $.post("/ValidateCodeQuestion", + $( "#" + idForm ).serialize(), + function(jsonData,status,jqXHR){ + var msgs = ""; + var feedback = jsonData.feedback; + if(jsonData.ok == true){ + if(feedback != null){ + $("#" + idFeedback).html("<i>" + feedback + "</i>"); + $("#" + idFeedback).show(100); + } + $("#" + idGood).show(100); + } else { + $("#" + idBad).show(100); + var failed = jsonData.failed; + var exceptions = jsonData.exceptions; + var syntax = jsonData.syntax; + + if(syntax != null){ + if(syntax.beginLine == syntax.endLine){ + msgs = "Syntax error near line " + syntax.beginLine + ", column " + syntax.beginColumn; + } else { + msgs = "Syntax error at lines " + syntax.beginLine + "-" + syntax.endLine; + } + } else if(failed.length != 0){ + if(failed.length == 1){ + msgs = "Test failed (near line " + failed[0].src.beginLine + ")"; + if(failed[0].msg != ""){ + msgs += ": " + failed[0].msg; + } + msgs += "."; + } else { + msgs = "Tests failed: <ul>"; + for(var i = 0; i < failed.length; i++){ + msgs += "<li>Near line "+ failed[i].src.beginLine; + if(failed[i].msg != ""){ + msgs += ": " + failed[i].msg + "."; + } + msgs += "</li>" + } + msgs += "</ul>" + } + } else if(exceptions.length != 0){ + msgs = "exception occurred: "; + for(var i = 0; i < exceptions.length; i++){ + msgs += " " + exceptions[i]; + } + } + if(feedback != null){ + msgs += "\n<i>" + feedback + "</i>"; + } + $("#" + idFeedback).html("<p>" + msgs + "</p>"); + $("#" + idFeedback).show(100); + } + }, + "json"); + return false; + }); +} + +// ChoiceQuestion + +function visitChoiceQuestion(index){ + var id = $( this ).attr('id'); + var idGood = id + "-good"; + var idBad = id + "-bad" + var idForm = id + "-form"; + var idFeedback = id + "-feedback"; + var content = $( this ).html(); + $( this ).html("<form id='" + idForm + "'>" + + content + + submitCode(idGood, idBad, idFeedback) + + "</form><br>"); + $( this ).submit(function(event){ + validateChoiceQuestion(id,idGood,idBad,idFeedback) + return false; + }); +} + +function validateChoiceQuestion(id, idGood, idBad, idFeedback){ + event.preventDefault(); + $("#" + idBad).hide(100); + $("#" + idGood).hide(100); + $("#" + idFeedback).hide(100); + var checked = $('input[name=' + id + ']:checked'); + + if(checked.val() === "y"){ + $("#" + idGood).show(100); + } else { + $("#" + idBad).show(100); + } + var fb = checked.attr("feedback"); + $("#" + idFeedback).html("<p><i>" + fb + "</i></p>"); + $("#" + idFeedback).show(100); +} + +// ClickQuestion + +function visitClickQuestion(index){ + var id = $( this ).attr('id'); + var idGood = id + "-good"; + var idBad = id + "-bad" + var idForm = id + "-form"; + var idFeedback = id + "-feedback"; + var content = $( this ).html(); + $( this ).html("<form id='" + idForm + "'>" + + content + + submitCode(idGood, idBad, idFeedback) + + "</form><br>"); + $( this ).submit(function(event){ + validateClickQuestion(id,idGood,idBad,idFeedback) + return false; + }); +} + +function validateClickQuestion(id, idGood, idBad, idFeedback){ + event.preventDefault(); + $("#" + idBad).hide(100); + $("#" + idGood).hide(100); + $("#" + idFeedback).hide(100); + var missed = 0; + $("#" + id + " .clickable").each(function(index, object) { if($(object).attr('clicked') != "true") { missed++; }}); + + if(missed == 0){ + $("#" + idGood).show(100); + } else { + $("#" + idBad).show(100); + var msg = missed == 1 ? "You missed 1 click" : "You missed " + missed + " clicks."; + $("#" + idFeedback).html("<p><i>" + msg + "</i></p>"); + $("#" + idFeedback).show(100); + } +} + +// MoveQuestion + +var deltax = 20; +var deltay = 1; + +function visitMoveQuestion(index){ + var id = $( this ).attr('id'); + var idGood = id + "-good"; + var idBad = id + "-bad" + var idForm = id + "-form"; + var idFeedback = id + "-feedback"; + var content = $( this ).html(); + + $( this ).html("<form id='" + idForm + "' class='movable-code-form'>" + + content + + submitCode(idGood, idBad, idFeedback) + + "</form><br>"); + $( this ).submit(function(event){ + validateMoveQuestion(id,idGood,idBad,idFeedback) + return false; + }); + + // Custom grid + $("#" + id + " .movable-code").draggable({ + drag: function( event, ui ) { + var snapTolerance = $(this).draggable('option', 'snapTolerance'); + var topRemainder = ui.position.top % deltay; + var leftRemainder = ui.position.left % deltax; + + if (topRemainder <= snapTolerance) { + ui.position.top = ui.position.top - topRemainder; + } + + if (leftRemainder <= snapTolerance) { + ui.position.left = ui.position.left - leftRemainder; + } + }, + containment: "parent", + cursor: "pointer", + cursorAt: { top:0, left: 0 } + }); + + + + // $(".movable-code-form").submit(function(event){ + // validateMovedCode(event); + // return false; + // }); +} + +function inside(inner, outer){ + return inner.left > outer.left && + inner.top > outer.top && + inner.right < outer.right && + inner.bottom < outer.bottom; +} + +function above(upper, lower){ + return upper.bottom < lower.top; +} + +function near(y1, y2){ + return Math.abs(y1 - y2) < 3; +} + +function compare(r1, r2){ + return r1.top == r2.top ? 0 : (r1.bottom < r2.top) ? -1 : 1; +} + +function insideTarget(target, elem){ + var targetRect = target.getBoundingClientRect(); + var elemRect = elem.getBoundingClientRect(); + + return inside(elemRect, targetRect); +} + +function validateIndent(firstBox, box){ + var firstRect = firstBox.getBoundingClientRect(); + var boxRect = box.getBoundingClientRect(); + var indent = parseInt($(box).attr("indent")); + return boxRect.left - firstRect.left == indent * deltax; +} + +function validateMoveQuestion(id, idGood, idBad, idFeedback){ + event.preventDefault(); + $("#" + idBad).hide(100); + $("#" + idGood).hide(100); + $("#" + idFeedback).hide(100); + var mq = event.target.closest(".move-question"); + + var boxes = $(mq).find(".movable-code"); + boxes.each(function(index,box){ $(box).attr("placement", "none"); }); + + target = $(mq).find(".movable-code-target").get(0); + var insideboxes = boxes.filter(function(index, elem) { return insideTarget(target, elem); }); + + insideboxes = insideboxes.sort(function(x, y) { + return compare(x.getBoundingClientRect(), + y.getBoundingClientRect()); + }); + + var decoy = 0; + var wrong_placement = 0; + var wrong_indent = 0; + insideboxes.each(function(index, box){ + + var indexBox = parseInt($(box).attr("index")); + console.log("indexBox", indexBox, box); + + if(indexBox >= 0){ + var indentOK = index == 0 || validateIndent(insideboxes.get(0), box); + var indexOK = index == indexBox; + if(!indentOK){ + wrong_indent += 1; + } + if(!indexOK){ + wrong_placement += 1; + } + $(box).attr("placement", indexOK ? (indentOK ? "correct" : "wrong-indent") : "wrong"); + } else { + $(box).attr("placement", "wrong"); + decoy += 1; + } + }); + var missing = 0; + boxes.each(function(index, box){ + if($(box).attr("placement") === "none" && parseInt($(box).attr("index")) >= 0){ + missing += 1; + } + }); + if(decoy > 0 || wrong_placement > 0 || wrong_indent > 0 || missing > 0 ){ + $("#" + idBad).show(100); + var msg = ""; + if(decoy > 0){ + msg += incorrect_fragments(decoy, "decoy"); + } + if(missing){ + msg += incorrect_fragments(missing, "missing"); + } + if(wrong_placement > 0){ + msg += incorrect_fragments(wrong_placement, "incorrectly placed"); + } + if(wrong_indent > 0){ + msg += incorrect_fragments(wrong_indent, "incorrectly indented"); + } + + $("#" + idFeedback).html("<p><i>" + msg + "</i></p>"); + $("#" + idFeedback).show(100); + } else { + $("#" + idGood).show(100); + } +} + +function incorrect_fragments(n, msg){ + return n + " " + msg + " fragment" + (n == 1 ? "" : "s") + ". "; +} + +// ---- FactQuestion + +function visitFactQuestion(index){ + var id = $( this ).attr('id'); + var idGood = id + "-good"; + var idBad = id + "-bad" + var idForm = id + "-form"; + var idFeedback = id + "-feedback"; + var content = $( this ).html(); + var $this = $( this ); + + $( this ).html("<form id='" + idForm + "'>" + + content + + "<div style='clear:left;'/>" + + submitCode(idGood, idBad, idFeedback) + + "<br></form><br>"); + + $( ".sortableLeft" ).sortable({ + containment: "parent", + cursor: "pointer" + }).disableSelection(); + $( ".sortableRight" ).sortable({ + containment: "parent", + cursor: "pointer" + }).disableSelection(); + + $(this).find("li").addClass("ui-state-default"); + $( this ).submit(function(event){ + validateFactQuestion(id,idGood,idBad,idFeedback) + return false; + }); +} + +function validateFactQuestion(id, idGood, idBad, idFeedback){ + event.preventDefault(); + $("#" + idBad).hide(100); + $("#" + idGood).hide(100); + $("#" + idFeedback).hide(100); + var fq = event.target.closest(".fact-question"); + + var leftItems = $(fq).find(".sortableLeft li"); + var rightItems = $(fq).find(".sortableRight li"); + var wrong = 0; + if(leftItems.length == rightItems.length){ + leftItems.each(function(index, item){ + var indexLeft = parseInt($(item).attr("index")); + var indexRight = parseInt($(rightItems.get(index)).attr("index")); + if(indexLeft == indexRight){ + $(item).attr("placement", "correct"); + $(rightItems.get(index)).attr("placement", "correct"); + } else { + $(item).attr("placement", "wrong"); + $(rightItems.get(index)).attr("placement", "wrong"); + wrong += 1; + } + }); + } + if(wrong > 0){ + $("#" + idBad).show(100); + } else { + $("#" + idGood).show(100); + } + +} diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java new file mode 100644 index 00000000000..cca8c003ff0 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -0,0 +1,167 @@ +package org.rascalmpl.library.lang.rascal.tutor.repl; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import org.rascalmpl.ideservices.IDEServices; +import org.rascalmpl.interpreter.Evaluator; +import org.rascalmpl.library.Prelude; +import org.rascalmpl.library.util.PathConfig; +import org.rascalmpl.repl.RascalInterpreterREPL; +import org.rascalmpl.shell.ShellEvaluatorFactory; + +import io.usethesource.vallang.IList; +import io.usethesource.vallang.ISourceLocation; +import io.usethesource.vallang.IValue; + +public class TutorCommandExecutor { + private final RascalInterpreterREPL repl; + private final ByteArrayOutputStream shellStandardOutput; + private final ByteArrayOutputStream shellErrorOutput; + + public TutorCommandExecutor(PathConfig pcfg) throws IOException, URISyntaxException{ + shellStandardOutput = new ByteArrayOutputStream(); + shellErrorOutput = new ByteArrayOutputStream(); + ByteArrayInputStream shellInputNotUsed = new ByteArrayInputStream("***this inputstream should not be used***".getBytes()); + + repl = new RascalInterpreterREPL(false, false, null) { + @Override + protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, OutputStream stderr, IDEServices services) { + Evaluator eval = ShellEvaluatorFactory.getDefaultEvaluator(input, stdout, stderr); + eval.getConfiguration().setRascalJavaClassPathProperty(javaCompilerPathAsString(pcfg.getJavaCompilerPath())); + eval.setMonitor(services); + return eval; + } + }; + + repl.initialize(shellInputNotUsed, shellStandardOutput, shellErrorOutput, null); + repl.setMeasureCommandTime(false); + } + + private String javaCompilerPathAsString(IList javaCompilerPath) { + StringBuilder b = new StringBuilder(); + + for (IValue elem : javaCompilerPath) { + ISourceLocation loc = (ISourceLocation) elem; + + if (b.length() != 0) { + b.append(File.pathSeparatorChar); + } + + assert loc.getScheme().equals("file"); + String path = loc.getPath(); + if (path.startsWith("/") && path.contains(":\\")) { + // a windows path should drop the leading / + path = path.substring(1); + } + b.append(path); + } + + return b.toString(); + } + + public void reset() { + try { + // make sure previously unterminated commands are cleared up + repl.handleInput("", new HashMap<>(), new HashMap<>()); + } + catch (InterruptedException e) { + // nothing needed + } + repl.cleanEnvironment(); + shellStandardOutput.reset(); + shellErrorOutput.reset(); + } + + public String getPrompt() { + return repl.getPrompt(); + } + + public Map<String, String> eval(String line) throws InterruptedException, IOException { + Map<String, InputStream> output = new HashMap<>(); + Map<String, String> result = new HashMap<>(); + + repl.handleInput(line, output, new HashMap<>()); + + for (String mimeType : output.keySet()) { + InputStream content = output.get(mimeType); + + if (mimeType.startsWith("text/")) { + result.put(mimeType, Prelude.consumeInputStream(new InputStreamReader(content, StandardCharsets.UTF_8))); + } + else { + result.put(mimeType, uuencode(content)); + } + } + + result.put("application/rascal+stdout", getPrintedOutput()); + result.put("application/rascal+stderr", getErrorOutput()); + + return result; + } + + public String uuencode(InputStream content) throws IOException { + int BUFFER_SIZE = 3 * 512; + Base64.Encoder encoder = Base64.getEncoder(); + + try (BufferedInputStream in = new BufferedInputStream(content, BUFFER_SIZE); ) { + StringBuilder result = new StringBuilder(); + byte[] chunk = new byte[BUFFER_SIZE]; + int len = 0; + + // read multiples of 3 until not possible anymore + while ( (len = in.read(chunk)) == BUFFER_SIZE ) { + result.append( encoder.encodeToString(chunk) ); + } + + // read final chunk which is not a multiple of 3 + if ( len > 0 ) { + chunk = Arrays.copyOf(chunk,len); + result.append( encoder.encodeToString(chunk) ); + } + + return result.toString(); + } + } + + public boolean isStatementComplete(String line){ + return repl.isStatementComplete(line); + } + + private String getPrintedOutput() throws UnsupportedEncodingException{ + try { + repl.getOutputWriter().flush(); + String result = shellStandardOutput.toString(StandardCharsets.UTF_8.name()); + shellStandardOutput.reset(); + return result; + } + catch (UnsupportedEncodingException e) { + return ""; + } + } + + private String getErrorOutput() { + try { + repl.getErrorWriter().flush(); + String result = shellErrorOutput.toString(StandardCharsets.UTF_8.name()); + shellErrorOutput.reset(); + return result; + } + catch (UnsupportedEncodingException e) { + return ""; + } + } +} diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc new file mode 100644 index 00000000000..26a30103442 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc @@ -0,0 +1,68 @@ +module lang::rascal::tutor::repl::TutorCommandExecutor + +import util::Reflective; + +@synopsis{A closure-based object wrapper for Rascal REPL} +@description{ +Using an instance of CommandExecutor you can simulate the exact interactions +between a Rascal ((RascalShell:REPL)) user and the REPL. + +This was created to implement documentation pages with example REPL runs. +} +data CommandExecutor + = executor( + PathConfig pcfg, + str () prompt, + void () reset, + map[str mimeType, str content] (str command) eval + ); + +@synopsis{Instantiates a ((CommandExecutor)) to simulate a REPL} +@examples{ +It's funny that the current example is also executed by a CommandExecutor of the tutor compiler. +Here we use to show how it works: + +```rascal-shell +import lang::rascal::tutor::repl::TutorCommandExecutor; +import util::Reflective; +e = createExecutor(pathConfig()); +// now we can find the current prompt: +e.prompt(); +// and evaluate an assignment +e.eval("x = 1;"); +// look what a continuation prompt looks like: +e.eval("println(\"abc\"") +e.prompt() +// finish the command we started +e.eval(")") +} +@javaClass{org.rascalmpl.library.lang.rascal.tutor.repl.TutorCommandExecutorCreator} +java CommandExecutor createExecutor(PathConfig pcfg); + +test bool executorSmokeTest() { + exec = createExecutor(pathConfig()); + + if (exec.prompt() != "rascal\>") { + return false; + } + + output = exec.eval("import IO;"); + + if (output["text/plain"] != "ok\n") { + return false; + } + + exec.eval("println(\"haai\""); + + if (exec.prompt() != "\>\>\>\>\>\>\>") { + return false; + } + + output = exec.eval(")"); + + if (output["application/rascal+stdout"] != "haai\n") { + return false; + } + + return true; +} diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutorCreator.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutorCreator.java new file mode 100644 index 00000000000..b2e8de9f721 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutorCreator.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2022, Jurgen J. Vinju, Centrum Wiskunde & Informatica (NWO-I - CWI) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.rascalmpl.library.lang.rascal.tutor.repl; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.Map; + +import org.rascalmpl.exceptions.RuntimeExceptionFactory; +import org.rascalmpl.library.util.PathConfig; +import org.rascalmpl.values.IRascalValueFactory; +import org.rascalmpl.values.functions.IFunction; + +import io.usethesource.vallang.IConstructor; +import io.usethesource.vallang.IMapWriter; +import io.usethesource.vallang.IString; +import io.usethesource.vallang.type.Type; +import io.usethesource.vallang.type.TypeFactory; +import io.usethesource.vallang.type.TypeStore; + +/** + * This class marshalls between a virtual Rascal REPL and Rascal client code. + */ +public class TutorCommandExecutorCreator { + private final IRascalValueFactory vf; + private final Type promptType; + private final Type resetType; + private final Type evalType; + private final Type execConstructor; + + public TutorCommandExecutorCreator(IRascalValueFactory vf, TypeFactory tf, TypeStore ts) { + this.vf = vf; + promptType = tf.functionType(tf.stringType(), tf.tupleEmpty(), tf.tupleEmpty()); + resetType = tf.functionType(tf.voidType(), tf.tupleEmpty(), tf.tupleEmpty()); + evalType = tf.functionType(tf.mapType(tf.stringType(), tf.stringType()), tf.tupleType(tf.stringType()), tf.tupleEmpty()); + execConstructor = ts.lookupConstructor(ts.lookupAbstractDataType("CommandExecutor"), "executor").iterator().next(); + } + + public IConstructor createExecutor(IConstructor pathConfigCons) { + try { + PathConfig pcfg = new PathConfig(pathConfigCons); + TutorCommandExecutor repl = new TutorCommandExecutor(pcfg); + return vf.constructor(execConstructor, + pathConfigCons, + prompt(repl), + reset(repl), + eval(repl) + ); + } + catch (IOException | URISyntaxException e) { + throw RuntimeExceptionFactory.io(vf.string(e.getMessage())); + } + } + + IFunction prompt(TutorCommandExecutor exec) { + return vf.function(promptType, (args,kwargs) -> { + return vf.string(exec.getPrompt()); + }); + } + + IFunction reset(TutorCommandExecutor exec) { + return vf.function(resetType, (args, kwargs) -> { + exec.reset(); + return null; + }); + } + + IFunction eval(TutorCommandExecutor exec) { + return vf.function(evalType, (args, kwargs) -> { + try { + IString command = (IString) args[0]; + Map<String, String> output = exec.eval(command.getValue()); + IMapWriter mw = vf.mapWriter(); + + for (String mimeType : output.keySet()) { + mw.put(vf.string(mimeType), vf.string(output.get(mimeType))); + } + + return mw.done(); + } + catch (InterruptedException | IOException e) { + throw RuntimeExceptionFactory.io(vf.string(e.getMessage())); + } + }); + } +} From d7ff0eb93b8fe4ad335a4d802b285583265d5e4d Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 12 Oct 2022 20:48:33 +0200 Subject: [PATCH 002/120] relocated classes to their right package --- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java | 2 +- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc | 2 +- .../lang/rascal/tutor/repl/TutorCommandExecutorCreator.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index cca8c003ff0..3f4f314d17f 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -1,4 +1,4 @@ -package org.rascalmpl.library.lang.rascal.tutor.repl; +package lang.rascal.tutor.repl; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc index 26a30103442..88e62e1d5bc 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc @@ -36,7 +36,7 @@ e.prompt() // finish the command we started e.eval(")") } -@javaClass{org.rascalmpl.library.lang.rascal.tutor.repl.TutorCommandExecutorCreator} +@javaClass{lang.rascal.tutor.repl.TutorCommandExecutorCreator} java CommandExecutor createExecutor(PathConfig pcfg); test bool executorSmokeTest() { diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutorCreator.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutorCreator.java index b2e8de9f721..1898a68e525 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutorCreator.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutorCreator.java @@ -10,7 +10,7 @@ * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.rascalmpl.library.lang.rascal.tutor.repl; +package lang.rascal.tutor.repl; import java.io.IOException; import java.net.URISyntaxException; From 12b1cdbf5f311e41ce5a23748afeab7b73016bac Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 12 Oct 2022 20:48:49 +0200 Subject: [PATCH 003/120] relocated classes to their right package --- .../rascalmpl/tutor/lang/rascal/tutor/questions/Feedback.java | 2 +- .../tutor/lang/rascal/tutor/questions/QuestionCompiler.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/Feedback.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/Feedback.java index eeacc4bbf5c..816a3668e79 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/Feedback.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/Feedback.java @@ -10,7 +10,7 @@ * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.rascalmpl.library.lang.rascal.tutor.questions; +package lang.rascal.tutor.questions; import java.util.Random; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java index b41ed71614c..42eeb5407a9 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java @@ -10,7 +10,7 @@ * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.rascalmpl.library.lang.rascal.tutor.questions; +package lang.rascal.tutor.questions; import java.io.File; From f1d9dde725f4a18c1c00ddee49510156cf77fe20 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 12 Oct 2022 20:49:37 +0200 Subject: [PATCH 004/120] fixed broken image links with empty alt tags, and changed test setup to work in different project --- .../tutor/lang/rascal/tutor/Compiler.rsc | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 23593398883..b4f0bbebaf8 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -43,30 +43,27 @@ import lang::rascal::tutor::Output; import lang::rascal::tutor::Includer; import lang::rascal::\syntax::Rascal; - - - public PathConfig defaultConfig = pathConfig( - bin=|target://rascal/doc|, + bin=|home:///git/rascal/target/classes/doc|, srcs=[ - |project://rascal/src/org/rascalmpl/courses/Rascalopedia|, - |project://rascal/src/org/rascalmpl/courses/CompileTimeErrors|, - |project://rascal/src/org/rascalmpl/courses/RascalConcepts|, - |project://rascal/src/org/rascalmpl/courses/Recipes|, - |project://rascal/src/org/rascalmpl/courses/Tutor|, - |project://rascal/src/org/rascalmpl/courses/GettingStarted|, - |project://rascal/src/org/rascalmpl/courses/GettingHelp|, - |project://rascal/src/org/rascalmpl/courses/WhyRascal|, - |project://rascal/src/org/rascalmpl/courses/Rascal|, - |project://rascal/src/org/rascalmpl/courses/RascalShell|, - |project://rascal/src/org/rascalmpl/courses/RunTimeErrors|, - |project://rascal/src/org/rascalmpl/courses/Developers|, - |project://rascal/src/org/rascalmpl/library| + |home:///git/rascal/src/org/rascalmpl/courses/Rascalopedia|, + |home:///git/rascal/src/org/rascalmpl/courses/CompileTimeErrors|, + |home:///git/rascal/src/org/rascalmpl/courses/RascalConcepts|, + |home:///git/rascal/src/org/rascalmpl/courses/Recipes|, + |home:///git/rascal/src/org/rascalmpl/courses/Tutor|, + |home:///git/rascal/src/org/rascalmpl/courses/GettingStarted|, + |home:///git/rascal/src/org/rascalmpl/courses/GettingHelp|, + |home:///git/rascal/src/org/rascalmpl/courses/WhyRascal|, + |home:///git/rascal/src/org/rascalmpl/courses/Rascal|, + |home:///git/rascal/src/org/rascalmpl/courses/RascalShell|, + |home:///git/rascal/src/org/rascalmpl/courses/RunTimeErrors|, + |home:///git/rascal/src/org/rascalmpl/courses/Developers|, + |home:///git/rascal/src/org/rascalmpl/library| ]); public PathConfig onlyAPIconfig - = defaultConfig[srcs=[|project://rascal/src/org/rascalmpl/library|]]; + = defaultConfig[srcs=[|home:///git/rascal/src/org/rascalmpl/library|]]; public list[Message] lastErrors = []; @@ -86,11 +83,9 @@ public void defaultCompile() { list[Message] compile(PathConfig pcfg, CommandExecutor exec = createExecutor(pcfg)) { ind = createConceptIndex(pcfg); - return [*compileCourse(dropSlash(src), pcfg[currentRoot=src], exec, ind) | src <- pcfg.srcs]; + return [*compileCourse(src, pcfg[currentRoot=src], exec, ind) | src <- pcfg.srcs]; } -loc dropSlash(loc src) = src.file == "" ? src[path=src.path[..-1]] : src; - list[Message] compileCourse(loc root, PathConfig pcfg, CommandExecutor exec, Index ind) = compileDirectory(root, pcfg[currentRoot=root], exec, ind); @@ -312,10 +307,14 @@ list[Output] compileMarkdown([/^<prefix:.*>~<digits:[^~]*[^aeh-pr-vx0-9]+[^~]*>~ ]; @synopsis{Resolve [labeled]((links))} -list[Output] compileMarkdown([/^<prefix:.*>\[<title:[^\]]+>\]\(\(<link:[A-Za-z0-9\-\ \t\.\:]+>\)\)<postfix:.*>$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) { +list[Output] compileMarkdown([/^<prefix:.*>\[<title:[^\]]*>\]\(\(<link:[A-Za-z0-9\-\ \t\.\:]+>\)\)<postfix:.*>$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) { resolution = ind[removeSpaces(link)]; p2r = pathToRoot(pcfg.currentRoot, pcfg.currentFile); + if (trim(title) == "") { + title = link; + } + switch (resolution) { case {str u}: { u = /^\/assets/ := u ? u : "<p2r><u>"; @@ -438,7 +437,7 @@ list[Output] compileMarkdown([a:/^\-\-\-\s*$/, *str header, b:/^\-\-\-\s*$/, *st } catch value e: { switch(e) { - case IllegalTypeArgument(str x, str y) : e = "<x>, <y>"; + // case IllegalTypeArgument(str x, str y) : e = "<x>, <y>"; case IllegalArgument(value i) : e = "<i>"; case IO(str msg) : e = "<msg>"; case Java(str class, str msg) : e = "<class>: <msg>"; From 14c6e1cb5254b189d346e3660ca68c62449249fd Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 12 Oct 2022 20:52:58 +0200 Subject: [PATCH 005/120] callout feature: added a line post-processor stage which can replace call-outs in source text and itemized lists with unicode inverted circled digits --- .../tutor/lang/rascal/tutor/Compiler.rsc | 6 ++-- .../tutor/lang/rascal/tutor/Output.rsc | 31 +++++++++++++++++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index b4f0bbebaf8..114913bf6f5 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -136,7 +136,7 @@ list[Message] compileDirectory(loc d, PathConfig pcfg, CommandExecutor exec, Ind i.file = (i.file == i.parent[extension="md"].file) ? "index.md" : i.file; writeFile(pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, i)[extension="md"].path, - "<for (out(x) <- output) {><x> + "<for (line(x) <- output) {><x> '<}>" ); @@ -185,7 +185,7 @@ list[Message] compileRascalFile(loc m, PathConfig pcfg, CommandExecutor exec, In list[Output] output = generateAPIMarkdown(relativize(pcfg.currentRoot, m).parent.path, m, pcfg, exec, ind); writeFile(pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, m)[extension="md"].path, - "<for (out(x) <- output) {><x> + "<for (line(x) <- output) {><x> '<}>" ); @@ -205,7 +205,7 @@ list[Message] compileMarkdownFile(loc m, PathConfig pcfg, CommandExecutor exec, m.file = (m.file == m.parent[extension="md"].file) ? "index.md" : m.file; writeFile(pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, m)[extension="md"].path, - "<for (out(x) <- output) {><x> + "<for (line(x) <- output) {><x> '<}>" ); diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc index 1d2b9cb5730..3a683f49fe9 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc @@ -1,13 +1,40 @@ module lang::rascal::tutor::Output extend Message; +import ParseTree; +import String; data Output - = out(str content) + = line(str content) | err(Message message) | details(list[str] order) | search(list[str] contents, str fragment) | \docTag(str tagName, list[Output] output) ; -Output empty() = out(""); + +@synopsis{single line comment with a lonely callout} +Output out(/^<pre:.*><t:\/\*\/\s*\<\s*[0-9]\s*\>><post:\s*\*\/\s*>$/) = out("<pre> <callout(t)> <post>"); + +@synopsis{multi line comment with a lonely callout} +Output out(/^<pre:.*><t:\/\/\s*\<\s*[0-9]\s*\>><post:\s*>$/) = out("<pre> <callout(t)> <post>"); + +@synopsis{bullets with callouts} +Output out(/^<pre:\s*\*>\s+<t:\<\s*[0-9]\s*\>><post:.*>$/) = out("<pre><callout(trim(t))><post>"); + +@synopsis{callouts as bullets} +Output out(/^<pre:\s*><t:\<\s*[0-9]\s*\>><post:.*>$/) = out("<pre>*<callout(trim(t))><post>"); + +default Output out(str output) = line(output); + +Output empty() = line(""); + + +@synopsis{replace all characters by space, except the digits by callout digits} +str callout(str input) = visit(input) { + case /^<ix:[0-9]>/ => callout(toInt(ix)) + case /^./ => " " +}; + +str callout(0) = "⓿"; +str callout(int i) = "<char(0x2775 + i)>" when i >= 1 && i <= 9; From 08bc51495b261ab3f48065ed1f343dbd12dfb2bc Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 13 Oct 2022 11:36:46 +0200 Subject: [PATCH 006/120] fixed callouts in multiline comments and fixed multidigit callouts --- src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc index 3a683f49fe9..0a584545c7a 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc @@ -13,17 +13,17 @@ data Output ; -@synopsis{single line comment with a lonely callout} -Output out(/^<pre:.*><t:\/\*\/\s*\<\s*[0-9]\s*\>><post:\s*\*\/\s*>$/) = out("<pre> <callout(t)> <post>"); - @synopsis{multi line comment with a lonely callout} -Output out(/^<pre:.*><t:\/\/\s*\<\s*[0-9]\s*\>><post:\s*>$/) = out("<pre> <callout(t)> <post>"); +Output out(/^<pre:.*><t:\/\*\s*\<\s*[0-9]+\s*\>\s*\*\/><post:\s*>$/) = out("<pre> <callout(t)> <post>"); + +@synopsis{single line comment with a lonely callout} +Output out(/^<pre:.*><t:\/\/\s*\<\s*[0-9]+\s*\>><post:\s*>$/) = out("<pre> <callout(t)> <post>"); @synopsis{bullets with callouts} -Output out(/^<pre:\s*\*>\s+<t:\<\s*[0-9]\s*\>><post:.*>$/) = out("<pre><callout(trim(t))><post>"); +Output out(/^<pre:\s*\*>\s+<t:\<\s*[0-9]+\s*\>><post:.*>$/) = out("<pre><callout(trim(t))><post>"); @synopsis{callouts as bullets} -Output out(/^<pre:\s*><t:\<\s*[0-9]\s*\>><post:.*>$/) = out("<pre>*<callout(trim(t))><post>"); +Output out(/^<pre:\s*><t:\<\s*[0-9]+\s*\>><post:.*>$/) = out("<pre>*<callout(trim(t))><post>"); default Output out(str output) = line(output); From a171a36a0300bab74883f337c8ea0169cec18edf Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 17 Oct 2022 13:40:48 +0200 Subject: [PATCH 007/120] improved regex for callouts --- src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc index 0a584545c7a..b33d91bd2fd 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Output.rsc @@ -14,7 +14,7 @@ data Output @synopsis{multi line comment with a lonely callout} -Output out(/^<pre:.*><t:\/\*\s*\<\s*[0-9]+\s*\>\s*\*\/><post:\s*>$/) = out("<pre> <callout(t)> <post>"); +Output out(/<pre:.*><t:\/\*\s*\<\s*[0-9]*\s*\>\s*\*\/><post:.*>/) = out("<pre> <callout(t)> <post>"); @synopsis{single line comment with a lonely callout} Output out(/^<pre:.*><t:\/\/\s*\<\s*[0-9]+\s*\>><post:\s*>$/) = out("<pre> <callout(t)> <post>"); From 8f9b0f20ddfe97368fba3d0cd3ce0d29ed00f9c2 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 24 Oct 2022 13:58:35 +0200 Subject: [PATCH 008/120] forgot to add classloader to REPL evaluator that depends only on the explicit pathConfig that is passed to the create function --- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 3f4f314d17f..6e3f9e26901 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -22,6 +22,7 @@ import org.rascalmpl.library.util.PathConfig; import org.rascalmpl.repl.RascalInterpreterREPL; import org.rascalmpl.shell.ShellEvaluatorFactory; +import org.rascalmpl.uri.classloaders.SourceLocationClassLoader; import io.usethesource.vallang.IList; import io.usethesource.vallang.ISourceLocation; @@ -43,6 +44,7 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O Evaluator eval = ShellEvaluatorFactory.getDefaultEvaluator(input, stdout, stderr); eval.getConfiguration().setRascalJavaClassPathProperty(javaCompilerPathAsString(pcfg.getJavaCompilerPath())); eval.setMonitor(services); + eval.addClassLoader(new SourceLocationClassLoader(pcfg.getClassloaders(), System.class.getClassLoader())); return eval; } }; From ee750163653cc93fe0984d7b73a55025d56e219c Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 25 Oct 2022 16:09:13 +0200 Subject: [PATCH 009/120] added screenshot feature to tutor --- .../tutor/lang/rascal/tutor/Compiler.rsc | 40 ++++--------------- .../tutor/repl/TutorCommandExecutor.java | 18 +++++++-- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 114913bf6f5..a8333b6fb29 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -45,26 +45,11 @@ import lang::rascal::\syntax::Rascal; public PathConfig defaultConfig = pathConfig( - bin=|home:///git/rascal/target/classes/doc|, + bin=|target://rascal-tutor/docs|, srcs=[ - |home:///git/rascal/src/org/rascalmpl/courses/Rascalopedia|, - |home:///git/rascal/src/org/rascalmpl/courses/CompileTimeErrors|, - |home:///git/rascal/src/org/rascalmpl/courses/RascalConcepts|, - |home:///git/rascal/src/org/rascalmpl/courses/Recipes|, - |home:///git/rascal/src/org/rascalmpl/courses/Tutor|, - |home:///git/rascal/src/org/rascalmpl/courses/GettingStarted|, - |home:///git/rascal/src/org/rascalmpl/courses/GettingHelp|, - |home:///git/rascal/src/org/rascalmpl/courses/WhyRascal|, - |home:///git/rascal/src/org/rascalmpl/courses/Rascal|, - |home:///git/rascal/src/org/rascalmpl/courses/RascalShell|, - |home:///git/rascal/src/org/rascalmpl/courses/RunTimeErrors|, - |home:///git/rascal/src/org/rascalmpl/courses/Developers|, - |home:///git/rascal/src/org/rascalmpl/library| + |project://rascal-tutor/src/lang/rascal/tutor/examples/Test| ]); -public PathConfig onlyAPIconfig - = defaultConfig[srcs=[|home:///git/rascal/src/org/rascalmpl/library|]]; - public list[Message] lastErrors = []; public void defaultCompile() { @@ -499,7 +484,7 @@ list[Output] compileRascalShell(list[str] block, bool allowErrors, bool isContin result = output["text/plain"]?""; stderr = output["application/rascal+stderr"]?""; stdout = output["application/rascal+stdout"]?""; - html = output["text/html"]?""; + shot = output["application/rascal+screenshot"]?""; if (filterErrors(stderr) != "" && /cancelled/ !:= stderr) { for (allowErrors, str errLine <- split("\n", stderr)) { @@ -525,20 +510,11 @@ list[Output] compileRascalShell(list[str] block, bool allowErrors, bool isContin } } - if (html != "") { - // unwrap an iframe if this is an iframe - xml = readXML(html); - - if ("iframe"(_, src=/\"http:\/\/localhost:<port:[0-9]+>\"/) := xml) { - html = readFile(|http://localhost:<port>/index.html|); - } - - // otherwise just inline the html - append(out("\<div class=\"rascal-html-output\"\>")); - for (htmlLine <- split("\n", html)) { - append OUT : out(" <htmlLine>"); - } - append OUT : out("\</div\>"); + if (shot != "") { + checksum = md5Sum(shot); + targetFile = (pcfg.bin + "assets" + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, pcfg.currentFile) + checksum)[extension="png"]; + writeBase64(targetFile, shot); + append(out("![screenshot](<relativize(pcfg.bin, targetFile)>)")); } else if (result != "") { for (str resultLine <- split("\n", result)) { diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 6e3f9e26901..49dfd268d79 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -16,6 +16,10 @@ import java.util.HashMap; import java.util.Map; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.TakesScreenshot; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.firefox.FirefoxDriver; import org.rascalmpl.ideservices.IDEServices; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.library.Prelude; @@ -95,15 +99,23 @@ public String getPrompt() { public Map<String, String> eval(String line) throws InterruptedException, IOException { Map<String, InputStream> output = new HashMap<>(); Map<String, String> result = new HashMap<>(); - - repl.handleInput(line, output, new HashMap<>()); + Map<String, String> metadata = new HashMap<>(); + + repl.handleInput(line, output, metadata); for (String mimeType : output.keySet()) { InputStream content = output.get(mimeType); - if (mimeType.startsWith("text/")) { + if (mimeType.startsWith("text/plain")) { result.put(mimeType, Prelude.consumeInputStream(new InputStreamReader(content, StandardCharsets.UTF_8))); } + else if (metadata.get("URL") != null) { + WebDriver driver = new FirefoxDriver(); + driver.manage().window().maximize(); + driver.get(metadata.get("URL")); + TakesScreenshot screenshot = (TakesScreenshot) driver; + result.put("application/rascal+screenshot", screenshot.getScreenshotAs(OutputType.BASE64)); + } else { result.put(mimeType, uuencode(content)); } From 4caef41caaf0654a80339e571dad8457619c91c0 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 26 Oct 2022 12:00:50 +0200 Subject: [PATCH 010/120] added screenshot feature for Content results that appear on the output of the Tutor REPL --- .../tutor/lang/rascal/tutor/Compiler.rsc | 12 +++-- .../tutor/repl/TutorCommandExecutor.java | 54 ++++++++++++++++--- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index a8333b6fb29..d3748f398a1 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -32,7 +32,6 @@ import util::Reflective; import util::FileSystem; import ValueIO; -import lang::xml::IO; import lang::yaml::Model; import lang::rascal::tutor::repl::TutorCommandExecutor; import lang::rascal::tutor::apidoc::GenerateMarkdown; @@ -46,6 +45,7 @@ import lang::rascal::\syntax::Rascal; public PathConfig defaultConfig = pathConfig( bin=|target://rascal-tutor/docs|, + libs=[|lib://rascal|], srcs=[ |project://rascal-tutor/src/lang/rascal/tutor/examples/Test| ]); @@ -485,6 +485,7 @@ list[Output] compileRascalShell(list[str] block, bool allowErrors, bool isContin stderr = output["application/rascal+stderr"]?""; stdout = output["application/rascal+stdout"]?""; shot = output["application/rascal+screenshot"]?""; + png = output["image/png"]?""; if (filterErrors(stderr) != "" && /cancelled/ !:= stderr) { for (allowErrors, str errLine <- split("\n", stderr)) { @@ -511,10 +512,13 @@ list[Output] compileRascalShell(list[str] block, bool allowErrors, bool isContin } if (shot != "") { - checksum = md5Sum(shot); - targetFile = (pcfg.bin + "assets" + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, pcfg.currentFile) + checksum)[extension="png"]; + loc targetFile = pcfg.bin + "assets" + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, pcfg.currentFile)[extension=""].path; + targetFile.file = targetFile.file + "_screenshot_<lineOffset>.png"; + println("screenshot at <targetFile>"); writeBase64(targetFile, shot); - append(out("![screenshot](<relativize(pcfg.bin, targetFile)>)")); + append OUT: out("```"); + append OUT: out("![image](<relativize(pcfg.bin, targetFile)>)"); + append OUT: out("```rascal-shell"); } else if (result != "") { for (str resultLine <- split("\n", result)) { diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 49dfd268d79..b7752fe7fc9 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -20,6 +20,8 @@ import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; +import org.openqa.selenium.firefox.FirefoxOptions; +import org.openqa.selenium.firefox.FirefoxProfile; import org.rascalmpl.ideservices.IDEServices; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.library.Prelude; @@ -36,6 +38,7 @@ public class TutorCommandExecutor { private final RascalInterpreterREPL repl; private final ByteArrayOutputStream shellStandardOutput; private final ByteArrayOutputStream shellErrorOutput; + private final FirefoxDriver browser; public TutorCommandExecutor(PathConfig pcfg) throws IOException, URISyntaxException{ shellStandardOutput = new ByteArrayOutputStream(); @@ -48,13 +51,15 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O Evaluator eval = ShellEvaluatorFactory.getDefaultEvaluator(input, stdout, stderr); eval.getConfiguration().setRascalJavaClassPathProperty(javaCompilerPathAsString(pcfg.getJavaCompilerPath())); eval.setMonitor(services); - eval.addClassLoader(new SourceLocationClassLoader(pcfg.getClassloaders(), System.class.getClassLoader())); + // eval.addClassLoader(new SourceLocationClassLoader(pcfg.getClassloaders(), System.class.getClassLoader())); return eval; } }; repl.initialize(shellInputNotUsed, shellStandardOutput, shellErrorOutput, null); repl.setMeasureCommandTime(false); + + this.browser = getBrowser(); } private String javaCompilerPathAsString(IList javaCompilerPath) { @@ -109,16 +114,21 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce if (mimeType.startsWith("text/plain")) { result.put(mimeType, Prelude.consumeInputStream(new InputStreamReader(content, StandardCharsets.UTF_8))); } - else if (metadata.get("URL") != null) { - WebDriver driver = new FirefoxDriver(); - driver.manage().window().maximize(); - driver.get(metadata.get("URL")); - TakesScreenshot screenshot = (TakesScreenshot) driver; - result.put("application/rascal+screenshot", screenshot.getScreenshotAs(OutputType.BASE64)); - } else { result.put(mimeType, uuencode(content)); } + + FirefoxDriver browser = getBrowser(); + if (metadata.get("url") != null && browser != null) { + try { + browser.get(metadata.get("url")); + String screenshot = browser.getScreenshotAs(OutputType.BASE64); + result.put("application/rascal+screenshot", screenshot); + } + catch (Throwable e) { + shellErrorOutput.write(e.getMessage().getBytes("UTF-8")); + } + } } result.put("application/rascal+stdout", getPrintedOutput()); @@ -127,6 +137,34 @@ else if (metadata.get("URL") != null) { return result; } + private FirefoxDriver getBrowser() { + if (this.browser != null) { + return browser; + } + + FirefoxOptions options = new FirefoxOptions() + .setAcceptInsecureCerts(true) + .setHeadless(true) + ; + + // System.setProperty("webdriver.gecko.driver", "/Users/jurgenv/Downloads/geckodriver"); + + if (System.getProperty("webdriver.gecko.driver") == null) { + return null; + } + + FirefoxProfile profile = options.getProfile(); + profile.setPreference("layout.css.devPixelsPerPx", "10"); + + options = options.setProfile(profile); + + + FirefoxDriver driver = new FirefoxDriver(options); + driver.manage().window().maximize(); + + return driver; + } + public String uuencode(InputStream content) throws IOException { int BUFFER_SIZE = 3 * 512; Base64.Encoder encoder = Base64.getEncoder(); From 534f28f43890ff78f1dc98b9cc70282d6eae3b71 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 26 Oct 2022 14:15:52 +0200 Subject: [PATCH 011/120] improvement of screenshot feature --- .../tutor/lang/rascal/tutor/Compiler.rsc | 4 ++-- .../rascal/tutor/examples/Test/Vis/Vis.md | 23 +++++++++++++++++++ .../tutor/repl/TutorCommandExecutor.java | 9 ++++++-- 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index d3748f398a1..b5066e8518b 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -514,10 +514,10 @@ list[Output] compileRascalShell(list[str] block, bool allowErrors, bool isContin if (shot != "") { loc targetFile = pcfg.bin + "assets" + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, pcfg.currentFile)[extension=""].path; targetFile.file = targetFile.file + "_screenshot_<lineOffset>.png"; - println("screenshot at <targetFile>"); + println("screenshot <targetFile>"); writeBase64(targetFile, shot); append OUT: out("```"); - append OUT: out("![image](<relativize(pcfg.bin, targetFile)>)"); + append OUT: out("![image](<relativize(pcfg.bin, targetFile).path>)"); append OUT: out("```rascal-shell"); } else if (result != "") { diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md new file mode 100644 index 00000000000..7d7ddeef38e --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md @@ -0,0 +1,23 @@ +--- +title: Visuals Test +--- + +```rascal-shell +import lang::html::IO; +serve(p([text("This is"), b([text("bold")]), text("!")])) +``` + +```rascal-shell +import lang::html::IO; +HTMLElement table(rel[&T, &U] r) + = table([ + tr([ + td([text("<a>")]), + td([text("<b>")]) + ]) + | <a, b> <- r + ]); +characters = {"Sneezy", "Sleepy", "Dopey", "Doc", "Happy", "Bashful", "Grumpy"}; +serve(table(characters * characters)) +``` + diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index b7752fe7fc9..8b2f58f3435 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -16,6 +16,7 @@ import java.util.HashMap; import java.util.Map; +import org.openqa.selenium.By; import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; @@ -122,7 +123,11 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce if (metadata.get("url") != null && browser != null) { try { browser.get(metadata.get("url")); - String screenshot = browser.getScreenshotAs(OutputType.BASE64); + browser.manage().window().maximize(); + + String screenshot = browser.findElement(By.tagName("body")) + .getScreenshotAs(OutputType.BASE64); + result.put("application/rascal+screenshot", screenshot); } catch (Throwable e) { @@ -154,7 +159,7 @@ private FirefoxDriver getBrowser() { } FirefoxProfile profile = options.getProfile(); - profile.setPreference("layout.css.devPixelsPerPx", "10"); + profile.setPreference("layout.css.devPixelsPerPx", "3"); options = options.setProfile(profile); From 1f9f3e00c854165765fa999407e820b0aaa3bf14 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 26 Oct 2022 14:44:27 +0200 Subject: [PATCH 012/120] organized imports --- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 8b2f58f3435..cb8c0d5084e 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -18,8 +18,6 @@ import org.openqa.selenium.By; import org.openqa.selenium.OutputType; -import org.openqa.selenium.TakesScreenshot; -import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.firefox.FirefoxOptions; import org.openqa.selenium.firefox.FirefoxProfile; @@ -29,7 +27,6 @@ import org.rascalmpl.library.util.PathConfig; import org.rascalmpl.repl.RascalInterpreterREPL; import org.rascalmpl.shell.ShellEvaluatorFactory; -import org.rascalmpl.uri.classloaders.SourceLocationClassLoader; import io.usethesource.vallang.IList; import io.usethesource.vallang.ISourceLocation; @@ -124,7 +121,7 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce try { browser.get(metadata.get("url")); browser.manage().window().maximize(); - + String screenshot = browser.findElement(By.tagName("body")) .getScreenshotAs(OutputType.BASE64); From 27d6e7cec474460c70f138ac939ef2341367b68b Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 7 Nov 2022 11:24:41 +0100 Subject: [PATCH 013/120] better highlighting for function headers and aliases --- .../tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index a5c0c7edd97..9b4822e542c 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -83,7 +83,9 @@ list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, Pat [ out("## function <d.name> {<fragment(d.moduleName)>-<d.name>}"), Output::empty(), - *[out("* ``<removeNewlines(ov)>``") | ov <- overloads], + out("```rascal"), + *[out(ov), empty() | ov <- overloads, str defLine <- split("\n", ov)], + out("```"), Output::empty(), *tags2Markdown(d.docs, pcfg, exec, ind, dtls) ]; @@ -113,7 +115,9 @@ list[Output] declInfo2Doc(str parent, d:aliasInfo(), list[str] overloads, PathCo [ out("## alias <d.name> {<fragment(d.moduleName)>-<d.name>}"), empty(), - *[out("* `<removeNewlines(ov)>`") | ov <- overloads], + out("```rascal"), + *[out(removeNewlines(ov)), empty() | ov <- overloads], + out("```"), empty(), *tags2Markdown(d.docs, pcfg, exec, ind, dtls) ]; From 94800a58948fb0b7a0302ed87dfe9687d5f7480b Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 22 Nov 2022 16:20:34 +0100 Subject: [PATCH 014/120] added a sleep to make sure the charts are rendered before the screenshot is taken --- .../rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md | 1 + .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md index 7d7ddeef38e..e9882e46a66 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md @@ -21,3 +21,4 @@ characters = {"Sneezy", "Sleepy", "Dopey", "Doc", "Happy", "Bashful", "Grumpy"}; serve(table(characters * characters)) ``` + diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index cb8c0d5084e..4b9ca94789a 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -121,6 +121,8 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce try { browser.get(metadata.get("url")); browser.manage().window().maximize(); + // waiting for a better solution + Thread.sleep(1000); String screenshot = browser.findElement(By.tagName("body")) .getScreenshotAs(OutputType.BASE64); From 192adf2027a2d8fbdf2264e6e6a0fbb2114679b9 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 24 Nov 2022 11:44:47 +0100 Subject: [PATCH 015/120] fixed reference to library index files --- src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc index 0b65565fb02..cd3a52aecfd 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -24,7 +24,7 @@ Index createConceptIndex(PathConfig pcfg) { writeBinaryValueFile(pcfg.bin + "index.value", ind); // read indices from projects we depend on, if present - ind += {*readBinaryValueFile(#rel[str,str], inx) | l <- pcfg.libs, inx := l + "doc" + "index.value", exists(inx)}; + ind += {*readBinaryValueFile(#rel[str,str], inx) | l <- pcfg.libs, inx := l + "docs" + "index.value", exists(inx)}; return ind; } From bd4598fbed71d8e8b6b39c2f665df11349214933 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 1 Dec 2022 08:58:24 +0100 Subject: [PATCH 016/120] added dummy empty IDEServices just in case some module uses it during documentation construction --- .../tutor/repl/TutorCommandExecutor.java | 4 +- .../rascal/tutor/repl/TutorIDEServices.java | 56 +++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 4b9ca94789a..5da6d231776 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -42,7 +42,6 @@ public TutorCommandExecutor(PathConfig pcfg) throws IOException, URISyntaxExcept shellStandardOutput = new ByteArrayOutputStream(); shellErrorOutput = new ByteArrayOutputStream(); ByteArrayInputStream shellInputNotUsed = new ByteArrayInputStream("***this inputstream should not be used***".getBytes()); - repl = new RascalInterpreterREPL(false, false, null) { @Override protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, OutputStream stderr, IDEServices services) { @@ -54,7 +53,8 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O } }; - repl.initialize(shellInputNotUsed, shellStandardOutput, shellErrorOutput, null); + TutorIDEServices services = new TutorIDEServices(); + repl.initialize(shellInputNotUsed, shellStandardOutput, shellErrorOutput, services); repl.setMeasureCommandTime(false); this.browser = getBrowser(); diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java new file mode 100644 index 00000000000..ab9f363e49b --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java @@ -0,0 +1,56 @@ +package lang.rascal.tutor.repl; + +import java.io.PrintWriter; +import java.net.URI; + +import org.rascalmpl.ideservices.IDEServices; + +import io.usethesource.vallang.ISourceLocation; + +public class TutorIDEServices implements IDEServices { + + @Override + public void jobStart(String name, int workShare, int totalWork) { + + } + + @Override + public void jobStep(String name, String message, int workShare) { + + } + + @Override + public int jobEnd(String name, boolean succeeded) { + return 0; + } + + @Override + public boolean jobIsCanceled(String name) { + return false; + } + + @Override + public void jobTodo(String name, int work) { + + } + + @Override + public void warning(String message, ISourceLocation src) { + + } + + @Override + public PrintWriter stderr() { + return new PrintWriter(System.err); + } + + @Override + public void browse(URI uri) { + + } + + @Override + public void edit(ISourceLocation path) { + + } +} From d1d59e3aaa592ddd322e6817622017839f40d368 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 2 Dec 2022 13:36:07 +0100 Subject: [PATCH 017/120] switched from firefox to chrome to avoid crashing during screenshots of large canvasses --- .../tutor/repl/TutorCommandExecutor.java | 62 ++++++++++++------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 5da6d231776..6ba8def0adc 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -11,6 +11,7 @@ import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.Arrays; import java.util.Base64; import java.util.HashMap; @@ -18,9 +19,11 @@ import org.openqa.selenium.By; import org.openqa.selenium.OutputType; -import org.openqa.selenium.firefox.FirefoxDriver; -import org.openqa.selenium.firefox.FirefoxOptions; -import org.openqa.selenium.firefox.FirefoxProfile; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeDriverLogLevel; +import org.openqa.selenium.chrome.ChromeDriverService; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.remote.RemoteWebDriver; import org.rascalmpl.ideservices.IDEServices; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.library.Prelude; @@ -36,7 +39,9 @@ public class TutorCommandExecutor { private final RascalInterpreterREPL repl; private final ByteArrayOutputStream shellStandardOutput; private final ByteArrayOutputStream shellErrorOutput; - private final FirefoxDriver browser; + private final ChromeDriverService service; + private RemoteWebDriver driver; + public TutorCommandExecutor(PathConfig pcfg) throws IOException, URISyntaxException{ shellStandardOutput = new ByteArrayOutputStream(); @@ -52,12 +57,17 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O return eval; } }; - + TutorIDEServices services = new TutorIDEServices(); repl.initialize(shellInputNotUsed, shellStandardOutput, shellErrorOutput, services); repl.setMeasureCommandTime(false); - this.browser = getBrowser(); + this.service = new ChromeDriverService.Builder() + .usingDriverExecutable(new File(System.getProperty("webdriver.chrome.driver"))) + .usingAnyFreePort() + .build(); + + this.service.start(); } private String javaCompilerPathAsString(IList javaCompilerPath) { @@ -82,6 +92,7 @@ private String javaCompilerPathAsString(IList javaCompilerPath) { return b.toString(); } + public void reset() { try { // make sure previously unterminated commands are cleared up @@ -116,7 +127,8 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce result.put(mimeType, uuencode(content)); } - FirefoxDriver browser = getBrowser(); + RemoteWebDriver browser = getBrowser(); + if (metadata.get("url") != null && browser != null) { try { browser.get(metadata.get("url")); @@ -141,31 +153,33 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce return result; } - private FirefoxDriver getBrowser() { - if (this.browser != null) { - return browser; + private RemoteWebDriver getBrowser() { + // System.setProperty("webdriver.gecko.driver", "/Users/jurgenv/Downloads/geckodriver"); + if (this.driver != null) { + return this.driver; } + - FirefoxOptions options = new FirefoxOptions() - .setAcceptInsecureCerts(true) + ChromeOptions options = new ChromeOptions() .setHeadless(true) + .setBinary(System.getProperty("webdriver.chrome.browser")) + .addArguments("--user-data-dir=/tmp/rascal-config/google-chrome") + .setLogLevel(ChromeDriverLogLevel.OFF) ; - // System.setProperty("webdriver.gecko.driver", "/Users/jurgenv/Downloads/geckodriver"); - - if (System.getProperty("webdriver.gecko.driver") == null) { - return null; - } - - FirefoxProfile profile = options.getProfile(); - profile.setPreference("layout.css.devPixelsPerPx", "3"); - - options = options.setProfile(profile); - + // ?ChromeProfile profile = options.getProfile(); + // profile.setPreference("layout.css.devPixelsPerPx", "3"); + // options = options.setProfile(profile); - FirefoxDriver driver = new FirefoxDriver(options); + + RemoteWebDriver driver = new RemoteWebDriver(service.getUrl(), options); + driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(3)); + driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); + driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(5)); driver.manage().window().maximize(); + this.driver = driver; + return driver; } From 4c4a8c2834f053f275fbca72436adc4258c3749a Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 2 Dec 2022 13:44:52 +0100 Subject: [PATCH 018/120] added ignore feature using pcfg.ignores --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index b5066e8518b..e59f0231f86 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -75,6 +75,10 @@ list[Message] compileCourse(loc root, PathConfig pcfg, CommandExecutor exec, Ind = compileDirectory(root, pcfg[currentRoot=root], exec, ind); list[Message] compile(loc src, PathConfig pcfg, CommandExecutor exec, Index ind, int sidebar_position=-1) { + if (src in pcfg.ignores) { + return [info("skipped ignored location: <src>")]; + } + println("\rcompiling <src>"); // new concept, new execution environment: From f3134df1f6a4cff390beab938e98e8d87377666b Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 2 Dec 2022 16:09:41 +0100 Subject: [PATCH 019/120] better structure for the webdriver --- .../tutor/repl/TutorCommandExecutor.java | 68 +++++++++---------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 6ba8def0adc..e2c67f33af2 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -42,7 +42,6 @@ public class TutorCommandExecutor { private final ChromeDriverService service; private RemoteWebDriver driver; - public TutorCommandExecutor(PathConfig pcfg) throws IOException, URISyntaxException{ shellStandardOutput = new ByteArrayOutputStream(); shellErrorOutput = new ByteArrayOutputStream(); @@ -62,12 +61,37 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O repl.initialize(shellInputNotUsed, shellStandardOutput, shellErrorOutput, services); repl.setMeasureCommandTime(false); - this.service = new ChromeDriverService.Builder() - .usingDriverExecutable(new File(System.getProperty("webdriver.chrome.driver"))) - .usingAnyFreePort() - .build(); + String chromeBinary = System.getProperty("webdriver.chrome.driver"); + + if (chromeBinary != null) { + service = new ChromeDriverService.Builder() + .usingDriverExecutable(new File(System.getProperty("webdriver.chrome.driver"))) + .usingAnyFreePort() + .build(); + + service.start(); + + ChromeOptions options = new ChromeOptions() + .setHeadless(true) + .setBinary(chromeBinary) + .addArguments("--user-data-dir=/tmp/rascal-config/google-chrome") + .addArguments("--disable-dev-shm-usage") + .setLogLevel(ChromeDriverLogLevel.OFF) + ; + + RemoteWebDriver driver = new RemoteWebDriver(service.getUrl(), options); - this.service.start(); + driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(3)); + driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); + driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(5)); + driver.manage().window().maximize(); + + this.driver = driver; + service.stop(); + } + else { + this.service = null; + } } private String javaCompilerPathAsString(IList javaCompilerPath) { @@ -127,7 +151,7 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce result.put(mimeType, uuencode(content)); } - RemoteWebDriver browser = getBrowser(); + RemoteWebDriver browser = driver; if (metadata.get("url") != null && browser != null) { try { @@ -153,36 +177,6 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce return result; } - private RemoteWebDriver getBrowser() { - // System.setProperty("webdriver.gecko.driver", "/Users/jurgenv/Downloads/geckodriver"); - if (this.driver != null) { - return this.driver; - } - - - ChromeOptions options = new ChromeOptions() - .setHeadless(true) - .setBinary(System.getProperty("webdriver.chrome.browser")) - .addArguments("--user-data-dir=/tmp/rascal-config/google-chrome") - .setLogLevel(ChromeDriverLogLevel.OFF) - ; - - // ?ChromeProfile profile = options.getProfile(); - // profile.setPreference("layout.css.devPixelsPerPx", "3"); - // options = options.setProfile(profile); - - - RemoteWebDriver driver = new RemoteWebDriver(service.getUrl(), options); - driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(3)); - driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); - driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(5)); - driver.manage().window().maximize(); - - this.driver = driver; - - return driver; - } - public String uuencode(InputStream content) throws IOException { int BUFFER_SIZE = 3 * 512; Base64.Encoder encoder = Base64.getEncoder(); From 8b47a9f0c1f86f927c6f8c830c385fd6f3643d2c Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Sat, 3 Dec 2022 11:36:03 +0100 Subject: [PATCH 020/120] Revert "better structure for the webdriver" This reverts commit f3134df1f6a4cff390beab938e98e8d87377666b. --- .../tutor/repl/TutorCommandExecutor.java | 68 ++++++++++--------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index e2c67f33af2..6ba8def0adc 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -42,6 +42,7 @@ public class TutorCommandExecutor { private final ChromeDriverService service; private RemoteWebDriver driver; + public TutorCommandExecutor(PathConfig pcfg) throws IOException, URISyntaxException{ shellStandardOutput = new ByteArrayOutputStream(); shellErrorOutput = new ByteArrayOutputStream(); @@ -61,37 +62,12 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O repl.initialize(shellInputNotUsed, shellStandardOutput, shellErrorOutput, services); repl.setMeasureCommandTime(false); - String chromeBinary = System.getProperty("webdriver.chrome.driver"); - - if (chromeBinary != null) { - service = new ChromeDriverService.Builder() - .usingDriverExecutable(new File(System.getProperty("webdriver.chrome.driver"))) - .usingAnyFreePort() - .build(); - - service.start(); - - ChromeOptions options = new ChromeOptions() - .setHeadless(true) - .setBinary(chromeBinary) - .addArguments("--user-data-dir=/tmp/rascal-config/google-chrome") - .addArguments("--disable-dev-shm-usage") - .setLogLevel(ChromeDriverLogLevel.OFF) - ; - - RemoteWebDriver driver = new RemoteWebDriver(service.getUrl(), options); + this.service = new ChromeDriverService.Builder() + .usingDriverExecutable(new File(System.getProperty("webdriver.chrome.driver"))) + .usingAnyFreePort() + .build(); - driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(3)); - driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); - driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(5)); - driver.manage().window().maximize(); - - this.driver = driver; - service.stop(); - } - else { - this.service = null; - } + this.service.start(); } private String javaCompilerPathAsString(IList javaCompilerPath) { @@ -151,7 +127,7 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce result.put(mimeType, uuencode(content)); } - RemoteWebDriver browser = driver; + RemoteWebDriver browser = getBrowser(); if (metadata.get("url") != null && browser != null) { try { @@ -177,6 +153,36 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce return result; } + private RemoteWebDriver getBrowser() { + // System.setProperty("webdriver.gecko.driver", "/Users/jurgenv/Downloads/geckodriver"); + if (this.driver != null) { + return this.driver; + } + + + ChromeOptions options = new ChromeOptions() + .setHeadless(true) + .setBinary(System.getProperty("webdriver.chrome.browser")) + .addArguments("--user-data-dir=/tmp/rascal-config/google-chrome") + .setLogLevel(ChromeDriverLogLevel.OFF) + ; + + // ?ChromeProfile profile = options.getProfile(); + // profile.setPreference("layout.css.devPixelsPerPx", "3"); + // options = options.setProfile(profile); + + + RemoteWebDriver driver = new RemoteWebDriver(service.getUrl(), options); + driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(3)); + driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); + driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(5)); + driver.manage().window().maximize(); + + this.driver = driver; + + return driver; + } + public String uuencode(InputStream content) throws IOException { int BUFFER_SIZE = 3 * 512; Base64.Encoder encoder = Base64.getEncoder(); From b3c9ec961d7412cbbae12eabf470f448707c1e61 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 6 Dec 2022 10:44:02 +0100 Subject: [PATCH 021/120] make tutor also run correctly without screenshot feature, and add cleanup of external processes --- .../tutor/repl/TutorCommandExecutor.java | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 6ba8def0adc..62cbf2e4491 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -19,7 +19,6 @@ import org.openqa.selenium.By; import org.openqa.selenium.OutputType; -import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeDriverLogLevel; import org.openqa.selenium.chrome.ChromeDriverService; import org.openqa.selenium.chrome.ChromeOptions; @@ -36,11 +35,13 @@ import io.usethesource.vallang.IValue; public class TutorCommandExecutor { + private static final String BROWSER_BINARY = System.getProperty("webdriver.chrome.browser"); + private static final String DRIVER_BINARY = System.getProperty("webdriver.chrome.driver"); private final RascalInterpreterREPL repl; private final ByteArrayOutputStream shellStandardOutput; private final ByteArrayOutputStream shellErrorOutput; private final ChromeDriverService service; - private RemoteWebDriver driver; + private final RemoteWebDriver driver; public TutorCommandExecutor(PathConfig pcfg) throws IOException, URISyntaxException{ @@ -58,16 +59,29 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O } }; - TutorIDEServices services = new TutorIDEServices(); - repl.initialize(shellInputNotUsed, shellStandardOutput, shellErrorOutput, services); - repl.setMeasureCommandTime(false); - - this.service = new ChromeDriverService.Builder() - .usingDriverExecutable(new File(System.getProperty("webdriver.chrome.driver"))) - .usingAnyFreePort() - .build(); - - this.service.start(); + if (DRIVER_BINARY != null && BROWSER_BINARY != null) { + TutorIDEServices services = new TutorIDEServices(); + repl.initialize(shellInputNotUsed, shellStandardOutput, shellErrorOutput, services); + repl.setMeasureCommandTime(false); + + this.service = new ChromeDriverService.Builder() + .usingDriverExecutable(new File(DRIVER_BINARY)) + .usingAnyFreePort() + .build(); + + this.service.start(); + this.driver = getBrowser(service); + + // external browser and driver processes may still be running when we stop this VM: + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + driver.quit(); + service.stop(); + })); + } + else { + this.service = null; + this.driver = null; + } } private String javaCompilerPathAsString(IList javaCompilerPath) { @@ -127,16 +141,14 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce result.put(mimeType, uuencode(content)); } - RemoteWebDriver browser = getBrowser(); - - if (metadata.get("url") != null && browser != null) { + if (metadata.get("url") != null && driver != null) { try { - browser.get(metadata.get("url")); - browser.manage().window().maximize(); + driver.get(metadata.get("url")); + driver.manage().window().maximize(); // waiting for a better solution Thread.sleep(1000); - String screenshot = browser.findElement(By.tagName("body")) + String screenshot = driver.findElement(By.tagName("body")) .getScreenshotAs(OutputType.BASE64); result.put("application/rascal+screenshot", screenshot); @@ -153,16 +165,14 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce return result; } - private RemoteWebDriver getBrowser() { - // System.setProperty("webdriver.gecko.driver", "/Users/jurgenv/Downloads/geckodriver"); - if (this.driver != null) { - return this.driver; + private static RemoteWebDriver getBrowser(ChromeDriverService service) { + if (BROWSER_BINARY == null || DRIVER_BINARY == null) { + return null; } - ChromeOptions options = new ChromeOptions() .setHeadless(true) - .setBinary(System.getProperty("webdriver.chrome.browser")) + .setBinary(BROWSER_BINARY) .addArguments("--user-data-dir=/tmp/rascal-config/google-chrome") .setLogLevel(ChromeDriverLogLevel.OFF) ; @@ -171,15 +181,12 @@ private RemoteWebDriver getBrowser() { // profile.setPreference("layout.css.devPixelsPerPx", "3"); // options = options.setProfile(profile); - RemoteWebDriver driver = new RemoteWebDriver(service.getUrl(), options); driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(3)); driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(5)); driver.manage().window().maximize(); - this.driver = driver; - return driver; } From 0ae2bcb7a618d039efebaaed145cffcdeedb4cd2 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 6 Dec 2022 10:46:47 +0100 Subject: [PATCH 022/120] guarded cleanup against null references --- .../lang/rascal/tutor/repl/TutorCommandExecutor.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 62cbf2e4491..45d2a2d463b 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -74,8 +74,12 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O // external browser and driver processes may still be running when we stop this VM: Runtime.getRuntime().addShutdownHook(new Thread(() -> { - driver.quit(); - service.stop(); + if (driver != null) { + driver.quit(); + } + if (service != null) { + service.stop(); + } })); } else { From 3502ee4be62d46228a1ff6bb5492ece25634e245 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 6 Dec 2022 11:11:54 +0100 Subject: [PATCH 023/120] fixed initialization bug in version without screenshots --- .../lang/rascal/tutor/repl/TutorCommandExecutor.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 45d2a2d463b..49628ceb2b3 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -58,12 +58,12 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O return eval; } }; + + TutorIDEServices services = new TutorIDEServices(); + repl.initialize(shellInputNotUsed, shellStandardOutput, shellErrorOutput, services); + repl.setMeasureCommandTime(false); if (DRIVER_BINARY != null && BROWSER_BINARY != null) { - TutorIDEServices services = new TutorIDEServices(); - repl.initialize(shellInputNotUsed, shellStandardOutput, shellErrorOutput, services); - repl.setMeasureCommandTime(false); - this.service = new ChromeDriverService.Builder() .usingDriverExecutable(new File(DRIVER_BINARY)) .usingAnyFreePort() From 250b9535cc1a9ee289c3c97803033aed93f56cae Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 9 Dec 2022 12:40:09 +0100 Subject: [PATCH 024/120] fixes #7 --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index e59f0231f86..4c44acfe807 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -517,7 +517,7 @@ list[Output] compileRascalShell(list[str] block, bool allowErrors, bool isContin if (shot != "") { loc targetFile = pcfg.bin + "assets" + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, pcfg.currentFile)[extension=""].path; - targetFile.file = targetFile.file + "_screenshot_<lineOffset>.png"; + targetFile.file = targetFile.file + "_screenshot_<lineOffsetHere+lineOffset>.png"; println("screenshot <targetFile>"); writeBase64(targetFile, shot); append OUT: out("```"); From 042a6c91997c4ef3f7e3056fdb6bcc3af448266c Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 9 Dec 2022 12:42:02 +0100 Subject: [PATCH 025/120] added more opportunity to ignore --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 4c44acfe807..6717d9ac6eb 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -109,6 +109,10 @@ list[Message] compile(loc src, PathConfig pcfg, CommandExecutor exec, Index ind, } list[Message] compileDirectory(loc d, PathConfig pcfg, CommandExecutor exec, Index ind, int sidebar_position=-1) { + if (d in pcfg.ignores) { + return [info("skipped ignored location: <d>")]; + } + indexFiles = {(d + "<d.file>")[extension="md"], (d + "index.md")}; if (!exists(d)) { From 76017d3ea90e840f50eb9f0e55083a34b56383c9 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 9 Dec 2022 15:36:13 +0100 Subject: [PATCH 026/120] fixed broken links --- .../tutor/lang/rascal/tutor/Indexer.rsc | 22 +++++++++---------- .../tutor/lang/rascal/tutor/Names.rsc | 3 ++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc index cd3a52aecfd..0fe0274c9ae 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -147,27 +147,27 @@ rel[str, str] createConceptIndex(loc src) } + // Finally, the index entries for Rascal modules and declarations, as extracted from the source code: { // `((getDefaultPathConfig))` -> `Libary/util/Reflective#getDefaultPathConfig` - *{<"<item.kind>:<item.name>","/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>">, - <item.name, "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>" > | item.name?}, + *{<"<item.kind>:<item.name>","/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>">, + <item.name, "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" > | item.name?}, // `((Library:getDefaultPathConfig))` -> `/Library/util/Reflective#getDefaultPathConfig` - *{<"<capitalize(src.file)>:<item.name>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>" >, - <"<capitalize(src.file)>:<item.kind>:<item.name>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>" > | item.name?}, + *{<"<capitalize(src.file)>:<item.name>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" >, + <"<capitalize(src.file)>:<item.kind>:<item.name>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" > | item.name?}, // `((util::Reflective::getDefaultPathConfig))` -> `/Library/util/Reflective#getDefaultPathConfig` - *{<"<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>" >, - <"<item.kind>:<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>" > | item.name?, sep <- {"::", "/", "-"}}, + *{<"<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" >, + <"<item.kind>:<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" > | item.name?, sep <- {"::", "/", "-"}}, // ((Library:util::Reflective::getDefaultPathConfig))` -> `/Library/util/Reflective#getDefaultPathConfig` - *{<"<capitalize(src.file)>:<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>" >, - <"<capitalize(src.file)>:<item.kind>:<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md#<item.moduleName>-<item.name>" > | item.name?, sep <- {"::", "/", "-"}}, + *{<"<capitalize(src.file)>:<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" >, + <"<capitalize(src.file)>:<item.kind>:<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" > | item.name?, sep <- {"::", "/", "-"}}, // ((Set)) -> `/Library/Set` - *{<item.moduleName, "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md" >, <"module:<item.moduleName>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md" > | item is moduleInfo}, + *{<item.moduleName, "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" >, <"module:<item.moduleName>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" > | item is moduleInfo}, // `((Library:Set))` -> `/Library/Set` - *{<"<capitalize(src.file)>:<item.moduleName>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md" >, - <"<capitalize(src.file)>:module:<item.moduleName>", "/<capitalize(src.file)>/<moduleFragment(item.moduleName)>.md" > | item is moduleInfo} + *{<"<capitalize(src.file)>:<item.moduleName>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" >, + <"<capitalize(src.file)>:module:<item.moduleName>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" > | item is moduleInfo} | loc f <- find(src, "rsc"), list[DeclarationInfo] inf := safeExtract(f), item <- inf } diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc index e7b29b957d5..6c0f9e8a4d5 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc @@ -17,7 +17,8 @@ str fragment(loc root, loc concept) = fragment(root, concept + "index.md") str fragment(loc root, loc concept) = fragment(root, concept.parent + "index.md") when concept.parent?, concept.parent.file == concept[extension=""].file; -str moduleFragment(str moduleName) = "<replaceAll(moduleName, "::", "/")>"; +str modulePath(str moduleName) = "<replaceAll(moduleName, "::", "/")>"; +str moduleFragment(str moduleName) = "#<replaceAll(moduleName, "::", "-")>"; str removeSpaces(/^<prefix:.*><spaces:\s+><postfix:.*>$/) = removeSpaces("<prefix><capitalize(postfix)>"); From 7f606436a171692255772eacfc9259997c311c52 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 12 Dec 2022 10:36:46 +0100 Subject: [PATCH 027/120] moved back to finalizer because the clean up thread has already unloaded the classes we need to shutdown the external processes --- .../tutor/repl/TutorCommandExecutor.java | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 49628ceb2b3..d445b3a09f8 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -11,6 +11,7 @@ import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; import java.time.Duration; import java.util.Arrays; import java.util.Base64; @@ -71,16 +72,6 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O this.service.start(); this.driver = getBrowser(service); - - // external browser and driver processes may still be running when we stop this VM: - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - if (driver != null) { - driver.quit(); - } - if (service != null) { - service.stop(); - } - })); } else { this.service = null; @@ -88,6 +79,18 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O } } + @Override + protected void finalize() throws Throwable { + if (driver != null) { + driver.quit(); + driver = null; + } + if (service != null) { + service.stop(); + service = null; + } + } + private String javaCompilerPathAsString(IList javaCompilerPath) { StringBuilder b = new StringBuilder(); @@ -98,13 +101,13 @@ private String javaCompilerPathAsString(IList javaCompilerPath) { b.append(File.pathSeparatorChar); } + // this is the precondition assert loc.getScheme().equals("file"); - String path = loc.getPath(); - if (path.startsWith("/") && path.contains(":\\")) { - // a windows path should drop the leading / - path = path.substring(1); + + // this is robustness in case of experimentation in pom.xml + if ("file".equals(loc.getScheme())) { + b.append(Paths.get(loc.getURI()).toAbsolutePath().toString()); } - b.append(path); } return b.toString(); From 85f39aafeddfa2e901cf6f97afe668003bb74217 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 12 Dec 2022 10:38:57 +0100 Subject: [PATCH 028/120] fixed compiler errors --- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index d445b3a09f8..e1f6c7461e5 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -83,11 +83,9 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O protected void finalize() throws Throwable { if (driver != null) { driver.quit(); - driver = null; } if (service != null) { service.stop(); - service = null; } } From 273355fc2cd898559f03a2e637894ed4c24abae3 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 12 Dec 2022 16:54:12 +0100 Subject: [PATCH 029/120] running tutor outside of rascal project requires some fixes in the configuration of the evaluator --- .../tutor/lang/rascal/tutor/Compiler.rsc | 6 +- .../rascal/tutor/apidoc/DeclarationInfo.rsc | 1 + .../rascal/tutor/apidoc/GenerateMarkdown.rsc | 8 +-- .../tutor/repl/TutorCommandExecutor.java | 57 ++++++++++++++++++- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 6717d9ac6eb..e04d503a460 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -76,7 +76,7 @@ list[Message] compileCourse(loc root, PathConfig pcfg, CommandExecutor exec, Ind list[Message] compile(loc src, PathConfig pcfg, CommandExecutor exec, Index ind, int sidebar_position=-1) { if (src in pcfg.ignores) { - return [info("skipped ignored location: <src>")]; + return [info("skipped ignored location: <src>", src)]; } println("\rcompiling <src>"); @@ -110,9 +110,9 @@ list[Message] compile(loc src, PathConfig pcfg, CommandExecutor exec, Index ind, list[Message] compileDirectory(loc d, PathConfig pcfg, CommandExecutor exec, Index ind, int sidebar_position=-1) { if (d in pcfg.ignores) { - return [info("skipped ignored location: <d>")]; + return [info("skipped ignored location: <d>", d)]; } - + indexFiles = {(d + "<d.file>")[extension="md"], (d + "index.md")}; if (!exists(d)) { diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc index c9f0ca6a416..72ed5164894 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc @@ -11,6 +11,7 @@ data DeclarationInfo( loc docSrc = src) = moduleInfo (str kind="module") | functionInfo (str kind="function") + | testInfo (str kind="test", str fullTest="") | constructorInfo (str kind="constructor") | dataInfo (str kind="data", list[str] overloads=[]) | aliasInfo (str kind="alias") diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 9b4822e542c..12165549bb5 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -81,7 +81,7 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = [ - out("## function <d.name> {<fragment(d.moduleName)>-<d.name>}"), + out("## function <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), Output::empty(), out("```rascal"), *[out(ov), empty() | ov <- overloads, str defLine <- split("\n", ov)], @@ -96,7 +96,7 @@ list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, Pat list[Output] declInfo2Doc(str parent, d:dataInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = [ - out("## data <d.name> {<fragment(d.moduleName)>-<d.name>}"), + out("## data <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), empty(), *[ out("```rascal"), @@ -113,7 +113,7 @@ list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, Pat list[Output] declInfo2Doc(str parent, d:aliasInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = [ - out("## alias <d.name> {<fragment(d.moduleName)>-<d.name>}"), + out("## alias <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), empty(), out("```rascal"), *[out(removeNewlines(ov)), empty() | ov <- overloads], @@ -143,7 +143,7 @@ public str basename(str cn){ return (/^.*::<base:[A-Za-z0-9\-\_]+>$/ := cn) ? base : cn; } -private str fragment(str moduleName) = "#<replaceAll(moduleName, "::", "-")>"; + str removeNewlines(str x) = visit(x) { case /\n/ => " " diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index e1f6c7461e5..4efab1ebacc 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -26,14 +26,25 @@ import org.openqa.selenium.remote.RemoteWebDriver; import org.rascalmpl.ideservices.IDEServices; import org.rascalmpl.interpreter.Evaluator; +import org.rascalmpl.interpreter.env.GlobalEnvironment; +import org.rascalmpl.interpreter.env.ModuleEnvironment; +import org.rascalmpl.interpreter.load.StandardLibraryContributor; +import org.rascalmpl.interpreter.utils.RascalManifest; import org.rascalmpl.library.Prelude; import org.rascalmpl.library.util.PathConfig; import org.rascalmpl.repl.RascalInterpreterREPL; import org.rascalmpl.shell.ShellEvaluatorFactory; +import org.rascalmpl.uri.URIResolverRegistry; +import org.rascalmpl.uri.URIUtil; +import org.rascalmpl.uri.classloaders.SourceLocationClassLoader; +import org.rascalmpl.uri.project.ProjectURIResolver; +import org.rascalmpl.uri.project.TargetURIResolver; +import org.rascalmpl.values.ValueFactoryFactory; import io.usethesource.vallang.IList; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.IValue; +import io.usethesource.vallang.IValueFactory; public class TutorCommandExecutor { private static final String BROWSER_BINARY = System.getProperty("webdriver.chrome.browser"); @@ -52,10 +63,33 @@ public TutorCommandExecutor(PathConfig pcfg) throws IOException, URISyntaxExcept repl = new RascalInterpreterREPL(false, false, null) { @Override protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, OutputStream stderr, IDEServices services) { - Evaluator eval = ShellEvaluatorFactory.getDefaultEvaluator(input, stdout, stderr); + GlobalEnvironment heap = new GlobalEnvironment(); + ModuleEnvironment root = heap.addModule(new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap)); + IValueFactory vf = ValueFactoryFactory.getValueFactory(); + Evaluator eval = new Evaluator(vf, input, stderr, stdout, root, heap); + + eval.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); + eval.setMonitor(services); eval.getConfiguration().setRascalJavaClassPathProperty(javaCompilerPathAsString(pcfg.getJavaCompilerPath())); eval.setMonitor(services); - // eval.addClassLoader(new SourceLocationClassLoader(pcfg.getClassloaders(), System.class.getClassLoader())); + + ISourceLocation projectRoot = inferProjectRoot((ISourceLocation) pcfg.getSrcs().get(0)); + String projectName = new RascalManifest().getProjectName(projectRoot); + URIResolverRegistry reg = URIResolverRegistry.getInstance(); + reg.registerLogical(new ProjectURIResolver(projectRoot, projectName)); + reg.registerLogical(new TargetURIResolver(projectRoot, projectName)); + + for (IValue path : pcfg.getSrcs()) { + eval.addRascalSearchPath((ISourceLocation) path); + } + + for (IValue path : pcfg.getLibs()) { + eval.addRascalSearchPath((ISourceLocation) path); + } + + ClassLoader cl = new SourceLocationClassLoader(pcfg.getClassloaders(), ShellEvaluatorFactory.class.getClassLoader()); + eval.addClassLoader(cl); + return eval; } }; @@ -79,6 +113,25 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O } } + private static ISourceLocation inferProjectRoot(ISourceLocation member) { + ISourceLocation current = member; + URIResolverRegistry reg = URIResolverRegistry.getInstance(); + while (current != null && reg.exists(current) && reg.isDirectory(current)) { + if (reg.exists(URIUtil.getChildLocation(current, "META-INF/RASCAL.MF"))) { + return current; + } + + if (URIUtil.getParentLocation(current).equals(current)) { + // we went all the way up to the root + return reg.isDirectory(member) ? member : URIUtil.getParentLocation(member); + } + + current = URIUtil.getParentLocation(current); + } + + return current; + } + @Override protected void finalize() throws Throwable { if (driver != null) { From b4ebb37c7895b535789975075d07119d845b13ff Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 14 Dec 2022 12:15:43 +0100 Subject: [PATCH 030/120] sorting out the test functions in a module and documenting those with their bodies because those communicate how to use functions and what to expect of the output --- .../lang/rascal/tutor/apidoc/ExtractInfo.rsc | 15 ++++++ .../rascal/tutor/apidoc/GenerateMarkdown.rsc | 48 ++++++++++++++----- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc index 72b2ee213df..b9f16e2d910 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc @@ -90,6 +90,9 @@ DeclarationInfo genVariant(str moduleName, v: (Variant) `<Name name>(<{TypeArg " } list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<FunctionDeclaration functionDeclaration>`) + = [ extractTestDecl(moduleName, functionDeclaration) ] when /(FunctionModifier) `test` := functionDeclaration.signature; + +default list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<FunctionDeclaration functionDeclaration>`) = [ extractFunDecl(moduleName, functionDeclaration) ]; // -- function declaration ------------------------------------------ @@ -120,6 +123,18 @@ private DeclarationInfo extractFunctionDeclaration(str moduleName, FunctionDecla return functionInfo(moduleName=moduleName, name=fname, signature=signature, src=fd@\loc, synopsis=getSynopsis(tags), docs=sortedDocTags(tags)); } +DeclarationInfo extractTestDecl(str moduleName, FunctionDeclaration fd) { + fname = "<fd.signature.name>"; + + signature = "<fd.signature>"; + if(startsWith(signature, "java")){ + signature = signature[size("java")+1 .. ]; + } + + tags = getTagContents(fd.tags); + + return testInfo(moduleName=moduleName, name=fname, src=fd@\loc, synopsis=getSynopsis(tags), fullTest="<fd>"); +} str getSynopsis(rel[str, DocTag] tags) { if (docTag(content=str docContents) <- tags["doc"]) { diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 12165549bb5..839cbb6e905 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -28,6 +28,12 @@ list[Output] generateAPIMarkdown(str parent, loc moduleLoc, PathConfig pcfg, Com try { dinfo = extractInfo(moduleLoc); + // filter the tests + tests = [t | t:testInfo() <- dinfo]; + + // remove the tests + dinfo -= tests; + dtls = sort(dup(["<capitalize(pcfg.currentRoot.file)>:<i.kind>:<i.moduleName>::<i.name>" | DeclarationInfo i <- dinfo, !(i is moduleInfo)])); // TODO: this overloading collection should happen in ExtractInfo @@ -38,23 +44,29 @@ list[Output] generateAPIMarkdown(str parent, loc moduleLoc, PathConfig pcfg, Com list[str] overloads = []; if (dinfo[i] has name) { - overloads = [dinfo[i].signature]; - - // TODO: this only collects consecutive overloads. if a utility function interupts the flow, - // then we do not get to see the other overloads with the current group. Rewrite to use a "group-by" query. - // Also this looses any additional documentation tags for anything but the first overloaded declaration - - while (j < size(dinfo) && dinfo[i].name == dinfo[j].name) { - // this loops eats the other declarations with the same name (if consecutive!) - overloads += dinfo[j].signature; - j += 1; - } + overloads = [dinfo[i].signature]; + + // TODO: this only collects consecutive overloads. if a utility function interupts the flow, + // then we do not get to see the other overloads with the current group. Rewrite to use a "group-by" query. + // Also this looses any additional documentation tags for anything but the first overloaded declaration + + while (j < size(dinfo) && dinfo[i].name == dinfo[j].name) { + // this loops eats the other declarations with the same name (if consecutive!) + overloads += dinfo[j].signature; + j += 1; + } } res += declInfo2Doc(parent, dinfo[i], overloads, pcfg, exec, ind, dinfo[i] is moduleInfo? dtls : []); i = j; } + res += line("### Tests"); + + for (di <- tests) { + res += declInfo2Doc(parent, di, [], pcfg, exec, ind, []); + } + return res; } catch Java(_,_): @@ -88,8 +100,18 @@ list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, Pat out("```"), Output::empty(), *tags2Markdown(d.docs, pcfg, exec, ind, dtls) - ]; - + ]; + +list[Output] declInfo2Doc(str parent, d:testInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = + [ + out("## **test** <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), + Output::empty(), + out("```rascal"), + *[out(ov), empty() | ov <- overloads, str defLine <- split("\n", d.fullTest)], + out("```"), + Output::empty(), + *tags2Markdown(d.docs, pcfg, exec, ind, dtls) + ]; list[Output] declInfo2Doc(str parent, constructorInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = []; From 6f1212de65ad5ce80efcf42c1b64dcd4a0655bb3 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 14 Dec 2022 16:49:01 +0100 Subject: [PATCH 031/120] added the rascal-commands feature to execute code --- .../tutor/lang/rascal/tutor/Compiler.rsc | 40 +++++++++++++++++++ .../lang/rascal/tutor/apidoc/ExtractInfo.rsc | 2 +- .../rascal/tutor/examples/Test/Vis/Vis.md | 15 +++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index e04d503a460..ec86e789256 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -232,6 +232,46 @@ list[Output] compileMarkdown([str first:/^\s*```rascal-include<rest1:.*>$/, *str ]; } +@synopsis{Include Rascal REPL commands literally and execute them as side-effects in the REPL without reporting output unless there are unexpected errors.} +list[Output] compileMarkdown([str first:/^\s*```rascal-commands<rest1:.*>$/, *str block, /^\s*```/, *str rest2], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) { + str code = "<for (l <- block) {><l> + '<}>"; + + try { + commands = ([start[Commands]] code).top.commands; + + if (/continue/ !:= rest1) { + exec.reset(); + } + + stderr = ""; + + for (EvalCommand c <- commands) { + output = exec.eval("<c>"); + stderr += output["application/rascal+stderr"]?""; + } + + return [ + Output::empty(), // must have an empty line + out("```rascal <rest1>"), + *[out(l) | l <- block], + Output::empty(), + out("```"), + *[ + out(":::danger"), + *[out(errLine) | errLine <- split("\n", stderr)], + out(":::") + | /errors/ !:= rest1, filterErrors(stderr) != "" + ], + *[err(error("rascal-declare block failed: <stderr>", pcfg.currentFile(offset, 1, <line, 0>, <line, 1>))) | filterErrors(stderr) != ""], + *compileMarkdown(rest2, line + 1 + size(block) + 1, offset + length(first) + length(block), pcfg, exec, ind, dtls, sidebar_position=sidebar_position) + ]; + } + catch ParseError(x): { + return [err(error("parse error in rascal declaration input, <x>", pcfg.currentFile(offset, 1, <line, 0>, <line, 1>)))]; + } +} + @synopsis{execute _rascal-shell_ blocks on the REPL} list[Output] compileMarkdown([str first:/^\s*```rascal-shell<rest1:.*>$/, *block, /^\s*```/, *str rest2], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) = [ Output::empty(), // must have an empty line diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc index b9f16e2d910..6bb4a6c1954 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc @@ -90,7 +90,7 @@ DeclarationInfo genVariant(str moduleName, v: (Variant) `<Name name>(<{TypeArg " } list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<FunctionDeclaration functionDeclaration>`) - = [ extractTestDecl(moduleName, functionDeclaration) ] when /(FunctionModifier) `test` := functionDeclaration.signature; + = [ extractTestDecl(moduleName, functionDeclaration) ] when /FunctionModifier m := functionDeclaration.signature, (FunctionModifier) `test` := m; default list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<FunctionDeclaration functionDeclaration>`) = [ extractFunDecl(moduleName, functionDeclaration) ]; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md index e9882e46a66..baaf22fc852 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md @@ -7,6 +7,21 @@ import lang::html::IO; serve(p([text("This is"), b([text("bold")]), text("!")])) ``` +```rascal-commands, continue + +import IO; + +void exampleFunction() { + + println("HelloWorld!"); + +} +``` + +```rascal-shell,continue +exampleFunction(); +``` + ```rascal-shell import lang::html::IO; HTMLElement table(rel[&T, &U] r) From 3c87c93528c55e5b4f92e932000140ae31127586 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 16 Dec 2022 10:41:15 +0100 Subject: [PATCH 032/120] incrementalized the indexer, only new files are parsed and considered for indexing compared to the datetime of the old index.value dump --- .../tutor/lang/rascal/tutor/Compiler.rsc | 2 +- .../tutor/lang/rascal/tutor/Indexer.rsc | 43 ++++++++++++++----- .../rascal/tutor/examples/Test/Vis/Vis.md | 2 +- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index ec86e789256..530164e34f9 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -268,7 +268,7 @@ list[Output] compileMarkdown([str first:/^\s*```rascal-commands<rest1:.*>$/, *st ]; } catch ParseError(x): { - return [err(error("parse error in rascal declaration input, <x>", pcfg.currentFile(offset, 1, <line, 0>, <line, 1>)))]; + return [err(error("parse error in rascal-commands block: <x>", pcfg.currentFile(offset, 1, <line, 0>, <line, 1>)))]; } } diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc index 0fe0274c9ae..14a00323ca2 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -18,10 +18,17 @@ Index readConceptIndex(PathConfig pcfg) { } Index createConceptIndex(PathConfig pcfg) { - ind = createConceptIndex(pcfg.srcs); + targetFile = pcfg.bin + "index.value"; + ind = createConceptIndex(pcfg.srcs, exists(targetFile) ? lastModified(targetFile) : $1970-01-01T00:00:00.000+00:00$); + + if (exists(targetFile)) { + // in incremental mode we will have skipped many files. This + // adds the old index to the newly created ones + ind += readreadBinaryValueFile(#rel[str,str], targetFile); + } // store index for later usage by depending documentation projects - writeBinaryValueFile(pcfg.bin + "index.value", ind); + writeBinaryValueFile(targetFile, ind); // read indices from projects we depend on, if present ind += {*readBinaryValueFile(#rel[str,str], inx) | l <- pcfg.libs, inx := l + "docs" + "index.value", exists(inx)}; @@ -29,11 +36,11 @@ Index createConceptIndex(PathConfig pcfg) { return ind; } -rel[str, str] createConceptIndex(list[loc] srcs) - = {*createConceptIndex(src) | src <- srcs}; +rel[str, str] createConceptIndex(list[loc] srcs, datetime lastModified) + = {*createConceptIndex(src, lastModified) | src <- srcs}; @synopsis{creates a lookup table for concepts nested in a folder} -rel[str, str] createConceptIndex(loc src) +rel[str, str] createConceptIndex(loc src, datetime lastModified) = // first we collect index entries for concept names, each file is one concept which // can be linked to in many different ways ranging from very short (handy but inexact) to very long (guaranteed to be exact.) @@ -62,7 +69,7 @@ rel[str, str] createConceptIndex(loc src) // `((Rascal:Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` <"<capitalize(src.file)>:<replaceAll(capitalize(relativize(src, f.parent).path)[1..], "/", "-")>", fr> - | loc f <- find(src, isConceptFile) + | loc f <- find(src, isFreshConceptFile(lastModified)) , f.parent? , f.parent.path != "/" , f.parent != src @@ -91,7 +98,7 @@ rel[str, str] createConceptIndex(loc src) // `((Rascal:Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` <"<capitalize(src.file)>:<replaceAll(capitalize(relativize(src, cf).path)[1..], "/", "-")>", fr> - | loc f <- find(src, isConceptFile) + | loc f <- find(src, isFreshConceptFile(lastModified)) , f.parent? , f.parent.path != "/" , f.parent != src @@ -121,7 +128,7 @@ rel[str, str] createConceptIndex(loc src) // `((Rascal:Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` <"<capitalize(src.file)>:<replaceAll(capitalize(relativize(src, f).path)[1..], "/", "-")>", fr> - | loc f <- find(src, isDirectory) + | loc f <- find(src, isFreshDirectory(lastModified)) , fr := "/<capitalize(src.file)>/<fragment(src, f)>" , f != src } @@ -140,7 +147,7 @@ rel[str, str] createConceptIndex(loc src) <"<capitalize(src.file)>:<capitalize(replaceAll(relativize(src, f).path[1..], "/", "-"))>", fr>, <"<capitalize(src.file)>:package:<replaceAll(relativize(src, f).path[1..], "/", "::")>", fr>, <"<capitalize(src.file)>:<capitalize(replaceAll(relativize(src, f).path[1..], "/", "::"))>", fr> - | loc f <- find(src, isDirectory) + | loc f <- find(src, isFreshDirectory(lastModified)) , /\/internal\// !:= f.path , f != src , fr := "/<capitalize(src.file)>/<fragment(src, f)>" @@ -169,11 +176,27 @@ rel[str, str] createConceptIndex(loc src) *{<"<capitalize(src.file)>:<item.moduleName>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" >, <"<capitalize(src.file)>:module:<item.moduleName>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" > | item is moduleInfo} - | loc f <- find(src, "rsc"), list[DeclarationInfo] inf := safeExtract(f), item <- inf + | loc f <- find(src, isFreshRascalFile(lastModified)), list[DeclarationInfo] inf := safeExtract(f), item <- inf } ; private bool isConceptFile(loc f) = f.extension in {"md"}; + +private bool(loc) isFreshConceptFile(datetime lM) + = bool (loc f) { + return isConceptFile(f) && lastModified(f) > lM; + }; + +private bool(loc) isFreshDirectory(datetime lM) + = bool (loc d) { + return isDirectory(d) && lastModified(d) > lM; + }; + +private bool(loc) isFreshRascalFile(datetime lM) + = bool (loc f) { + return f.extension in {"rsc"} && lastModified(f) > lM; + }; + private bool isImageFile(loc f) = f.extension in {"png", "jpg", "svg", "jpeg"}; @synopsis{ignores extracting errors because they will be found later} diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md index baaf22fc852..39244ffd3da 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Vis.md @@ -7,7 +7,7 @@ import lang::html::IO; serve(p([text("This is"), b([text("bold")]), text("!")])) ``` -```rascal-commands, continue +```rascal-commands,continue import IO; From b4859e311a5d45f9f176786de0dba3fdf64e80b1 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 16 Dec 2022 12:47:03 +0100 Subject: [PATCH 033/120] incrementalized the compiler too, except for copying resources --- .../tutor/lang/rascal/tutor/Compiler.rsc | 120 +++++++++++++----- .../tutor/lang/rascal/tutor/Indexer.rsc | 3 +- .../lang/rascal/tutor/examples/Test/Test.md | 4 +- .../tutor/examples/Test/Vis/Example.rsc | 13 ++ 4 files changed, 108 insertions(+), 32 deletions(-) create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Example.rsc diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 530164e34f9..6ab422fd600 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -52,8 +52,10 @@ public PathConfig defaultConfig public list[Message] lastErrors = []; -public void defaultCompile() { - remove(defaultConfig.bin, recursive=true); +public void defaultCompile(bool clean=false) { + if (clean) { + remove(defaultConfig.bin, recursive=true); + } errors = compile(defaultConfig); for (e <- errors) { @@ -78,8 +80,6 @@ list[Message] compile(loc src, PathConfig pcfg, CommandExecutor exec, Index ind, if (src in pcfg.ignores) { return [info("skipped ignored location: <src>", src)]; } - - println("\rcompiling <src>"); // new concept, new execution environment: exec.reset(); @@ -95,6 +95,7 @@ list[Message] compile(loc src, PathConfig pcfg, CommandExecutor exec, Index ind, } else if (src.extension in {"png","jpg","svg","jpeg", "html", "js"}) { try { + println("copying <src> [Asset]"); copy(src, pcfg.bin + "assets" + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, src).path); return []; @@ -116,28 +117,48 @@ list[Message] compileDirectory(loc d, PathConfig pcfg, CommandExecutor exec, Ind indexFiles = {(d + "<d.file>")[extension="md"], (d + "index.md")}; if (!exists(d)) { - return [error("Course does not exist <d>", d)]; + return [warning("Course folder does not exist on disk: <d>", d)]; } output = []; + errors = []; nestedDtls = []; if (i <- indexFiles && exists(i)) { // this can only be a markdown file (see above) - output = compileMarkdown(i, pcfg[currentFile=i], exec, ind, sidebar_position=sidebar_position); - - i.file = (i.file == i.parent[extension="md"].file) ? "index.md" : i.file; - - writeFile(pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, i)[extension="md"].path, - "<for (line(x) <- output) {><x> - '<}>" - ); + j=i; + j.file = (j.file == j.parent[extension="md"].file) ? "index.md" : j.file; + targetFile = pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, j)[extension="md"].path; + + if (!exists(targetFile) || lastModified(i) > lastModified(targetFile)) { + println("compiling <i> [Index Markdown]"); + output = compileMarkdown(i, pcfg[currentFile=i], exec, ind, sidebar_position=sidebar_position); + + writeFile(targetFile, + "<for (line(x) <- output) {><x> + '<}>" + ); + + if (details(list[str] xxx) <- output) { + // here we give the details list declared in `details` header + // on to compute the right sidebar_positions down for the nested + // concepts + nestedDtls = xxx; + } - if (details(list[str] xxx) <- output) { - // here we give the details list declared in `details` header - // on to compute the right sidebar_positions down for the nested - // concepts - nestedDtls = xxx; + errors = [e | err(e) <- output]; + if (errors != []) { + writeBinaryValueFile(targetFile[extension="errors"], errors); + } + else { + remove(targetFile[extension="errors"]); + } + } + else { + println("reusing <i>"); + if (exists(targetFile[extension="errors"])) { + errors = readBinaryValueFile(#list[Message], targetFile[extension="errors"]); + } } } else { @@ -145,7 +166,7 @@ list[Message] compileDirectory(loc d, PathConfig pcfg, CommandExecutor exec, Ind } return [ - *[e | err(e) <- output], + *errors, *[*compile(s, pcfg, exec, ind, sidebar_position=sp) | s <- d.ls , !(s in indexFiles) @@ -175,14 +196,34 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) @synopsis{Translates Rascal source files to docusaurus markdown.} list[Message] compileRascalFile(loc m, PathConfig pcfg, CommandExecutor exec, Index ind) { - list[Output] output = generateAPIMarkdown(relativize(pcfg.currentRoot, m).parent.path, m, pcfg, exec, ind); + loc targetFile = pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, m)[extension="md"].path; + errors = []; - writeFile(pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, m)[extension="md"].path, + if (!exists(targetFile) || lastModified(targetFile) < lastModified(m)) { + println("compiling <m> [Rascal Source File]"); + list[Output] output = generateAPIMarkdown(relativize(pcfg.currentRoot, m).parent.path, m, pcfg, exec, ind); + + writeFile(targetFile, "<for (line(x) <- output) {><x> '<}>" - ); + ); + + errors = [e | err(e) <- output]; + if (errors != []) { + writeBinaryValueFile(targetFile[extension="errors"], errors); + } + else { + remove(targetFile[extension="errors"]); + } + } + else { + println("reusing <m>"); + if (exists(targetFile[extension="errors"])) { + errors = readBinaryValueFile(#list[Message], targetFile[extension=""]); + } + } - return [e | err(e) <- output]; + return errors; } @synopsis{This uses another nested directory listing to construct information for the TOC embedded in the current document.} @@ -192,17 +233,36 @@ list[str] createDetailsList(loc m, PathConfig pcfg) list[Message] compileMarkdownFile(loc m, PathConfig pcfg, CommandExecutor exec, Index ind, int sidebar_position=-1) { order = createDetailsList(m, pcfg); - list[Output] output = compileMarkdown(m, pcfg[currentFile=m], exec, ind, order, sidebar_position=sidebar_position) + [Output::empty()]; - // turn A/B/B.md into A/B/index.md for better URLs in the end result (`A/B/`` is better than `A/B/B.html`) m.file = (m.file == m.parent[extension="md"].file) ? "index.md" : m.file; - writeFile(pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, m)[extension="md"].path, - "<for (line(x) <- output) {><x> - '<}>" - ); + loc targetFile = pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, m)[extension="md"].path; + errors = []; + + if (!exists(targetFile) || lastModified(m) > lastModified(targetFile)) { + println("compiling <m> [Normal Markdown]"); + list[Output] output = compileMarkdown(m, pcfg[currentFile=m], exec, ind, order, sidebar_position=sidebar_position) + [Output::empty()]; + + writeFile(targetFile, + "<for (line(x) <- output) {><x> + '<}>" + ); + + errors = [e | err(e) <- output]; + if (errors != []) { + writeBinaryValueFile(targetFile[extension="errors"], errors); + } + return errors; + } + else { + println("reusing <m>"); + if (exists(targetFile[extension="errors"])) { + // keep reporting the errors of the previous run, for clarity's sake + return readBinaryValueFile(#list[Message], targetFile[extension="errors"]); + } + } - return [e | err(e) <- output]; + return []; } list[Output] compileMarkdown(loc m, PathConfig pcfg, CommandExecutor exec, Index ind, int sidebar_position=-1) { diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc index 14a00323ca2..fe144ae8663 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -5,6 +5,7 @@ import ValueIO; import String; import util::FileSystem; import IO; +import ValueIO; import Location; import lang::rascal::tutor::apidoc::DeclarationInfo; @@ -24,7 +25,7 @@ Index createConceptIndex(PathConfig pcfg) { if (exists(targetFile)) { // in incremental mode we will have skipped many files. This // adds the old index to the newly created ones - ind += readreadBinaryValueFile(#rel[str,str], targetFile); + ind += readBinaryValueFile(#rel[str,str], targetFile); } // store index for later usage by depending documentation projects diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Test.md b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Test.md index aa7c0946a8d..68a03c04c26 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Test.md +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Test.md @@ -2,7 +2,9 @@ This is a test synopsis. # Description -See examples below!!! +See examples below!!! nieuwe content + + # Examples diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Example.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Example.rsc new file mode 100644 index 00000000000..d42b6b6dc98 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Example.rsc @@ -0,0 +1,13 @@ +@synopsis{Just a module} +module Vis::Examples + +@synopsis{main is marvelous} +@description{ +```rascal-shell +import IO; +println("hai") +``` +} +int main() { + +} \ No newline at end of file From e8e2f777cd08558e9b836c2f9d061a9c66f86c8b Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 16 Dec 2022 13:04:55 +0100 Subject: [PATCH 034/120] fixed issue in incremental indexer --- .../rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc index fe144ae8663..ea4851bcea6 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -20,13 +20,13 @@ Index readConceptIndex(PathConfig pcfg) { Index createConceptIndex(PathConfig pcfg) { targetFile = pcfg.bin + "index.value"; - ind = createConceptIndex(pcfg.srcs, exists(targetFile) ? lastModified(targetFile) : $1970-01-01T00:00:00.000+00:00$); - if (exists(targetFile)) { - // in incremental mode we will have skipped many files. This - // adds the old index to the newly created ones - ind += readBinaryValueFile(#rel[str,str], targetFile); - } + // in incremental mode we will have skipped many files. This + // adds the old index to the newly created ones + ind = exists(targetFile) ? readConceptIndex(pcfg) : {}; + + // now we add the new index items on top of the old ones + ind += createConceptIndex(pcfg.srcs, exists(targetFile) ? lastModified(targetFile) : $1970-01-01T00:00:00.000+00:00$); // store index for later usage by depending documentation projects writeBinaryValueFile(targetFile, ind); From cc3462d36139514cdba15749179f704b154833f4 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 16 Dec 2022 15:28:06 +0100 Subject: [PATCH 035/120] removed directory modification time caching; was broken --- .../rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc index ea4851bcea6..5090327e669 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -28,7 +28,8 @@ Index createConceptIndex(PathConfig pcfg) { // now we add the new index items on top of the old ones ind += createConceptIndex(pcfg.srcs, exists(targetFile) ? lastModified(targetFile) : $1970-01-01T00:00:00.000+00:00$); - // store index for later usage by depending documentation projects + // store index for later usage by depending documentation projects, + // and for future runs of the compiler on the current project writeBinaryValueFile(targetFile, ind); // read indices from projects we depend on, if present @@ -129,7 +130,7 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified) // `((Rascal:Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` <"<capitalize(src.file)>:<replaceAll(capitalize(relativize(src, f).path)[1..], "/", "-")>", fr> - | loc f <- find(src, isFreshDirectory(lastModified)) + | loc f <- find(src, isDirectory) , fr := "/<capitalize(src.file)>/<fragment(src, f)>" , f != src } @@ -148,7 +149,7 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified) <"<capitalize(src.file)>:<capitalize(replaceAll(relativize(src, f).path[1..], "/", "-"))>", fr>, <"<capitalize(src.file)>:package:<replaceAll(relativize(src, f).path[1..], "/", "::")>", fr>, <"<capitalize(src.file)>:<capitalize(replaceAll(relativize(src, f).path[1..], "/", "::"))>", fr> - | loc f <- find(src, isFreshDirectory(lastModified)) + | loc f <- find(src, isDirectory) , /\/internal\// !:= f.path , f != src , fr := "/<capitalize(src.file)>/<fragment(src, f)>" @@ -188,11 +189,6 @@ private bool(loc) isFreshConceptFile(datetime lM) return isConceptFile(f) && lastModified(f) > lM; }; -private bool(loc) isFreshDirectory(datetime lM) - = bool (loc d) { - return isDirectory(d) && lastModified(d) > lM; - }; - private bool(loc) isFreshRascalFile(datetime lM) = bool (loc f) { return f.extension in {"rsc"} && lastModified(f) > lM; From e6eccdee2709ed0cc022ce2da372b70edd9b0466 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 16 Dec 2022 15:31:47 +0100 Subject: [PATCH 036/120] better error reporting on stdout with cause of failures in code blocks --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 6ab422fd600..f1370e14fff 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -601,7 +601,8 @@ list[Output] compileRascalShell(list[str] block, bool allowErrors, bool isContin } if (!allowErrors) { - append OUT : err(error("Code execution failed", pcfg.currentFile(offset, 1, <lineOffset + lineOffsetHere, 0>, <lineOffset + lineOffsetHere, 1>), cause=stderr)); + append OUT : err(error("Code execution failed: + ' <stderr>", pcfg.currentFile(offset, 1, <lineOffset + lineOffsetHere, 0>, <lineOffset + lineOffsetHere, 1>), cause=stderr)); append OUT : out("```"); append OUT : out(":::danger"); append OUT : out("Rascal code execution failed (unexpectedly) during compilation of this documentation."); @@ -610,6 +611,7 @@ list[Output] compileRascalShell(list[str] block, bool allowErrors, bool isContin for (errLine <- split("\n", stderr)) { append OUT : out(errLine); } + append OUT : out("```"); } } @@ -664,7 +666,8 @@ list[Output] compileRascalShellPrepare(list[str] block, bool isContinued, int li append OUT : out(errLine); } append OUT : out("\</pre\>"); - append OUT : err(error("Code execution failed in prepare block", pcfg.currentFile(offset, 1, <lineOffset + lineOffsetHere, 0>, <lineOffset + lineOffsetHere, 1>), cause=stderr)); + append OUT : err(error("Code execution failed in prepare block: + ' <stderr>", pcfg.currentFile(offset, 1, <lineOffset + lineOffsetHere, 0>, <lineOffset + lineOffsetHere, 1>), cause=stderr)); } lineOffsetHere +=1; From c9614709a162c9266d8f1b14dcfc0c12c8a2e990 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 28 Dec 2022 14:26:16 +0100 Subject: [PATCH 037/120] added config options and changed targetFile destinations for package documentation --- .../tutor/lang/rascal/tutor/Compiler.rsc | 30 ++++++++++++++++--- .../tutor/lang/rascal/tutor/Names.rsc | 8 ++++- .../rascal/tutor/apidoc/GenerateMarkdown.rsc | 7 ++++- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index f1370e14fff..9f5dfef4f7d 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -128,7 +128,11 @@ list[Message] compileDirectory(loc d, PathConfig pcfg, CommandExecutor exec, Ind // this can only be a markdown file (see above) j=i; j.file = (j.file == j.parent[extension="md"].file) ? "index.md" : j.file; - targetFile = pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, j)[extension="md"].path; + + targetFile = pcfg.bin + + (pcfg.isPackageCourse ? "Packages/<capitalize(pcfg.packageName)>" : "") + + (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"} ? "API" : capitalize(pcfg.currentRoot.file)) + + relativize(pcfg.currentRoot, j)[extension="md"].path; if (!exists(targetFile) || lastModified(i) > lastModified(targetFile)) { println("compiling <i> [Index Markdown]"); @@ -180,7 +184,15 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) try { p2r = pathToRoot(pcfg.currentRoot, d); title = replaceAll(relativize(pcfg.currentRoot, d).path[1..], "/", "::"); - writeFile(pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, d).path + "index.md", + + targetFile = pcfg.bin + + (pcfg.isPackageCourse ? "Packages/<capitalize(pcfg.packageName)>" : "") + + (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"} ? "API" : capitalize(pcfg.currentRoot.file)) + + relativize(pcfg.currentRoot, d).path + + "index.md" + ; + + writeFile(targetFile, "--- 'title: <if (trim(title) == "") {><capitalize(pcfg.currentRoot.file)><} else {><title><}> '<if (sidebar_position != -1) {>sidebar_position: <sidebar_position> @@ -196,7 +208,10 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) @synopsis{Translates Rascal source files to docusaurus markdown.} list[Message] compileRascalFile(loc m, PathConfig pcfg, CommandExecutor exec, Index ind) { - loc targetFile = pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, m)[extension="md"].path; + loc targetFile = pcfg.bin + + (pcfg.isPackageCourse ? "Packages/<capitalize(pcfg.packageName)>" : "") + + (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"} ? "API" : capitalize(pcfg.currentRoot.file)) + + relativize(pcfg.currentRoot, m)[extension="md"].path; errors = []; if (!exists(targetFile) || lastModified(targetFile) < lastModified(m)) { @@ -236,7 +251,11 @@ list[Message] compileMarkdownFile(loc m, PathConfig pcfg, CommandExecutor exec, // turn A/B/B.md into A/B/index.md for better URLs in the end result (`A/B/`` is better than `A/B/B.html`) m.file = (m.file == m.parent[extension="md"].file) ? "index.md" : m.file; - loc targetFile = pcfg.bin + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, m)[extension="md"].path; + loc targetFile = pcfg.bin + + (pcfg.isPackageCourse ? "Packages/<capitalize(pcfg.packageName)>" : "") + + (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"} ? "API" : capitalize(pcfg.currentRoot.file)) + + relativize(pcfg.currentRoot, m)[extension="md"].path; + errors = []; if (!exists(targetFile) || lastModified(m) > lastModified(targetFile)) { @@ -525,6 +544,9 @@ list[Output] compileMarkdown([a:/^\-\-\-\s*$/, *str header, b:/^\-\-\-\s*$/, *st *[out(l) | l <- header], *[out("sidebar_position: <sidebar_position>") | sidebar_position != -1], out("---"), + out(":::tip"), + out("rascal-<getRascalVersion()><if (pcfg.isPackageCourse) {>, <pcfg.packageName>-<pcfg.packageVersion><}>."), + out(":::"), *compileMarkdown(rest, line + 2 + size(header), offset + size(a) + size(b) + length(header), pcfg, exec, ind, dtls, sidebar_position=sidebar_position) ]; } diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc index 6c0f9e8a4d5..34b343f2674 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc @@ -4,11 +4,17 @@ import String; import Location; import List; import IO; +import util::Reflective; + +data PathConfig( + str packageName="", + str packageVersion=getRascalVersion(), + bool isPackageCourse=false +); data PathConfig(loc currentRoot = |unknown:///|, loc currentFile = |unknown:///|); data Message(str cause=""); - default str fragment(loc root, loc concept) = capitalize(relativize(root, concept).path)[1..]; str fragment(loc root, loc concept) = fragment(root, concept + "index.md") diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 839cbb6e905..dd48488d812 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -37,7 +37,8 @@ list[Output] generateAPIMarkdown(str parent, loc moduleLoc, PathConfig pcfg, Com dtls = sort(dup(["<capitalize(pcfg.currentRoot.file)>:<i.kind>:<i.moduleName>::<i.name>" | DeclarationInfo i <- dinfo, !(i is moduleInfo)])); // TODO: this overloading collection should happen in ExtractInfo - res = []; + res = [ + ]; int i = 0; while (i < size(dinfo)) { j = i + 1; @@ -83,6 +84,10 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC out("title: \"module <escape(d.moduleName, escapes)>\""), out("---"), Output::empty(), + out(":::tip"), + out("rascal-<getRascalVersion()><if (pcfg.isPackageCourse) {>, <pcfg.packageName>-<pcfg.packageVersion><}>."), + out(":::"), + Output::empty(), out("#### Usage"), Output::empty(), out("`import <replaceAll(d.name, "/", "::")>;`"), From 50450194270ad5812e594f7c4c257d9668fbe589 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 28 Dec 2022 14:49:40 +0100 Subject: [PATCH 038/120] moved links and relative paths for package courses --- .../tutor/lang/rascal/tutor/Compiler.rsc | 6 +-- .../tutor/lang/rascal/tutor/Indexer.rsc | 48 ++++++++++--------- .../tutor/lang/rascal/tutor/Names.rsc | 12 ++--- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 9f5dfef4f7d..e80044c8170 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -182,7 +182,7 @@ list[Message] compileDirectory(loc d, PathConfig pcfg, CommandExecutor exec, Ind list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) { try { - p2r = pathToRoot(pcfg.currentRoot, d); + p2r = pathToRoot(pcfg.currentRoot, d, pcfg.isPackageCourse); title = replaceAll(relativize(pcfg.currentRoot, d).path[1..], "/", "::"); targetFile = pcfg.bin @@ -421,7 +421,7 @@ list[Output] compileMarkdown([/^<prefix:.*>~<digits:[^~]*[^aeh-pr-vx0-9]+[^~]*>~ @synopsis{Resolve [labeled]((links))} list[Output] compileMarkdown([/^<prefix:.*>\[<title:[^\]]*>\]\(\(<link:[A-Za-z0-9\-\ \t\.\:]+>\)\)<postfix:.*>$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) { resolution = ind[removeSpaces(link)]; - p2r = pathToRoot(pcfg.currentRoot, pcfg.currentFile); + p2r = pathToRoot(pcfg.currentRoot, pcfg.currentFile, pcfg.isPackageCourse); if (trim(title) == "") { title = link; @@ -474,7 +474,7 @@ list[Output] compileMarkdown([/^<prefix:.*>\[<title:[^\]]*>\]\(\(<link:[A-Za-z0- @synopsis{Resolve unlabeled links} default list[Output] compileMarkdown([/^<prefix:.*>\(\(<link:[A-Za-z0-9\-\ \t\.\:]+>\)\)<postfix:.*>$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) { resolution = ind[removeSpaces(link)]; - p2r = pathToRoot(pcfg.currentRoot, pcfg.currentFile); + p2r = pathToRoot(pcfg.currentRoot, pcfg.currentFile, pcfg.isPackageCourse); switch (resolution) { case {u}: { diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc index 5090327e669..8246a551755 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -26,7 +26,7 @@ Index createConceptIndex(PathConfig pcfg) { ind = exists(targetFile) ? readConceptIndex(pcfg) : {}; // now we add the new index items on top of the old ones - ind += createConceptIndex(pcfg.srcs, exists(targetFile) ? lastModified(targetFile) : $1970-01-01T00:00:00.000+00:00$); + ind += createConceptIndex(pcfg.srcs, exists(targetFile) ? lastModified(targetFile) : $1970-01-01T00:00:00.000+00:00$, pcfg.isPackageCourse, pcfg.packageName); // store index for later usage by depending documentation projects, // and for future runs of the compiler on the current project @@ -38,17 +38,17 @@ Index createConceptIndex(PathConfig pcfg) { return ind; } -rel[str, str] createConceptIndex(list[loc] srcs, datetime lastModified) - = {*createConceptIndex(src, lastModified) | src <- srcs}; +rel[str, str] createConceptIndex(list[loc] srcs, datetime lastModified, bool isPackageCourse, str packageName) + = {*createConceptIndex(src, lastModified, isPackageCourse, packageName) | src <- srcs}; @synopsis{creates a lookup table for concepts nested in a folder} -rel[str, str] createConceptIndex(loc src, datetime lastModified) +rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageCourse, str packageName) = // first we collect index entries for concept names, each file is one concept which // can be linked to in many different ways ranging from very short (handy but inexact) to very long (guaranteed to be exact.) // First we handle the root concept { - <capitalize(src.file), "/<capitalize(src.file)>/index.md"> + <capitalize(src.file), "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/index.md"> } + // Then we handle the cases where the concept name is the same as the folder it is nested in: @@ -76,7 +76,7 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified) , f.parent.path != "/" , f.parent != src , f.parent.file == f[extension=""].file - , fr := "/<capitalize(src.file)>/<fragment(src, f)>" + , fr := "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<fragment(src, f)>" , cf := f[extension=""] } + @@ -105,7 +105,7 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified) , f.parent.path != "/" , f.parent != src , f.parent.file != f[extension=""].file - , fr := "/<capitalize(src.file)>/<fragment(src, f)>" + , fr := "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<fragment(src, f)>" , cf := f[extension=""] } + @@ -131,16 +131,17 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified) // `((Rascal:Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` <"<capitalize(src.file)>:<replaceAll(capitalize(relativize(src, f).path)[1..], "/", "-")>", fr> | loc f <- find(src, isDirectory) - , fr := "/<capitalize(src.file)>/<fragment(src, f)>" + , fr := "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<fragment(src, f)>" , f != src } + + // Now follow the index entries for image files: - { <"<f.parent.file>-<f.file>", "/assets/<capitalize(src.file)><relativize(src, f).path>">, - <f.file, "/assets/<capitalize(src.file)><relativize(src, f).path>">, - <"<capitalize(src.file)>:<f.file>", "/assets/<capitalize(src.file)><relativize(src, f).path>"> - | loc f <- find(src, isImageFile) + { <"<f.parent.file>-<f.file>", fr>, + <f.file, fr>, + <"<capitalize(src.file)>:<f.file>", fr> + | loc f <- find(src, isImageFile), + fr := "/assets/<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}><relativize(src, f).path>" } + { // these are links to packages/folders/directories via module path prefixes, like `analysis::m3` <"<replaceAll(relativize(src, f).path[1..], "/", "::")>", fr>, @@ -152,33 +153,34 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified) | loc f <- find(src, isDirectory) , /\/internal\// !:= f.path , f != src - , fr := "/<capitalize(src.file)>/<fragment(src, f)>" + , fr := "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<fragment(src, f)>" } + // Finally, the index entries for Rascal modules and declarations, as extracted from the source code: { // `((getDefaultPathConfig))` -> `Libary/util/Reflective#getDefaultPathConfig` - *{<"<item.kind>:<item.name>","/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>">, - <item.name, "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" > | item.name?}, + *{<"<item.kind>:<item.name>", fr>, + <item.name, fr > | item.name?}, // `((Library:getDefaultPathConfig))` -> `/Library/util/Reflective#getDefaultPathConfig` - *{<"<capitalize(src.file)>:<item.name>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" >, - <"<capitalize(src.file)>:<item.kind>:<item.name>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" > | item.name?}, + *{<"<capitalize(src.file)>:<item.name>", fr >, + <"<capitalize(src.file)>:<item.kind>:<item.name>", fr > | item.name?}, // `((util::Reflective::getDefaultPathConfig))` -> `/Library/util/Reflective#getDefaultPathConfig` - *{<"<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" >, - <"<item.kind>:<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" > | item.name?, sep <- {"::", "/", "-"}}, + *{<"<item.moduleName><sep><item.name>", fr >, + <"<item.kind>:<item.moduleName><sep><item.name>", fr > | item.name?, sep <- {"::", "/", "-"}}, // ((Library:util::Reflective::getDefaultPathConfig))` -> `/Library/util/Reflective#getDefaultPathConfig` - *{<"<capitalize(src.file)>:<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" >, - <"<capitalize(src.file)>:<item.kind>:<item.moduleName><sep><item.name>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" > | item.name?, sep <- {"::", "/", "-"}}, + *{<"<capitalize(src.file)>:<item.moduleName><sep><item.name>", fr >, + <"<capitalize(src.file)>:<item.kind>:<item.moduleName><sep><item.name>", fr > | item.name?, sep <- {"::", "/", "-"}}, // ((Set)) -> `/Library/Set` - *{<item.moduleName, "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" >, <"module:<item.moduleName>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" > | item is moduleInfo}, + *{<item.moduleName, "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" >, <"module:<item.moduleName>", "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" > | item is moduleInfo}, // `((Library:Set))` -> `/Library/Set` *{<"<capitalize(src.file)>:<item.moduleName>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" >, <"<capitalize(src.file)>:module:<item.moduleName>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" > | item is moduleInfo} - | loc f <- find(src, isFreshRascalFile(lastModified)), list[DeclarationInfo] inf := safeExtract(f), item <- inf + | loc f <- find(src, isFreshRascalFile(lastModified)), list[DeclarationInfo] inf := safeExtract(f), item <- inf, + fr := "/<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" } ; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc index 34b343f2674..9a740165199 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc @@ -45,11 +45,11 @@ str addSpaces(/^<prefix:[A-Za-z0-9\ ]+[a-z0-9]><postfix:[A-Z].+>/) = default str addSpaces(str s) = split("-", s)[-1]; -@synopsis{produces `"../../.."` for pathToRoot(|aap:///a/b|, |aap:///a/b/c/d|) } -str pathToRoot(loc root, loc src) - = "..<for (e <- split("/", relativize(root, src).path), e != "") {>/..<}>" - when isDirectory(src); +@synopsis{produces `"../../.."` for pathToRoot(|aap:///a/b|, |aap:///a/b/c/d|)} +str pathToRoot(loc root, loc src, bool isPackageCourse) + = "<if (isPackageCourse) {>../../<}>..<for (e <- split("/", relativize(root, src).path), e != "") {>/..<}>" + when isDirectory(src) -str pathToRoot(loc root, loc src) - = pathToRoot(root, src.parent) +str pathToRoot(loc root, loc src, bool isPackageCourse) + = pathToRoot(root, src.parent, isPackageCourse) when isFile(src); From cc85e31d8d597b7af57c09d26427b30143e71860 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 28 Dec 2022 16:11:58 +0100 Subject: [PATCH 039/120] fiddling with the paths and the links --- .../tutor/lang/rascal/tutor/Compiler.rsc | 35 ++++++++++++++----- .../tutor/lang/rascal/tutor/Names.rsc | 2 +- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index e80044c8170..63fd340d86d 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -70,9 +70,26 @@ public void defaultCompile(bool clean=false) { list[Message] compile(PathConfig pcfg, CommandExecutor exec = createExecutor(pcfg)) { ind = createConceptIndex(pcfg); + if (pcfg.isPackageCourse) { + generatePackageIndex(pcfg); + } + return [*compileCourse(src, pcfg[currentRoot=src], exec, ind) | src <- pcfg.srcs]; } +void generatePackageIndex(PathConfig pcfg) { + targetFile = pcfg.bin + "Packages" + pcfg.packageName + "index.md"; + + writeFile(targetFile, + "--- + 'title: <pcfg.packageName> - <pcfg.packageVersion> + '--- + ' + '<if (src <- pcfg.srcs, src.file in {"src", "rascal", "api"}) {>* [API documentation](../../Packages/<pcfg.packageName>/API)<}> + '<for (src <- pcfg.srcs, src.file notin {"src", "rascal", "api"}) {>* [<capitalize(src.file)>](../../Packages/<pcfg.packageName>/<capitalized(src.file)>)<}> + "); +} + list[Message] compileCourse(loc root, PathConfig pcfg, CommandExecutor exec, Index ind) = compileDirectory(root, pcfg[currentRoot=root], exec, ind); @@ -130,8 +147,8 @@ list[Message] compileDirectory(loc d, PathConfig pcfg, CommandExecutor exec, Ind j.file = (j.file == j.parent[extension="md"].file) ? "index.md" : j.file; targetFile = pcfg.bin - + (pcfg.isPackageCourse ? "Packages/<capitalize(pcfg.packageName)>" : "") - + (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"} ? "API" : capitalize(pcfg.currentRoot.file)) + + (pcfg.isPackageCourse ? "Packages/<pcfg.packageName>" : "") + + ((pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) ? "API" : capitalize(pcfg.currentRoot.file)) + relativize(pcfg.currentRoot, j)[extension="md"].path; if (!exists(targetFile) || lastModified(i) > lastModified(targetFile)) { @@ -186,8 +203,8 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) title = replaceAll(relativize(pcfg.currentRoot, d).path[1..], "/", "::"); targetFile = pcfg.bin - + (pcfg.isPackageCourse ? "Packages/<capitalize(pcfg.packageName)>" : "") - + (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"} ? "API" : capitalize(pcfg.currentRoot.file)) + + (pcfg.isPackageCourse ? "Packages/<pcfg.packageName>" : "") + + ((pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) ? "API" : capitalize(pcfg.currentRoot.file)) + relativize(pcfg.currentRoot, d).path + "index.md" ; @@ -199,7 +216,7 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) '<}>--- ' '<for (e <- d.ls, isDirectory(e) || e.extension in {"rsc", "md"}, e.file != "internal") {> - '* [<e[extension=""].file>](<p2r>/<capitalize(pcfg.currentRoot.file)><relativize(pcfg.currentRoot, e)[extension=isDirectory(e)?"":"md"].path>)<}>"); + '* [<e[extension=""].file>](<p2r>/<if (pcfg.isPackageCourse) {>/Packages/<pcfg.packageName><}>/<if (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><relativize(pcfg.currentRoot, e)[extension=isDirectory(e)?"":"md"].path>)<}>"); return []; } catch IO(msg): { return [error(msg, d)]; @@ -209,8 +226,8 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) @synopsis{Translates Rascal source files to docusaurus markdown.} list[Message] compileRascalFile(loc m, PathConfig pcfg, CommandExecutor exec, Index ind) { loc targetFile = pcfg.bin - + (pcfg.isPackageCourse ? "Packages/<capitalize(pcfg.packageName)>" : "") - + (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"} ? "API" : capitalize(pcfg.currentRoot.file)) + + (pcfg.isPackageCourse ? "Packages/<pcfg.packageName>" : "") + + ((pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) ? "API" : capitalize(pcfg.currentRoot.file)) + relativize(pcfg.currentRoot, m)[extension="md"].path; errors = []; @@ -252,8 +269,8 @@ list[Message] compileMarkdownFile(loc m, PathConfig pcfg, CommandExecutor exec, m.file = (m.file == m.parent[extension="md"].file) ? "index.md" : m.file; loc targetFile = pcfg.bin - + (pcfg.isPackageCourse ? "Packages/<capitalize(pcfg.packageName)>" : "") - + (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"} ? "API" : capitalize(pcfg.currentRoot.file)) + + (pcfg.isPackageCourse ? "Packages/<pcfg.packageName>" : "") + + ((pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) ? "API" : capitalize(pcfg.currentRoot.file)) + relativize(pcfg.currentRoot, m)[extension="md"].path; errors = []; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc index 9a740165199..ad016746eba 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc @@ -48,7 +48,7 @@ default str addSpaces(str s) = split("-", s)[-1]; @synopsis{produces `"../../.."` for pathToRoot(|aap:///a/b|, |aap:///a/b/c/d|)} str pathToRoot(loc root, loc src, bool isPackageCourse) = "<if (isPackageCourse) {>../../<}>..<for (e <- split("/", relativize(root, src).path), e != "") {>/..<}>" - when isDirectory(src) + when isDirectory(src); str pathToRoot(loc root, loc src, bool isPackageCourse) = pathToRoot(root, src.parent, isPackageCourse) From 6bba645f22ac3ff727e33c16588c52f0d63d61db Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 28 Dec 2022 16:56:36 +0100 Subject: [PATCH 040/120] fiddling with slashes --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 63fd340d86d..c63bc6d3e9d 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -86,7 +86,7 @@ void generatePackageIndex(PathConfig pcfg) { '--- ' '<if (src <- pcfg.srcs, src.file in {"src", "rascal", "api"}) {>* [API documentation](../../Packages/<pcfg.packageName>/API)<}> - '<for (src <- pcfg.srcs, src.file notin {"src", "rascal", "api"}) {>* [<capitalize(src.file)>](../../Packages/<pcfg.packageName>/<capitalized(src.file)>)<}> + '<for (src <- pcfg.srcs, src.file notin {"src", "rascal", "api"}) {>* [<capitalize(src.file)>](../../Packages/<pcfg.packageName>/<capitalize(src.file)>)<}> "); } @@ -216,7 +216,7 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) '<}>--- ' '<for (e <- d.ls, isDirectory(e) || e.extension in {"rsc", "md"}, e.file != "internal") {> - '* [<e[extension=""].file>](<p2r>/<if (pcfg.isPackageCourse) {>/Packages/<pcfg.packageName><}>/<if (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><relativize(pcfg.currentRoot, e)[extension=isDirectory(e)?"":"md"].path>)<}>"); + '* [<e[extension=""].file>](<p2r>/<if (pcfg.isPackageCourse) {>/Packages/<pcfg.packageName>/<}><if (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><relativize(pcfg.currentRoot, e)[extension=isDirectory(e)?"":"md"].path>)<}>"); return []; } catch IO(msg): { return [error(msg, d)]; From 08a1cb96b49e2b5cb155721657579a459b3c2bff Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 28 Dec 2022 17:25:37 +0100 Subject: [PATCH 041/120] tweaking the version class and more slash fiddling --- .../rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 4 ++-- .../lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 13 ++++--------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index c63bc6d3e9d..e1ca4b13fc8 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -211,12 +211,12 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) writeFile(targetFile, "--- - 'title: <if (trim(title) == "") {><capitalize(pcfg.currentRoot.file)><} else {><title><}> + 'title: <if (trim(title) == "") {><if (pcfg.currentRoot in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><} else {><title><}> '<if (sidebar_position != -1) {>sidebar_position: <sidebar_position> '<}>--- ' '<for (e <- d.ls, isDirectory(e) || e.extension in {"rsc", "md"}, e.file != "internal") {> - '* [<e[extension=""].file>](<p2r>/<if (pcfg.isPackageCourse) {>/Packages/<pcfg.packageName>/<}><if (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><relativize(pcfg.currentRoot, e)[extension=isDirectory(e)?"":"md"].path>)<}>"); + '* [<e[extension=""].file>](<p2r>/<if (pcfg.isPackageCourse) {>Packages/<pcfg.packageName>/<}><if (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><relativize(pcfg.currentRoot, e)[extension=isDirectory(e)?"":"md"].path>)<}>"); return []; } catch IO(msg): { return [error(msg, d)]; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index dd48488d812..6c8de444f66 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -84,9 +84,7 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC out("title: \"module <escape(d.moduleName, escapes)>\""), out("---"), Output::empty(), - out(":::tip"), - out("rascal-<getRascalVersion()><if (pcfg.isPackageCourse) {>, <pcfg.packageName>-<pcfg.packageVersion><}>."), - out(":::"), + out("\<div class=\"theme-doc-version-badge\"\>rascal-<getRascalVersion()><if (pcfg.isPackageCourse) {>, <pcfg.packageName>-<pcfg.packageVersion><}>\</div\>"), Output::empty(), out("#### Usage"), Output::empty(), @@ -101,7 +99,7 @@ list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, Pat out("## function <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), Output::empty(), out("```rascal"), - *[out(ov), empty() | ov <- overloads, str defLine <- split("\n", ov)], + *[out(defLine), empty() | ov <- overloads, str defLine <- split("\n", ov)], out("```"), Output::empty(), *tags2Markdown(d.docs, pcfg, exec, ind, dtls) @@ -112,7 +110,7 @@ list[Output] declInfo2Doc(str parent, d:testInfo(), list[str] overloads, PathCon out("## **test** <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), Output::empty(), out("```rascal"), - *[out(ov), empty() | ov <- overloads, str defLine <- split("\n", d.fullTest)], + *[out(defLine), empty() | ov <- overloads, str defLine <- split("\n", d.fullTest)], out("```"), Output::empty(), *tags2Markdown(d.docs, pcfg, exec, ind, dtls) @@ -127,10 +125,7 @@ list[Output] declInfo2Doc(str parent, d:testInfo(), list[str] overloads, PathCon empty(), *[ out("```rascal"), - *[ - out(defLine) - | str defLine <- split("\n", ov) - ], + *[out(defLine) | str defLine <- split("\n", ov)], out("```"), empty() | ov <- overloads From 0c89a25be069c9c2a2768126b4fb035a1a3b4fa3 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 28 Dec 2022 17:59:41 +0100 Subject: [PATCH 042/120] better version banner --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index e1ca4b13fc8..20728f49df3 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -561,9 +561,7 @@ list[Output] compileMarkdown([a:/^\-\-\-\s*$/, *str header, b:/^\-\-\-\s*$/, *st *[out(l) | l <- header], *[out("sidebar_position: <sidebar_position>") | sidebar_position != -1], out("---"), - out(":::tip"), - out("rascal-<getRascalVersion()><if (pcfg.isPackageCourse) {>, <pcfg.packageName>-<pcfg.packageVersion><}>."), - out(":::"), + out("\<div class=\"theme-doc-version-badge\"\>rascal-<getRascalVersion()><if (pcfg.isPackageCourse) {>, <pcfg.packageName>-<pcfg.packageVersion><}>\</div\>"), *compileMarkdown(rest, line + 2 + size(header), offset + size(a) + size(b) + length(header), pcfg, exec, ind, dtls, sidebar_position=sidebar_position) ]; } From af16081fda7473c98f25905a18cd1b633472e020 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 10 Jan 2023 16:36:25 +0100 Subject: [PATCH 043/120] added demo feature, such that files in demo folders show all the function bodies and improved the generation of package index files with useful information about the package such as the source location, the issues tracker and the so question tag and the pom XML dependency for copy/pasting --- .../tutor/lang/rascal/tutor/Compiler.rsc | 28 ++++++++++++- .../tutor/lang/rascal/tutor/Names.rsc | 4 ++ .../rascal/tutor/apidoc/DeclarationInfo.rsc | 4 +- .../lang/rascal/tutor/apidoc/ExtractInfo.rsc | 4 +- .../rascal/tutor/apidoc/GenerateMarkdown.rsc | 42 +++++++++---------- 5 files changed, 56 insertions(+), 26 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 20728f49df3..d52b7807782 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -80,13 +80,39 @@ list[Message] compile(PathConfig pcfg, CommandExecutor exec = createExecutor(pcf void generatePackageIndex(PathConfig pcfg) { targetFile = pcfg.bin + "Packages" + pcfg.packageName + "index.md"; + if (pcfg.license?) { + writeFile(targetFile.parent + "License.md", + "--- + 'title: <pcfg.packageName> open-source license + '--- + ' + '<readFile(pcfg.license)>"); + } + writeFile(targetFile, "--- 'title: <pcfg.packageName> - <pcfg.packageVersion> '--- ' '<if (src <- pcfg.srcs, src.file in {"src", "rascal", "api"}) {>* [API documentation](../../Packages/<pcfg.packageName>/API)<}> - '<for (src <- pcfg.srcs, src.file notin {"src", "rascal", "api"}) {>* [<capitalize(src.file)>](../../Packages/<pcfg.packageName>/<capitalize(src.file)>)<}> + '<for (src <- pcfg.srcs, src.file notin {"src", "rascal", "api"}) {>* [<capitalize(src.file)>](../../Packages/<pcfg.packageName>/<capitalize(src.file)>) + '<}>* [Stackoverflow questions](https://stackoverflow.com/questions/tagged/rascal+<pcfg.packageName>) + '<if (pcfg.license?) {>* [Open-source license](../../Packages/<pcfg.packageName>/License.md)<}> + '<if (pcfg.sources?) {>* [Source code](<"<pcfg.sources>"[1..-1]>)<}> + ' + '#### Installation + ' + 'To use <pcfg.packageName> in your maven-based Rascal project, include the following dependency: + ' + '```xml + '\<dependencies\> + ' \<dependency\> + ' \<groupId\><pcfg.packageGroup>\</groupId\> + ' \<artifactId\><pcfg.packageName>\</artifactId\> + ' \<version\><pcfg.packageVersion>\</version\> + ' \</dependency\> + '\</dependencies\> + '``` "); } diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc index ad016746eba..995009c32fd 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc @@ -8,6 +8,10 @@ import util::Reflective; data PathConfig( str packageName="", + str packageGroup="", + loc sources=|http://github.com/usethesource/rascal|, + loc issues=|http://github.com/usethesource/rascal/issues|, + loc license=|cwd:///LICENSE.md|, str packageVersion=getRascalVersion(), bool isPackageCourse=false ); diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc index 72ed5164894..e7da82febef 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc @@ -9,8 +9,8 @@ data DeclarationInfo( str signature="", list[DocTag] docs = [], loc docSrc = src) - = moduleInfo (str kind="module") - | functionInfo (str kind="function") + = moduleInfo (str kind="module", bool demo=false) + | functionInfo (str kind="function", str fullFunction="") | testInfo (str kind="test", str fullTest="") | constructorInfo (str kind="constructor") | dataInfo (str kind="data", list[str] overloads=[]) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc index 6bb4a6c1954..2b0f001048c 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc @@ -27,7 +27,7 @@ list[DeclarationInfo] extractModule(m: (Module) `<Header header> <Body body>`) { synopsis = getSynopsis(tags); - return moduleInfo(moduleName=moduleName, src=m@\loc, synopsis=synopsis, docs=sortedDocTags(tags)) + tls; + return moduleInfo(moduleName=moduleName, src=m@\loc, synopsis=synopsis, docs=sortedDocTags(tags), demo=/demo/ := moduleName) + tls; } /********************************************************************/ @@ -120,7 +120,7 @@ private DeclarationInfo extractFunctionDeclaration(str moduleName, FunctionDecla tags = getTagContents(fd.tags); - return functionInfo(moduleName=moduleName, name=fname, signature=signature, src=fd@\loc, synopsis=getSynopsis(tags), docs=sortedDocTags(tags)); + return functionInfo(moduleName=moduleName, name=fname, signature=signature, src=fd@\loc, synopsis=getSynopsis(tags), docs=sortedDocTags(tags), fullFunction="<fd>"); } DeclarationInfo extractTestDecl(str moduleName, FunctionDeclaration fd) { diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 6c8de444f66..70e0e05cefd 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -31,6 +31,8 @@ list[Output] generateAPIMarkdown(str parent, loc moduleLoc, PathConfig pcfg, Com // filter the tests tests = [t | t:testInfo() <- dinfo]; + isDemo = DeclarationInfo i <- dinfo && i is moduleInfo && i.demo; + // remove the tests dinfo -= tests; @@ -45,7 +47,7 @@ list[Output] generateAPIMarkdown(str parent, loc moduleLoc, PathConfig pcfg, Com list[str] overloads = []; if (dinfo[i] has name) { - overloads = [dinfo[i].signature]; + overloads = [(isDemo && dinfo[i].fullFunction?) ? dinfo[i].fullFunction : dinfo[i].signature]; // TODO: this only collects consecutive overloads. if a utility function interupts the flow, // then we do not get to see the other overloads with the current group. Rewrite to use a "group-by" query. @@ -53,19 +55,19 @@ list[Output] generateAPIMarkdown(str parent, loc moduleLoc, PathConfig pcfg, Com while (j < size(dinfo) && dinfo[i].name == dinfo[j].name) { // this loops eats the other declarations with the same name (if consecutive!) - overloads += dinfo[j].signature; + overloads += ((isDemo && dinfo[j].fullFunction?) ? dinfo[j].fullFunction : dinfo[j].signature); j += 1; } } - res += declInfo2Doc(parent, dinfo[i], overloads, pcfg, exec, ind, dinfo[i] is moduleInfo? dtls : []); + res += declInfo2Doc(parent, dinfo[i], overloads, pcfg, exec, ind, dinfo[i] is moduleInfo? dtls : [], isDemo); i = j; } res += line("### Tests"); for (di <- tests) { - res += declInfo2Doc(parent, di, [], pcfg, exec, ind, []); + res += declInfo2Doc(parent, di, [], pcfg, exec, ind, [], isDemo); } return res; @@ -78,7 +80,7 @@ list[Output] generateAPIMarkdown(str parent, loc moduleLoc, PathConfig pcfg, Com private map[str,str] escapes = ("\\": "\\\\", "\"": "\\\""); -list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = +list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("---"), out("title: \"module <escape(d.moduleName, escapes)>\""), @@ -90,22 +92,22 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC Output::empty(), out("`import <replaceAll(d.name, "/", "::")>;`"), Output::empty(), - *tags2Markdown(d.docs, pcfg, exec, ind, dtls), + *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo), out("") ]; -list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = +list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("## function <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), Output::empty(), + *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo), + Output::empty(), out("```rascal"), *[out(defLine), empty() | ov <- overloads, str defLine <- split("\n", ov)], - out("```"), - Output::empty(), - *tags2Markdown(d.docs, pcfg, exec, ind, dtls) + out("```") ]; -list[Output] declInfo2Doc(str parent, d:testInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = +list[Output] declInfo2Doc(str parent, d:testInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("## **test** <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), Output::empty(), @@ -113,13 +115,13 @@ list[Output] declInfo2Doc(str parent, d:testInfo(), list[str] overloads, PathCon *[out(defLine), empty() | ov <- overloads, str defLine <- split("\n", d.fullTest)], out("```"), Output::empty(), - *tags2Markdown(d.docs, pcfg, exec, ind, dtls) + *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo) ]; - list[Output] declInfo2Doc(str parent, constructorInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = + list[Output] declInfo2Doc(str parent, constructorInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = []; - list[Output] declInfo2Doc(str parent, d:dataInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = + list[Output] declInfo2Doc(str parent, d:dataInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("## data <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), empty(), @@ -130,10 +132,10 @@ list[Output] declInfo2Doc(str parent, d:testInfo(), list[str] overloads, PathCon empty() | ov <- overloads ], - *tags2Markdown(d.docs, pcfg, exec, ind, dtls) + *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo) ]; -list[Output] declInfo2Doc(str parent, d:aliasInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) = +list[Output] declInfo2Doc(str parent, d:aliasInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("## alias <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), empty(), @@ -141,13 +143,13 @@ list[Output] declInfo2Doc(str parent, d:aliasInfo(), list[str] overloads, PathCo *[out(removeNewlines(ov)), empty() | ov <- overloads], out("```"), empty(), - *tags2Markdown(d.docs, pcfg, exec, ind, dtls) + *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo) ]; -default list[Output] declInfo2Doc(str parent, DeclarationInfo d, list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) +default list[Output] declInfo2Doc(str parent, DeclarationInfo d, list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [err(info("No content generated for <d>", d.src))]; -list[Output] tags2Markdown(list[DocTag] tags, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls) +list[Output] tags2Markdown(list[DocTag] tags, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ // every doc tag has its own header title, except the "doc" tag which may contain them all (backward compatibility) *(l != "doc" ? [out("#### <capitalize(l)>"), empty()] : []), @@ -165,8 +167,6 @@ public str basename(str cn){ return (/^.*::<base:[A-Za-z0-9\-\_]+>$/ := cn) ? base : cn; } - - str removeNewlines(str x) = visit(x) { case /\n/ => " " }; From cd6483d951db7a543cc824e8d7271b03e15065aa Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 11 Jan 2023 13:05:55 +0100 Subject: [PATCH 044/120] fixed some issues with issues --- .../tutor/lang/rascal/tutor/Compiler.rsc | 19 ++++++++++++++++--- .../rascal/tutor/apidoc/DeclarationInfo.rsc | 2 +- .../rascal/tutor/apidoc/GenerateMarkdown.rsc | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index d52b7807782..3fa5057ce48 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -91,7 +91,7 @@ void generatePackageIndex(PathConfig pcfg) { writeFile(targetFile, "--- - 'title: <pcfg.packageName> - <pcfg.packageVersion> + 'title: <pcfg.packageName>-<pcfg.packageVersion> documentation '--- ' '<if (src <- pcfg.srcs, src.file in {"src", "rascal", "api"}) {>* [API documentation](../../Packages/<pcfg.packageName>/API)<}> @@ -99,10 +99,11 @@ void generatePackageIndex(PathConfig pcfg) { '<}>* [Stackoverflow questions](https://stackoverflow.com/questions/tagged/rascal+<pcfg.packageName>) '<if (pcfg.license?) {>* [Open-source license](../../Packages/<pcfg.packageName>/License.md)<}> '<if (pcfg.sources?) {>* [Source code](<"<pcfg.sources>"[1..-1]>)<}> + '<if (pcfg.issues?) {>* [Issue tracker](<"<pcfg.issues>"[1..-1]>)<}> ' '#### Installation ' - 'To use <pcfg.packageName> in your maven-based Rascal project, include the following dependency: + 'To use <pcfg.packageName> in a maven-based Rascal project, include the following dependency: ' '```xml '\<dependencies\> @@ -113,6 +114,18 @@ void generatePackageIndex(PathConfig pcfg) { ' \</dependency\> '\</dependencies\> '``` + '**and** change the `Require-Libraries` field in `/path/to/yourProjectName/META-INF/RASCAL.MF` like so: + ' + '```MF + 'Manifest-Version: 0.0.1 + 'Project-Name: yourProjectName + 'Source: path/to/src + 'Require-Libraries: |lib://<pcfg.packageName>| + ' + '``` + ':::info + 'dot.MF files _must_ end with an empty line. + '::: "); } @@ -237,7 +250,7 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) writeFile(targetFile, "--- - 'title: <if (trim(title) == "") {><if (pcfg.currentRoot in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><} else {><title><}> + 'title: <if (trim(title) == "") {><if (pcfg.currentRoot.file in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><} else {><title><}> '<if (sidebar_position != -1) {>sidebar_position: <sidebar_position> '<}>--- ' diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc index e7da82febef..428e9167240 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc @@ -5,7 +5,7 @@ data DeclarationInfo( str moduleName="", str name=moduleName, loc src = |unknown:///|, - str synopsis="", + str synopsis="", str signature="", list[DocTag] docs = [], loc docSrc = src) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 70e0e05cefd..e0f6266fc5a 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -4,7 +4,7 @@ import List; import String; import util::Reflective; -import lang::rascal::tutor::apidoc::DeclarationInfo; +import lang::rascal::tutor::apidoc::DeclarationInfo; import lang::rascal::tutor::apidoc::ExtractInfo; import lang::rascal::tutor::Output; import lang::rascal::tutor::Indexer; From f303f7e64d7420383769cf94a6c70d1348a47316 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 12 Jan 2023 10:33:29 +0100 Subject: [PATCH 045/120] minor improvements in Package index page --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 3fa5057ce48..c188347275c 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -91,9 +91,11 @@ void generatePackageIndex(PathConfig pcfg) { writeFile(targetFile, "--- - 'title: <pcfg.packageName>-<pcfg.packageVersion> documentation + 'title: <pcfg.packageName> '--- ' + 'This is the documentation for version <pcfg.packageVersion> of <pcfg.packageName>. + ' '<if (src <- pcfg.srcs, src.file in {"src", "rascal", "api"}) {>* [API documentation](../../Packages/<pcfg.packageName>/API)<}> '<for (src <- pcfg.srcs, src.file notin {"src", "rascal", "api"}) {>* [<capitalize(src.file)>](../../Packages/<pcfg.packageName>/<capitalize(src.file)>) '<}>* [Stackoverflow questions](https://stackoverflow.com/questions/tagged/rascal+<pcfg.packageName>) @@ -103,7 +105,7 @@ void generatePackageIndex(PathConfig pcfg) { ' '#### Installation ' - 'To use <pcfg.packageName> in a maven-based Rascal project, include the following dependency: + 'To use <pcfg.packageName> in a maven-based Rascal project, include the following dependency in the `pom.xml` file: ' '```xml '\<dependencies\> @@ -122,6 +124,7 @@ void generatePackageIndex(PathConfig pcfg) { 'Source: path/to/src 'Require-Libraries: |lib://<pcfg.packageName>| ' + ' '``` ':::info 'dot.MF files _must_ end with an empty line. From 6c9f58b6a294679eaf34b1a5a142eac56f29479d Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 12 Jan 2023 15:10:52 +0100 Subject: [PATCH 046/120] minor fixes --- .../rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 4 ++-- .../tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc | 8 ++------ .../lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 12 +++++++----- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index c188347275c..cbd635ccfaa 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -242,7 +242,7 @@ list[Message] compileDirectory(loc d, PathConfig pcfg, CommandExecutor exec, Ind list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) { try { p2r = pathToRoot(pcfg.currentRoot, d, pcfg.isPackageCourse); - title = replaceAll(relativize(pcfg.currentRoot, d).path[1..], "/", "::"); + title = (d == pcfg.currentRoot && d.file in {"src","rascal","api"}) ? "API" : d.file; targetFile = pcfg.bin + (pcfg.isPackageCourse ? "Packages/<pcfg.packageName>" : "") @@ -253,7 +253,7 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) writeFile(targetFile, "--- - 'title: <if (trim(title) == "") {><if (pcfg.currentRoot.file in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><} else {><title><}> + 'title: <title> '<if (sidebar_position != -1) {>sidebar_position: <sidebar_position> '<}>--- ' diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc index 2b0f001048c..e00be02e4c1 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc @@ -22,12 +22,12 @@ private list[DeclarationInfo] doExtractInfo(loc moduleLoc, datetime _/*lastModif list[DeclarationInfo] extractModule(m: (Module) `<Header header> <Body body>`) { moduleName = "<header.name>"; tags = getTagContents(header.tags); - + name = "<header.name.names[-1]>"; tls = [ *extractTopLevel(moduleName, tl) | tl <- body.toplevels ]; synopsis = getSynopsis(tags); - return moduleInfo(moduleName=moduleName, src=m@\loc, synopsis=synopsis, docs=sortedDocTags(tags), demo=/demo/ := moduleName) + tls; + return moduleInfo(moduleName=moduleName, name=name, src=m@\loc, synopsis=synopsis, docs=sortedDocTags(tags), demo=(/demo/ := moduleName)) + tls; } /********************************************************************/ @@ -127,10 +127,6 @@ DeclarationInfo extractTestDecl(str moduleName, FunctionDeclaration fd) { fname = "<fd.signature.name>"; signature = "<fd.signature>"; - if(startsWith(signature, "java")){ - signature = signature[size("java")+1 .. ]; - } - tags = getTagContents(fd.tags); return testInfo(moduleName=moduleName, name=fname, src=fd@\loc, synopsis=getSynopsis(tags), fullTest="<fd>"); diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index e0f6266fc5a..56d76bce824 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -64,7 +64,7 @@ list[Output] generateAPIMarkdown(str parent, loc moduleLoc, PathConfig pcfg, Com i = j; } - res += line("### Tests"); + res += line("# Tests"); for (di <- tests) { res += declInfo2Doc(parent, di, [], pcfg, exec, ind, [], isDemo); @@ -83,14 +83,16 @@ private map[str,str] escapes = ("\\": "\\\\", "\"": "\\\""); list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("---"), - out("title: \"module <escape(d.moduleName, escapes)>\""), + out("title: \"module <d.name>\""), out("---"), Output::empty(), out("\<div class=\"theme-doc-version-badge\"\>rascal-<getRascalVersion()><if (pcfg.isPackageCourse) {>, <pcfg.packageName>-<pcfg.packageVersion><}>\</div\>"), Output::empty(), out("#### Usage"), Output::empty(), - out("`import <replaceAll(d.name, "/", "::")>;`"), + out("```rascal"), + out("import <replaceAll(d.moduleName, "/", "::")>;"), + out("```"), Output::empty(), *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo), out("") @@ -109,10 +111,10 @@ list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, Pat list[Output] declInfo2Doc(str parent, d:testInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ - out("## **test** <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), + out("## test <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), Output::empty(), out("```rascal"), - *[out(defLine), empty() | ov <- overloads, str defLine <- split("\n", d.fullTest)], + *[out(defLine) | str defLine <- split("\n", d.fullTest)], out("```"), Output::empty(), *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo) From cb999a8461dfe051e08d607e0a940433f1d93ee6 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 16 Jan 2023 15:16:25 +0100 Subject: [PATCH 047/120] fixed two bugs wrt Package feature: location of static assets and some module name links were wrong --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 2 +- src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index cbd635ccfaa..0f7c346629f 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -155,7 +155,7 @@ list[Message] compile(loc src, PathConfig pcfg, CommandExecutor exec, Index ind, else if (src.extension in {"png","jpg","svg","jpeg", "html", "js"}) { try { println("copying <src> [Asset]"); - copy(src, pcfg.bin + "assets" + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, src).path); + copy(src, pcfg.bin + (pcfg.isPackageCourse ? "assets/Packages/<pcfg.packageName>" : "assets") + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, src).path); return []; } diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc index 8246a551755..a2e91907341 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -173,7 +173,10 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC <"<capitalize(src.file)>:<item.kind>:<item.moduleName><sep><item.name>", fr > | item.name?, sep <- {"::", "/", "-"}}, // ((Set)) -> `/Library/Set` - *{<item.moduleName, "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" >, <"module:<item.moduleName>", "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" > | item is moduleInfo}, + *{<item.moduleName, "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" >, + <"module:<item.moduleName>", "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" > + | item is moduleInfo + }, // `((Library:Set))` -> `/Library/Set` *{<"<capitalize(src.file)>:<item.moduleName>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" >, From caf4a0a87a9b42ded9d2c0af410a102cbf1329b2 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 16 Jan 2023 16:58:47 +0100 Subject: [PATCH 048/120] different class for version badges --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 3 ++- .../tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 0f7c346629f..0f17870b9dd 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -603,7 +603,8 @@ list[Output] compileMarkdown([a:/^\-\-\-\s*$/, *str header, b:/^\-\-\-\s*$/, *st *[out(l) | l <- header], *[out("sidebar_position: <sidebar_position>") | sidebar_position != -1], out("---"), - out("\<div class=\"theme-doc-version-badge\"\>rascal-<getRascalVersion()><if (pcfg.isPackageCourse) {>, <pcfg.packageName>-<pcfg.packageVersion><}>\</div\>"), + out("\<div class=\"theme-doc-version-badge badge badge--secondary\"\>rascal-<getRascalVersion()><if (pcfg.isPackageCourse) {>, <pcfg.packageName>-<pcfg.packageVersion><}>\</div\>"), + out(""), *compileMarkdown(rest, line + 2 + size(header), offset + size(a) + size(b) + length(header), pcfg, exec, ind, dtls, sidebar_position=sidebar_position) ]; } diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 56d76bce824..60ef9c3a717 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -86,7 +86,7 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC out("title: \"module <d.name>\""), out("---"), Output::empty(), - out("\<div class=\"theme-doc-version-badge\"\>rascal-<getRascalVersion()><if (pcfg.isPackageCourse) {>, <pcfg.packageName>-<pcfg.packageVersion><}>\</div\>"), + out("\<div class=\"theme-doc-version-badge badge badge--secondary\"\>rascal-<getRascalVersion()><if (pcfg.isPackageCourse) {>, <pcfg.packageName>-<pcfg.packageVersion><}>\</div\>"), Output::empty(), out("#### Usage"), Output::empty(), From 0a9544ab54785021db98d8cbe58c1cbd6f28e0a9 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 16 Jan 2023 17:14:53 +0100 Subject: [PATCH 049/120] split version badges into two separate badges --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 2 +- .../tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 0f17870b9dd..5ae5eb57a9b 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -603,7 +603,7 @@ list[Output] compileMarkdown([a:/^\-\-\-\s*$/, *str header, b:/^\-\-\-\s*$/, *st *[out(l) | l <- header], *[out("sidebar_position: <sidebar_position>") | sidebar_position != -1], out("---"), - out("\<div class=\"theme-doc-version-badge badge badge--secondary\"\>rascal-<getRascalVersion()><if (pcfg.isPackageCourse) {>, <pcfg.packageName>-<pcfg.packageVersion><}>\</div\>"), + out("\<div class=\"theme-doc-version-badge badge badge--secondary\"\>rascal-<getRascalVersion()>\</div\><if (pcfg.isPackageCourse) {> \<div class=\"theme-doc-version-badge badge badge--secondary\"\><pcfg.packageName>-<pcfg.packageVersion>\</div\><}>"), out(""), *compileMarkdown(rest, line + 2 + size(header), offset + size(a) + size(b) + length(header), pcfg, exec, ind, dtls, sidebar_position=sidebar_position) ]; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 60ef9c3a717..555f5e6da5d 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -86,7 +86,7 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC out("title: \"module <d.name>\""), out("---"), Output::empty(), - out("\<div class=\"theme-doc-version-badge badge badge--secondary\"\>rascal-<getRascalVersion()><if (pcfg.isPackageCourse) {>, <pcfg.packageName>-<pcfg.packageVersion><}>\</div\>"), + out("\<div class=\"theme-doc-version-badge badge badge--secondary\"\>rascal-<getRascalVersion()>\</div\><if (pcfg.isPackageCourse) {> \<div class=\"theme-doc-version-badge badge badge--secondary\"\><pcfg.packageName>-<pcfg.packageVersion>\</div\><}>"), Output::empty(), out("#### Usage"), Output::empty(), From 23977d3df8eb6252fc66f9b90ac0f90e5bde0669 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 16 Jan 2023 20:37:50 +0100 Subject: [PATCH 050/120] extract and use syntax definitions as well in docs --- .../lang/rascal/tutor/apidoc/DeclarationInfo.rsc | 1 + .../tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc | 12 +++++++++++- .../lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 14 ++++++++++++++ .../rascal/tutor/examples/Test/Vis/Example.rsc | 6 ++++-- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc index 428e9167240..ffc491d2c44 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc @@ -16,6 +16,7 @@ data DeclarationInfo( | dataInfo (str kind="data", list[str] overloads=[]) | aliasInfo (str kind="alias") | varInfo (str kind="variable") + | syntaxInfo (str kind="syntax") ; data DocTag(str label="", loc src=|unknown:///|, str content="") diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc index e00be02e4c1..0686270b138 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc @@ -23,13 +23,23 @@ list[DeclarationInfo] extractModule(m: (Module) `<Header header> <Body body>`) { moduleName = "<header.name>"; tags = getTagContents(header.tags); name = "<header.name.names[-1]>"; - tls = [ *extractTopLevel(moduleName, tl) | tl <- body.toplevels ]; + tls = [*extractImport(moduleName, imp) | imp <- header.imports] + + [ *extractTopLevel(moduleName, tl) | tl <- body.toplevels ]; synopsis = getSynopsis(tags); return moduleInfo(moduleName=moduleName, name=name, src=m@\loc, synopsis=synopsis, docs=sortedDocTags(tags), demo=(/demo/ := moduleName)) + tls; } +/********************************************************************/ +/* Process imports and syntax definitions */ +/********************************************************************/ + +default list[DeclarationInfo] extractImport(str moduleName, (Import) `<SyntaxDefinition def>` ) + = [syntaxInfo(moduleName=moduleName, name="<def.defined>", signature="<def>")]; + +default list[DeclarationInfo] extractImport(str _moduleName, Import _ ) = []; + /********************************************************************/ /* Process declarations in a module */ /********************************************************************/ diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 555f5e6da5d..37f2ae534fa 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -137,6 +137,20 @@ list[Output] declInfo2Doc(str parent, d:testInfo(), list[str] overloads, PathCon *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo) ]; +list[Output] declInfo2Doc(str parent, d:syntaxInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = + [ + out("## syntax <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), + empty(), + *[ + out("```rascal"), + *[out(defLine) | str defLine <- split("\n", ov)], + out("```"), + empty() + | ov := d.signature + ], + *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo) + ]; + list[Output] declInfo2Doc(str parent, d:aliasInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("## alias <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Example.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Example.rsc index d42b6b6dc98..cdd4c2bcf9b 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Example.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/Vis/Example.rsc @@ -1,5 +1,7 @@ @synopsis{Just a module} -module Vis::Examples +module Vis::Example + +syntax VIS = "vis"; @synopsis{main is marvelous} @description{ @@ -9,5 +11,5 @@ println("hai") ``` } int main() { - + return 0; } \ No newline at end of file From 7bd2a7befe36bed41b0f7c9e4e12c9ee2a7c84cc Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 16 Jan 2023 20:41:23 +0100 Subject: [PATCH 051/120] added examples next to demo as keyword to trigger demo feature --- .../rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc index 0686270b138..4cafd72217c 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc @@ -28,7 +28,7 @@ list[DeclarationInfo] extractModule(m: (Module) `<Header header> <Body body>`) { synopsis = getSynopsis(tags); - return moduleInfo(moduleName=moduleName, name=name, src=m@\loc, synopsis=synopsis, docs=sortedDocTags(tags), demo=(/demo/ := moduleName)) + tls; + return moduleInfo(moduleName=moduleName, name=name, src=m@\loc, synopsis=synopsis, docs=sortedDocTags(tags), demo=(/demo|examples/ := moduleName)) + tls; } /********************************************************************/ From 3790d6e34b9349a25b4449722ee2bca1ebd24b36 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 17 Jan 2023 17:11:19 +0100 Subject: [PATCH 052/120] generating slugs to disambiguate package names from module names with the same name modulo case, and some minor improvements --- .../rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 11 +++++++++-- .../tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc | 8 ++++++-- .../lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 7 ++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 5ae5eb57a9b..89172220af9 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -251,9 +251,12 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) + "index.md" ; + str slug = relativize(pcfg.bin, targetFile).parent.path; + writeFile(targetFile, "--- - 'title: <title> + 'title: <title>  + 'slug: <slug> '<if (sidebar_position != -1) {>sidebar_position: <sidebar_position> '<}>--- ' @@ -274,8 +277,12 @@ list[Message] compileRascalFile(loc m, PathConfig pcfg, CommandExecutor exec, In errors = []; if (!exists(targetFile) || lastModified(targetFile) < lastModified(m)) { + str parentSlug = (|path:///| + (pcfg.isPackageCourse ? "Packages/<pcfg.packageName>" : "") + + ((pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) ? "API" : capitalize(pcfg.currentRoot.file)) + + relativize(pcfg.currentRoot, m).parent.path).path; + println("compiling <m> [Rascal Source File]"); - list[Output] output = generateAPIMarkdown(relativize(pcfg.currentRoot, m).parent.path, m, pcfg, exec, ind); + list[Output] output = generateAPIMarkdown(parentSlug, m, pcfg, exec, ind); writeFile(targetFile, "<for (line(x) <- output) {><x> diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc index 4cafd72217c..01ba4fba18b 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc @@ -130,7 +130,7 @@ private DeclarationInfo extractFunctionDeclaration(str moduleName, FunctionDecla tags = getTagContents(fd.tags); - return functionInfo(moduleName=moduleName, name=fname, signature=signature, src=fd@\loc, synopsis=getSynopsis(tags), docs=sortedDocTags(tags), fullFunction="<fd>"); + return functionInfo(moduleName=moduleName, name=fname, signature=signature, src=fd@\loc, synopsis=getSynopsis(tags), docs=sortedDocTags(tags), fullFunction="<removeTags(fd)>"); } DeclarationInfo extractTestDecl(str moduleName, FunctionDeclaration fd) { @@ -139,9 +139,13 @@ DeclarationInfo extractTestDecl(str moduleName, FunctionDeclaration fd) { signature = "<fd.signature>"; tags = getTagContents(fd.tags); - return testInfo(moduleName=moduleName, name=fname, src=fd@\loc, synopsis=getSynopsis(tags), fullTest="<fd>"); + return testInfo(moduleName=moduleName, name=fname, src=fd@\loc, synopsis=getSynopsis(tags), fullTest="<removeTags(fd)>"); } +private Tree removeTags(Tree x) = visit(x) { + case Tags _ => (Tags) `` +}; + str getSynopsis(rel[str, DocTag] tags) { if (docTag(content=str docContents) <- tags["doc"]) { if ([*_, /^.Synopsis\s+<rest:.*>$/, *str cont, /^.[A-Za-z].*$/, *str _] := split("\n", docContents)) { diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 37f2ae534fa..39db1750302 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -3,6 +3,7 @@ module lang::rascal::tutor::apidoc::GenerateMarkdown import List; import String; import util::Reflective; +import Message; import lang::rascal::tutor::apidoc::DeclarationInfo; import lang::rascal::tutor::apidoc::ExtractInfo; @@ -64,7 +65,9 @@ list[Output] generateAPIMarkdown(str parent, loc moduleLoc, PathConfig pcfg, Com i = j; } - res += line("# Tests"); + if (tests != []) { + res += line("# Tests"); + } for (di <- tests) { res += declInfo2Doc(parent, di, [], pcfg, exec, ind, [], isDemo); @@ -84,6 +87,8 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC [ out("---"), out("title: \"module <d.name>\""), + out("id: <d.name>"), + out("slug: <parent>/<d.name>"), out("---"), Output::empty(), out("\<div class=\"theme-doc-version-badge badge badge--secondary\"\>rascal-<getRascalVersion()>\</div\><if (pcfg.isPackageCourse) {> \<div class=\"theme-doc-version-badge badge badge--secondary\"\><pcfg.packageName>-<pcfg.packageVersion>\</div\><}>"), From 57dca98cba8eb1f88e9f2fe34beb7338b6227004 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 18 Jan 2023 13:30:01 +0100 Subject: [PATCH 053/120] fixed bugs in indexer and added dependency information to module docs --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 2 +- src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc | 10 +++++----- .../tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc | 2 +- .../tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc | 10 +++++++++- .../lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 8 ++++++++ 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 89172220af9..79ef61a6b55 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -300,7 +300,7 @@ list[Message] compileRascalFile(loc m, PathConfig pcfg, CommandExecutor exec, In else { println("reusing <m>"); if (exists(targetFile[extension="errors"])) { - errors = readBinaryValueFile(#list[Message], targetFile[extension=""]); + errors = readBinaryValueFile(#list[Message], targetFile[extension="errors"]); } } diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc index a2e91907341..33dd2481ce6 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -162,15 +162,15 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC // `((Library:getDefaultPathConfig))` -> `/Library/util/Reflective#getDefaultPathConfig` *{<"<capitalize(src.file)>:<item.name>", fr >, - <"<capitalize(src.file)>:<item.kind>:<item.name>", fr > | item.name?}, + <"<capitalize(src.file)>:<item.kind>:<item.name>", fr > | item.name?, !(item is moduleInfo)}, // `((util::Reflective::getDefaultPathConfig))` -> `/Library/util/Reflective#getDefaultPathConfig` *{<"<item.moduleName><sep><item.name>", fr >, - <"<item.kind>:<item.moduleName><sep><item.name>", fr > | item.name?, sep <- {"::", "/", "-"}}, + <"<item.kind>:<item.moduleName><sep><item.name>", fr > | item.name?, !(item is moduleInfo), sep <- {"::", "/", "-"}}, // ((Library:util::Reflective::getDefaultPathConfig))` -> `/Library/util/Reflective#getDefaultPathConfig` *{<"<capitalize(src.file)>:<item.moduleName><sep><item.name>", fr >, - <"<capitalize(src.file)>:<item.kind>:<item.moduleName><sep><item.name>", fr > | item.name?, sep <- {"::", "/", "-"}}, + <"<capitalize(src.file)>:<item.kind>:<item.moduleName><sep><item.name>", fr > | item.name?, !(item is moduleInfo), sep <- {"::", "/", "-"}}, // ((Set)) -> `/Library/Set` *{<item.moduleName, "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" >, @@ -179,8 +179,8 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC }, // `((Library:Set))` -> `/Library/Set` - *{<"<capitalize(src.file)>:<item.moduleName>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" >, - <"<capitalize(src.file)>:module:<item.moduleName>", "/<capitalize(src.file)>/<modulePath(item.moduleName)>.md" > | item is moduleInfo} + *{<"<capitalize(src.file)>:<item.moduleName>", "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" >, + <"<capitalize(src.file)>:module:<item.moduleName>", "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" > | item is moduleInfo} | loc f <- find(src, isFreshRascalFile(lastModified)), list[DeclarationInfo] inf := safeExtract(f), item <- inf, fr := "/<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc index ffc491d2c44..d3870d97ef9 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/DeclarationInfo.rsc @@ -9,7 +9,7 @@ data DeclarationInfo( str signature="", list[DocTag] docs = [], loc docSrc = src) - = moduleInfo (str kind="module", bool demo=false) + = moduleInfo (str kind="module", bool demo=false, list[str] dependencies=[]) | functionInfo (str kind="function", str fullFunction="") | testInfo (str kind="test", str fullTest="") | constructorInfo (str kind="constructor") diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc index 01ba4fba18b..56281fb8e35 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc @@ -28,7 +28,15 @@ list[DeclarationInfo] extractModule(m: (Module) `<Header header> <Body body>`) { synopsis = getSynopsis(tags); - return moduleInfo(moduleName=moduleName, name=name, src=m@\loc, synopsis=synopsis, docs=sortedDocTags(tags), demo=(/demo|examples/ := moduleName)) + tls; + return moduleInfo( + moduleName=moduleName, + name=name, + src=m@\loc, + synopsis=synopsis, + docs=sortedDocTags(tags), + demo=(/demo|examples/ := moduleName), + dependencies=[trim("<d>") | d <- header.imports, !(d is \syntax)] + ) + tls; } /********************************************************************/ diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 39db1750302..7532c0e2407 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -99,6 +99,14 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC out("import <replaceAll(d.moduleName, "/", "::")>;"), out("```"), Output::empty(), + *[ + out("#### Dependencies"), + out("```rascal"), + *[ out(dep) | dep <- d.dependencies], + out("```") + | d.dependencies != [] + ], + Output::empty(), *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo), out("") ]; From bae4e8e995baae096eb39a1997e13064484a7fff Mon Sep 17 00:00:00 2001 From: Jasper Timmer <jjwtimmer@gmail.com> Date: Sun, 22 Jan 2023 11:51:16 +0100 Subject: [PATCH 054/120] move Tutor docs to tutor --- .../rascal/tutor/examples/Test/CallAnalysis/CallAnalysis.md | 2 +- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/CallAnalysis.md b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/CallAnalysis.md index 5ff9bbdc791..4b25c81e265 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/CallAnalysis.md +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/CallAnalysis.md @@ -18,7 +18,7 @@ Let's see how these questions can be answered using Rascal. Examples: Consider the following call graph (a box represents a procedure and an arrow represents a call from one procedure to another procedure): -![calls]((calls.png)) +![calls]((CallAnalysis-calls.png)) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc index 88e62e1d5bc..c28fcd05ee1 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc @@ -5,7 +5,7 @@ import util::Reflective; @synopsis{A closure-based object wrapper for Rascal REPL} @description{ Using an instance of CommandExecutor you can simulate the exact interactions -between a Rascal ((RascalShell:REPL)) user and the REPL. +between a Rascal ((GettingStarted-RunningRascal-Commandline)) user and the REPL. This was created to implement documentation pages with example REPL runs. } From ec13b2a3406f9c685f82c6c53bec2984668022d0 Mon Sep 17 00:00:00 2001 From: Jasper Timmer <jjwtimmer@gmail.com> Date: Sun, 22 Jan 2023 15:34:26 +0100 Subject: [PATCH 055/120] Move our versions and java version to properties; fix tutor docs build --- .../rascal/tutor/examples/Test/CallAnalysis/CallAnalysis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/CallAnalysis.md b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/CallAnalysis.md index 4b25c81e265..8d42a16e188 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/CallAnalysis.md +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/examples/Test/CallAnalysis/CallAnalysis.md @@ -18,7 +18,7 @@ Let's see how these questions can be answered using Rascal. Examples: Consider the following call graph (a box represents a procedure and an arrow represents a call from one procedure to another procedure): -![calls]((CallAnalysis-calls.png)) +![calls]((rascal\-tutor:CallAnalysis-calls.png)) From defb5b15ced926523e2a1bf40035585ca03302dd Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 23 Jan 2023 10:32:05 +0100 Subject: [PATCH 056/120] fixed bug in index generator which forgot to filter ignores and also set errorsAsWarnings to false --- .../tutor/lang/rascal/tutor/Compiler.rsc | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 79ef61a6b55..ed17a64523c 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -89,6 +89,27 @@ void generatePackageIndex(PathConfig pcfg) { '<readFile(pcfg.license)>"); } + dependencies = [ f | f <- pcfg.classloaders, exists(f), f.extension=="jar"]; + + if (dependencies != []) { + writeFile(targetFile.parent + "Dependencies.md", + "--- + 'title: <pcfg.packageName> dependencies + '--- + ' + 'These are compile-time and run-time dependencies of <pcfg.packageName>: + '<for (loc d <- dependencies) {> * <d[extension=""].file> + '<}> + ' + ':::warning + 'This message is automatically generated. The authors of <pcfg.packageName> probably prefer open-source licenses for their dependencies which are permissive to commercial exploitation + 'and any kind of reuse, and **non-viral**, however they are not liable for any damages caused by the licenses of the above dependencies, or changes therein. + 'The diligent user checks if these licenses are compatible with their situation! + '::: + " + ); + } + writeFile(targetFile, "--- 'title: <pcfg.packageName> @@ -100,6 +121,7 @@ void generatePackageIndex(PathConfig pcfg) { '<for (src <- pcfg.srcs, src.file notin {"src", "rascal", "api"}) {>* [<capitalize(src.file)>](../../Packages/<pcfg.packageName>/<capitalize(src.file)>) '<}>* [Stackoverflow questions](https://stackoverflow.com/questions/tagged/rascal+<pcfg.packageName>) '<if (pcfg.license?) {>* [Open-source license](../../Packages/<pcfg.packageName>/License.md)<}> + '<if (dependencies != []) {> * [Dependencies](../../Packages/<pcfg.packageName>/Dependencies.md)<}> '<if (pcfg.sources?) {>* [Source code](<"<pcfg.sources>"[1..-1]>)<}> '<if (pcfg.issues?) {>* [Issue tracker](<"<pcfg.issues>"[1..-1]>)<}> ' @@ -309,7 +331,9 @@ list[Message] compileRascalFile(loc m, PathConfig pcfg, CommandExecutor exec, In @synopsis{This uses another nested directory listing to construct information for the TOC embedded in the current document.} list[str] createDetailsList(loc m, PathConfig pcfg) - = sort([ "<capitalize(pcfg.currentRoot.file)>:<if (isDirectory(d), !exists(d + "index.md"), !exists((d + d.file)[extension="md"])) {>package:<}><if (d.extension == "rsc") {>module:<}><replaceAll(relativize(pcfg.currentRoot, d)[extension=""].path[1..], "/", "-")>" | d <- m.parent.ls, m != d, d.file != "index.md", isDirectory(d) || d.extension in {"rsc", "md"}]); + = sort([ "<capitalize(pcfg.currentRoot.file)>:<if (isDirectory(d), !exists(d + "index.md"), !exists((d + d.file)[extension="md"])) {>package:<}><if (d.extension == "rsc") {>module:<}><replaceAll(relativize(pcfg.currentRoot, d)[extension=""].path[1..], "/", "-")>" + | loc d <- m.parent.ls, m != d, !(d in pcfg.ignores), d.file != "index.md", isDirectory(d) || d.extension in {"rsc", "md"} + ]); list[Message] compileMarkdownFile(loc m, PathConfig pcfg, CommandExecutor exec, Index ind, int sidebar_position=-1) { order = createDetailsList(m, pcfg); From 55b1d4ee497a86f51c2ce82c91fc9a3d85933574 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 23 Jan 2023 10:34:13 +0100 Subject: [PATCH 057/120] fixed broken links due to moving course Tutor here --- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc index c28fcd05ee1..fab68a503e7 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc @@ -5,7 +5,7 @@ import util::Reflective; @synopsis{A closure-based object wrapper for Rascal REPL} @description{ Using an instance of CommandExecutor you can simulate the exact interactions -between a Rascal ((GettingStarted-RunningRascal-Commandline)) user and the REPL. +between a Rascal REPL user and the REPL. This was created to implement documentation pages with example REPL runs. } From b29d2eafd475fa45bf83fd90eb0dd44a331cc76d Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 23 Jan 2023 10:39:12 +0100 Subject: [PATCH 058/120] bumped license year and fixed itemized dependency list --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index ed17a64523c..1491799d3ea 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -98,6 +98,7 @@ void generatePackageIndex(PathConfig pcfg) { '--- ' 'These are compile-time and run-time dependencies of <pcfg.packageName>: + ' '<for (loc d <- dependencies) {> * <d[extension=""].file> '<}> ' From 5856e1aa7933bab77415d503408242551138ab96 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 23 Jan 2023 16:33:56 +0100 Subject: [PATCH 059/120] added workaround for accidental trailing slashes produced by URIUtil.file --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 1491799d3ea..85c17b22375 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -74,6 +74,9 @@ list[Message] compile(PathConfig pcfg, CommandExecutor exec = createExecutor(pcf generatePackageIndex(pcfg); } + // remove trailing slashes + pcfg.ignores = [ i.parent + i.file | i <- pcfg.ignores]; + return [*compileCourse(src, pcfg[currentRoot=src], exec, ind) | src <- pcfg.srcs]; } @@ -196,6 +199,8 @@ list[Message] compileDirectory(loc d, PathConfig pcfg, CommandExecutor exec, Ind return [info("skipped ignored location: <d>", d)]; } + println("compiling <d> [Folder]"); + indexFiles = {(d + "<d.file>")[extension="md"], (d + "index.md")}; if (!exists(d)) { @@ -255,6 +260,7 @@ list[Message] compileDirectory(loc d, PathConfig pcfg, CommandExecutor exec, Ind *errors, *[*compile(s, pcfg, exec, ind, sidebar_position=sp) | s <- d.ls + , !(s in pcfg.ignores) , !(s in indexFiles) , isDirectory(s) || s.extension in {"md","rsc","png","jpg","svg","jpeg", "html", "js"} , int sp := indexOf(nestedDtls, capitalize(s[extension=""].file)) @@ -283,7 +289,7 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) '<if (sidebar_position != -1) {>sidebar_position: <sidebar_position> '<}>--- ' - '<for (e <- d.ls, isDirectory(e) || e.extension in {"rsc", "md"}, e.file != "internal") {> + '<for (e <- d.ls, isDirectory(e) || e.extension in {"rsc", "md"}, e.file != "internal", !(e in pcfg.ignores)) {> '* [<e[extension=""].file>](<p2r>/<if (pcfg.isPackageCourse) {>Packages/<pcfg.packageName>/<}><if (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><relativize(pcfg.currentRoot, e)[extension=isDirectory(e)?"":"md"].path>)<}>"); return []; } catch IO(msg): { From 9b5933c5016ee069287f1e47f104fcd3134913cc Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 24 Jan 2023 12:38:56 +0100 Subject: [PATCH 060/120] store important information about licenses and dependencies in .txt files such that downstream websites can include this information in their documentation pages --- .../tutor/lang/rascal/tutor/Compiler.rsc | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 85c17b22375..a555bb7d287 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -73,13 +73,41 @@ list[Message] compile(PathConfig pcfg, CommandExecutor exec = createExecutor(pcf if (pcfg.isPackageCourse) { generatePackageIndex(pcfg); } + else { + storeImportantProjectMetaData(pcfg); + } // remove trailing slashes - pcfg.ignores = [ i.parent + i.file | i <- pcfg.ignores]; + pcfg.ignores = [i.parent + i.file | i <- pcfg.ignores]; return [*compileCourse(src, pcfg[currentRoot=src], exec, ind) | src <- pcfg.srcs]; } +void storeImportantProjectMetaData(PathConfig pcfg) { + // these files are with the .txt extension such that they are not automatically + // incorporated into the website. Rather other pages can include them where they see fit. + // this information, however, is not easy to obtain outside of the build + // environment of the current project. Therefore we store it here and now. + + if (!pcfg.packageName?) { + return; + } + + if (pcfg.license?) { + copy(pcfg.license, pcfg.bin + "LICENSE_<pcfg.packageName>.txt"); + } + + dependencies = [ f | f <- pcfg.classloaders, exists(f), f.extension=="jar"]; + + if (dependencies != []) { + writeFile(pcfg.bin + "DEPENDENCIES_<pcfg.packageName>.txt", + "<for (loc d <- dependencies) {> * <d[extension=""].file> + '<}> + " + ); + } +} + void generatePackageIndex(PathConfig pcfg) { targetFile = pcfg.bin + "Packages" + pcfg.packageName + "index.md"; @@ -106,9 +134,9 @@ void generatePackageIndex(PathConfig pcfg) { '<}> ' ':::warning - 'This message is automatically generated. The authors of <pcfg.packageName> probably prefer open-source licenses for their dependencies which are permissive to commercial exploitation - 'and any kind of reuse, and **non-viral**, however they are not liable for any damages caused by the licenses of the above dependencies, or changes therein. - 'The diligent user checks if these licenses are compatible with their situation! + 'You should check that the licenses of the above dependencies are compatible with your goals and situation. The authors and owners of <pcfg.packageName> cannot be held liable for any damages caused by the use of those licenses, or changes therein. + ' + 'The authors contributing to <pcfg.packageName> do prefer open-source licenses for their dependencies that are permissive to commercial exploitation and any kind of reuse, and that are non-viral. '::: " ); From 8978797252fca3fdb71f08f73c7884dc3c075812 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 31 Jan 2023 14:59:24 +0100 Subject: [PATCH 061/120] added docs and renamed some docs and added details --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index a555bb7d287..833b328c8c1 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -133,7 +133,7 @@ void generatePackageIndex(PathConfig pcfg) { '<for (loc d <- dependencies) {> * <d[extension=""].file> '<}> ' - ':::warning + ':::info 'You should check that the licenses of the above dependencies are compatible with your goals and situation. The authors and owners of <pcfg.packageName> cannot be held liable for any damages caused by the use of those licenses, or changes therein. ' 'The authors contributing to <pcfg.packageName> do prefer open-source licenses for their dependencies that are permissive to commercial exploitation and any kind of reuse, and that are non-viral. From 03eb7bfd6396e09eee5f6cd79e6d91d49e28c6d9 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 8 Feb 2023 10:11:41 +0100 Subject: [PATCH 062/120] commands block had an extra newline always --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 833b328c8c1..da032ecba5c 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -93,7 +93,7 @@ void storeImportantProjectMetaData(PathConfig pcfg) { return; } - if (pcfg.license?) { + if (pcfg.license? && exists(pcfg.license)) { copy(pcfg.license, pcfg.bin + "LICENSE_<pcfg.packageName>.txt"); } @@ -458,7 +458,7 @@ list[Output] compileMarkdown([str first:/^\s*```rascal-commands<rest1:.*>$/, *st return [ Output::empty(), // must have an empty line out("```rascal <rest1>"), - *[out(l) | l <- block], + *[out(l) | l <- trim(block)], Output::empty(), out("```"), *[ @@ -467,7 +467,7 @@ list[Output] compileMarkdown([str first:/^\s*```rascal-commands<rest1:.*>$/, *st out(":::") | /errors/ !:= rest1, filterErrors(stderr) != "" ], - *[err(error("rascal-declare block failed: <stderr>", pcfg.currentFile(offset, 1, <line, 0>, <line, 1>))) | filterErrors(stderr) != ""], + *[err(error("rascal-commands block failed: <stderr>", pcfg.currentFile(offset, 1, <line, 0>, <line, 1>))) | filterErrors(stderr) != ""], *compileMarkdown(rest2, line + 1 + size(block) + 1, offset + length(first) + length(block), pcfg, exec, ind, dtls, sidebar_position=sidebar_position) ]; } From 5cb55aa57f6672aa15b55acf45a8f8a99682da22 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 9 Feb 2023 17:29:19 +0100 Subject: [PATCH 063/120] removed mysterious dot and fixed capitalization of links with MultipleWordInThem --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 2 +- src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index da032ecba5c..7bb5ecaeb47 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -629,7 +629,7 @@ default list[Output] compileMarkdown([/^<prefix:.*>\(\(<link:[A-Za-z0-9\-\ \t\.\ // ambiguous resolution, first try and resolve within the current course: if ({u} := ind["<capitalize(pcfg.currentRoot.file)>:<removeSpaces(link)>"]) { u = /^\/assets/ := u ? u : "<p2r><u>"; - return compileMarkdown(["<prefix>[./<addSpaces(link)>](<u>)<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position); + return compileMarkdown(["<prefix>[<addSpaces(link)>](<u>)<postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position); } else if ({u} := ind["<capitalize(pcfg.currentRoot.file)>-<removeSpaces(link)>"]) { u = /^\/assets/ := u ? u : "<p2r><u>"; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc index 995009c32fd..40b56b3bc06 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc @@ -47,7 +47,7 @@ str addSpaces(/^<prefix:.+>::<name:[^:]+>$/) str addSpaces(/^<prefix:[A-Za-z0-9\ ]+[a-z0-9]><postfix:[A-Z].+>/) = addSpaces("<uncapitalize(prefix)> <uncapitalize(postfix)>"); -default str addSpaces(str s) = split("-", s)[-1]; +default str addSpaces(str s) = capitalize(split("-", s)[-1]); @synopsis{produces `"../../.."` for pathToRoot(|aap:///a/b|, |aap:///a/b/c/d|)} str pathToRoot(loc root, loc src, bool isPackageCourse) From 1fb938f74416c1bdf8cb730ff5fe7a78cd3be8e1 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 9 Feb 2023 18:59:40 +0100 Subject: [PATCH 064/120] fixed bug --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 7bb5ecaeb47..fdad927467b 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -458,8 +458,7 @@ list[Output] compileMarkdown([str first:/^\s*```rascal-commands<rest1:.*>$/, *st return [ Output::empty(), // must have an empty line out("```rascal <rest1>"), - *[out(l) | l <- trim(block)], - Output::empty(), + *[out(l) | l <- block], out("```"), *[ out(":::danger"), From d565a05f8bcd53a8738369f89da99ead602bf468 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 22 May 2023 09:48:45 +0200 Subject: [PATCH 065/120] removed extra new line after the latest function header --- .../tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 7532c0e2407..0dc272ee2ff 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -118,7 +118,7 @@ list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, Pat *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo), Output::empty(), out("```rascal"), - *[out(defLine), empty() | ov <- overloads, str defLine <- split("\n", ov)], + *([out(defLine), empty() | ov <- overloads, str defLine <- split("\n", ov)][..-1]), out("```") ]; From a542480d83b9ee6c64040f3aa900c3990369cebc Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 8 Jun 2023 17:10:19 +0200 Subject: [PATCH 066/120] if a code block expects errors but no errors are reported, this is now flagged as an error.\n\nThis helps to avoid spurious error expect declarations that may start hiding unexpected errors accidentally in the future. --- .../tutor/lang/rascal/tutor/Compiler.rsc | 16 ++++- .../rascal/tutor/conversions/SplitDocTag.rsc | 66 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/SplitDocTag.rsc diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index fdad927467b..5180d7ff9ed 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -723,8 +723,9 @@ list[Output] compileRascalShell(list[str] block, bool allowErrors, bool isContin exec.reset(); } + errorsDetected = false; lineOffsetHere = 0; - return OUT:for (str line <- block) { + result = OUT:for (str line <- block) { if (/^\s*\/\/<comment:.*>$/ := line) { // comment line append OUT : out("```"); append OUT : out(trim(comment)); @@ -742,6 +743,7 @@ list[Output] compileRascalShell(list[str] block, bool allowErrors, bool isContin if (filterErrors(stderr) != "" && /cancelled/ !:= stderr) { for (allowErrors, str errLine <- split("\n", stderr)) { + errorsDetected = true; append OUT : out(errLine); } @@ -783,6 +785,17 @@ list[Output] compileRascalShell(list[str] block, bool allowErrors, bool isContin lineOffsetHere +=1; } + + if (allowErrors && !errorsDetected) { + result += [ + out(":::warning"), + out("The above code block was declared to expect errors, but no errors were detected during its execution."), + out(":::"), + err(error("Code execution failed to produce an expected error", pcfg.currentFile(offset, 1, <lineOffset + lineOffsetHere, 0>, <lineOffset + lineOffsetHere, 1>))) + ]; + } + + return result; } @synopsis{Prepare blocks run the REPL but show no input or output} @@ -792,6 +805,7 @@ list[Output] compileRascalShellPrepare(list[str] block, bool isContinued, int li } lineOffsetHere = 0; + return OUT:for (str line <- block) { output = exec.eval(line); result = output["text/plain"]?""; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/SplitDocTag.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/SplitDocTag.rsc new file mode 100644 index 00000000000..408be729693 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/SplitDocTag.rsc @@ -0,0 +1,66 @@ +module lang::rascal::tutor::conversions::SplitDocTag + +import util::FileSystem; +import lang::rascal::\syntax::Rascal; +import ParseTree; +import IO; +import String; + +list[loc] findDocTags(loc root) + = [*findDocTags(parse(#start[Module], f).top) | loc f <- find(root, "rsc") ]; + +list[loc] findDocTags(Module m) + = [ t.src | /t:(Tag) `@doc <TagString _>` := m]; + +void rewriteDocTags(loc root) { + locs = findDocTags(root); + + for (l <- locs) { + rewriteDocTag(l); + } +} + +void rewriteDocTag(loc t) { + T = parse(#Tag, trim(readFile(t))); + + // drop the { } and the leading and trailing whitespace + cleanContent = trim("<T.contents>"[1..-1]); + newContent = ""; + + while (/^#### <heading:[A-Z][a-z]+>\s*<body:.*>\n\s*<rest:####.*>\s*$/m := cleanContent) { + if (heading == "Synopsis") { + newContent = "@synopsis{<trim(body)>}<if (trim(body)[-1] != ".") {>.<}> + '"; + } else { + newContent += "@<toLowerCase(heading)>{ + '<body> + '} + '"; + } + cleanContent = trim(rest); + } + + if (/^####\s*<heading:[A-Z][a-z]+>\s*\n<body:.*>$/ := cleanContent) { + if (heading == "Synopsis") { + newContent = "@synopsis{<trim(body)><if (trim(body)[-1] != ".") {>.<}>} + '"; + } else { + println("doing <heading> with <body>"); + newContent += "@<toLowerCase(heading)>{ + '<trim(body)> + '} + '"; + } + cleanContent = ""; + } + + println("REPORTING <t> + ' NEW : <newContent> + ' LEFT: <cleanContent>"); +} + +public str example = + "#### Synopsis + ' + 'Syntax definition for S-Expressions, based on http://people.csail.mit.edu/rivest/Sexp.txt"; + From 9c8e94a67ba20e32a26d430b8bf782ab52f56297 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 12 Jun 2023 10:44:50 +0200 Subject: [PATCH 067/120] added optional pages about funding and citation to packages --- .../tutor/lang/rascal/tutor/Compiler.rsc | 45 ++++++++++++++++++- .../tutor/lang/rascal/tutor/Names.rsc | 2 + 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 5180d7ff9ed..e5a862fe21b 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -97,6 +97,14 @@ void storeImportantProjectMetaData(PathConfig pcfg) { copy(pcfg.license, pcfg.bin + "LICENSE_<pcfg.packageName>.txt"); } + if (pcfg.citation? && exists(pcfg.citation)) { + copy(pcfg.citation, pcfg.bin + "CITATION.md"); + } + + if (pcfg.funding? && exists(pcfg.funding)) { + copy(pcfg.funding, pcfg.bin + "FUNDING.md"); + } + dependencies = [ f | f <- pcfg.classloaders, exists(f), f.extension=="jar"]; if (dependencies != []) { @@ -120,6 +128,39 @@ void generatePackageIndex(PathConfig pcfg) { '<readFile(pcfg.license)>"); } + if (pcfg.funding?) { + writeFile(targetFile.parent + "Funding.md", + "--- + 'title: Funding sources of <pcfg.packageName> + '--- + ' + ':::info + 'Open-source software is free for use, yet it does not come for free. + 'The following sources of funding have been instrumental in the creation + 'and maintenance of <pcfg.packageName>. You may consider also to become + 'a [sponsor](https://github.com/sponsors/usethesource?o=esb) + '::: + ' + '<readFile(pcfg.funding)>"); + } + + if (pcfg.citation?) { + writeFile(targetFile.parent + "Citation.md", + "--- + 'title: Citing <pcfg.packageName> + '--- + ' + ':::info + 'Open-source software is [citeable](https://www.software.ac.uk/how-cite-software) output of research and development efforts. + 'Citing software **recognizes** the associated investment and the quality of the result. + 'If you use this software, it is becoming standard practise to recognize the authors by citing this work as + 'they have indicated below. In turn their effort might be **awarded** with renewed funding + 'based on the evidence of your appreciation. + '::: + ' + '<readFile(pcfg.citation)>"); + } + dependencies = [ f | f <- pcfg.classloaders, exists(f), f.extension=="jar"]; if (dependencies != []) { @@ -153,7 +194,9 @@ void generatePackageIndex(PathConfig pcfg) { '<for (src <- pcfg.srcs, src.file notin {"src", "rascal", "api"}) {>* [<capitalize(src.file)>](../../Packages/<pcfg.packageName>/<capitalize(src.file)>) '<}>* [Stackoverflow questions](https://stackoverflow.com/questions/tagged/rascal+<pcfg.packageName>) '<if (pcfg.license?) {>* [Open-source license](../../Packages/<pcfg.packageName>/License.md)<}> - '<if (dependencies != []) {> * [Dependencies](../../Packages/<pcfg.packageName>/Dependencies.md)<}> + '<if (pcfg.citation?) {>* How to [cite this software](../../Packages/<pcfg.packageName>/Citation.md)<}> + '<if (pcfg.funding?) {>* Follow the [money](../../Packages/<pcfg.packageName>/Funding.md) sources.<}> + '<if (dependencies != []) {> * Check the [dependencies](../../Packages/<pcfg.packageName>/Dependencies.md)<}> '<if (pcfg.sources?) {>* [Source code](<"<pcfg.sources>"[1..-1]>)<}> '<if (pcfg.issues?) {>* [Issue tracker](<"<pcfg.issues>"[1..-1]>)<}> ' diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc index 40b56b3bc06..277f669cafd 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc @@ -12,6 +12,8 @@ data PathConfig( loc sources=|http://github.com/usethesource/rascal|, loc issues=|http://github.com/usethesource/rascal/issues|, loc license=|cwd:///LICENSE.md|, + loc citation=|cwd:///CITATION.md|, + loc funding=|cwd:///FUNDING.md|, str packageVersion=getRascalVersion(), bool isPackageCourse=false ); From a061cad27acddf4a803872a65382f1e53b77c996 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 12 Jun 2023 10:49:42 +0200 Subject: [PATCH 068/120] better text --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index e5a862fe21b..1d034c0d82f 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -153,9 +153,9 @@ void generatePackageIndex(PathConfig pcfg) { ':::info 'Open-source software is [citeable](https://www.software.ac.uk/how-cite-software) output of research and development efforts. 'Citing software **recognizes** the associated investment and the quality of the result. - 'If you use this software, it is becoming standard practise to recognize the authors by citing this work as - 'they have indicated below. In turn their effort might be **awarded** with renewed funding - 'based on the evidence of your appreciation. + 'If you use open-source software, it is becoming standard practise to recognize the work as + 'its authors have indicated below. In turn their effort might be **awarded** with renewed <if (pcfg.funding?) {>[funding](../../Packages/<pcfg.packageName>/Funding.md)<} else {>funding<}> for <pcfg.packageName> + 'based on the evidence of your appreciation, and it may help their individual career perspectives. '::: ' '<readFile(pcfg.citation)>"); From d486ddc7631992bbfc0fb9a3419ca5a7484dc0ca Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Sat, 24 Jun 2023 14:01:59 +0200 Subject: [PATCH 069/120] fixed some bugs --- .../lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 0dc272ee2ff..dbea736efbf 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -3,6 +3,7 @@ module lang::rascal::tutor::apidoc::GenerateMarkdown import List; import String; import util::Reflective; +import Location; import Message; import lang::rascal::tutor::apidoc::DeclarationInfo; @@ -99,6 +100,8 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC out("import <replaceAll(d.moduleName, "/", "::")>;"), out("```"), Output::empty(), + out("#### Source code"), + out("\<<((pcfg.sources + "blob") + "main") + relativize(pcfg.currentRoot, d.src).path>\>"), *[ out("#### Dependencies"), out("```rascal"), @@ -114,12 +117,11 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("## function <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), - Output::empty(), - *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo), - Output::empty(), out("```rascal"), *([out(defLine), empty() | ov <- overloads, str defLine <- split("\n", ov)][..-1]), - out("```") + out("```"), + Output::empty(), + *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo) ]; list[Output] declInfo2Doc(str parent, d:testInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = From 31777b4cda362645ca3380f5e2686b5b9291a39a Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Sat, 24 Jun 2023 18:23:56 +0200 Subject: [PATCH 070/120] better support for source links in documentation --- src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc | 1 + .../tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc index 277f669cafd..a84b48586d4 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc @@ -9,6 +9,7 @@ import util::Reflective; data PathConfig( str packageName="", str packageGroup="", + loc packageRoot=|unknown:///|, loc sources=|http://github.com/usethesource/rascal|, loc issues=|http://github.com/usethesource/rascal/issues|, loc license=|cwd:///LICENSE.md|, diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index dbea736efbf..b33b4336fbd 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -100,8 +100,10 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC out("import <replaceAll(d.moduleName, "/", "::")>;"), out("```"), Output::empty(), - out("#### Source code"), - out("\<<((pcfg.sources + "blob") + "main") + relativize(pcfg.currentRoot, d.src).path>\>"), + *[out("#### Source code"), + out("\<<(pcfg.sources + relativize(pcfg.packageRoot, pcfg.currentRoot).path) + relativize(pcfg.currentRoot, d.src).path>\>"), + Output::empty() | pcfg.sources?, pcfg.packageRoot? + ], *[ out("#### Dependencies"), out("```rascal"), From 52ecbaa9b9e335d8f4f832d13f0ca6f65fabb020 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Sat, 24 Jun 2023 20:34:54 +0200 Subject: [PATCH 071/120] fixed extra space --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 1d034c0d82f..06dc64ae6ba 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -196,7 +196,7 @@ void generatePackageIndex(PathConfig pcfg) { '<if (pcfg.license?) {>* [Open-source license](../../Packages/<pcfg.packageName>/License.md)<}> '<if (pcfg.citation?) {>* How to [cite this software](../../Packages/<pcfg.packageName>/Citation.md)<}> '<if (pcfg.funding?) {>* Follow the [money](../../Packages/<pcfg.packageName>/Funding.md) sources.<}> - '<if (dependencies != []) {> * Check the [dependencies](../../Packages/<pcfg.packageName>/Dependencies.md)<}> + '<if (dependencies != []) {>* Check the [dependencies](../../Packages/<pcfg.packageName>/Dependencies.md)<}> '<if (pcfg.sources?) {>* [Source code](<"<pcfg.sources>"[1..-1]>)<}> '<if (pcfg.issues?) {>* [Issue tracker](<"<pcfg.issues>"[1..-1]>)<}> ' From 333d113d1272b6cb3de616658e475b292eda418a Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Sat, 24 Jun 2023 20:51:13 +0200 Subject: [PATCH 072/120] fixed links --- .../tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index b33b4336fbd..4764a56ceb7 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -101,8 +101,8 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC out("```"), Output::empty(), *[out("#### Source code"), - out("\<<(pcfg.sources + relativize(pcfg.packageRoot, pcfg.currentRoot).path) + relativize(pcfg.currentRoot, d.src).path>\>"), - Output::empty() | pcfg.sources?, pcfg.packageRoot? + out("<(pcfg.sources + relativize(pcfg.packageRoot, pcfg.currentRoot).path) + relativize(pcfg.currentRoot, d.src).path>"), + Output::empty() | pcfg.sources?, pcfg.packageRoot? ], *[ out("#### Dependencies"), From 5bbf0e80200268b044caed672de272f83166e05a Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Sun, 25 Jun 2023 14:12:26 +0200 Subject: [PATCH 073/120] do not generate source links if its not a package; and fix the links --- .../tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 4764a56ceb7..4ef9059fc24 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -101,8 +101,8 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC out("```"), Output::empty(), *[out("#### Source code"), - out("<(pcfg.sources + relativize(pcfg.packageRoot, pcfg.currentRoot).path) + relativize(pcfg.currentRoot, d.src).path>"), - Output::empty() | pcfg.sources?, pcfg.packageRoot? + out("<(pcfg.sources + relativize(pcfg.packageRoot, pcfg.currentRoot).path) + relativize(pcfg.currentRoot, d.src).path>"[1..-1]), + Output::empty() | pcfg.isPackageCourse, pcfg.sources?, pcfg.packageRoot? ], *[ out("#### Dependencies"), From de980d9d654d06f9365ff2ddea0874f2c9383b1e Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Sun, 25 Jun 2023 20:50:09 +0200 Subject: [PATCH 074/120] tutor now is type-correct, also added FUNDING and CITATION --- .../tutor/lang/rascal/tutor/Compiler.rsc | 16 +++++++++------- .../rascal/tutor/apidoc/GenerateMarkdown.rsc | 2 +- .../rascal/tutor/questions/QuestionCompiler.rsc | 8 ++++---- .../rascal/tutor/questions/ValueGenerator.rsc | 2 +- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 06dc64ae6ba..05803723298 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -428,7 +428,7 @@ list[Message] compileMarkdownFile(loc m, PathConfig pcfg, CommandExecutor exec, if (!exists(targetFile) || lastModified(m) > lastModified(targetFile)) { println("compiling <m> [Normal Markdown]"); - list[Output] output = compileMarkdown(m, pcfg[currentFile=m], exec, ind, order, sidebar_position=sidebar_position) + [Output::empty()]; + list[Output] output = compileMarkdown(m, pcfg[currentFile=m], exec, ind, sidebar_position=sidebar_position) + [Output::empty()]; writeFile(targetFile, "<for (line(x) <- output) {><x> @@ -768,6 +768,8 @@ list[Output] compileRascalShell(list[str] block, bool allowErrors, bool isContin errorsDetected = false; lineOffsetHere = 0; + list[Output] result = []; + result = OUT:for (str line <- block) { if (/^\s*\/\/<comment:.*>$/ := line) { // comment line append OUT : out("```"); @@ -778,11 +780,11 @@ list[Output] compileRascalShell(list[str] block, bool allowErrors, bool isContin append out("<exec.prompt()><line>"); output = exec.eval(line); - result = output["text/plain"]?""; - stderr = output["application/rascal+stderr"]?""; - stdout = output["application/rascal+stdout"]?""; - shot = output["application/rascal+screenshot"]?""; - png = output["image/png"]?""; + str result = output["text/plain"]?""; + str stderr = output["application/rascal+stderr"]?""; + str stdout = output["application/rascal+stdout"]?""; + str shot = output["application/rascal+screenshot"]?""; + str png = output["image/png"]?""; if (filterErrors(stderr) != "" && /cancelled/ !:= stderr) { for (allowErrors, str errLine <- split("\n", stderr)) { @@ -940,4 +942,4 @@ private map[str, str] subscripts "V" : "\u1d65", "X" : "\u2093" ); - \ No newline at end of file + diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 4ef9059fc24..a53542c1ec3 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -33,7 +33,7 @@ list[Output] generateAPIMarkdown(str parent, loc moduleLoc, PathConfig pcfg, Com // filter the tests tests = [t | t:testInfo() <- dinfo]; - isDemo = DeclarationInfo i <- dinfo && i is moduleInfo && i.demo; + isDemo = DeclarationInfo k <- dinfo && k is moduleInfo && k.demo; // remove the tests dinfo -= tests; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.rsc index d0a26fe262f..2abfcd9f302 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.rsc @@ -13,9 +13,9 @@ import util::Eval; import DateTime; import ParseTree; -import lang::rascal::tutor::Questions; -import lang::rascal::tutor::ParseQuestions; -import lang::rascal::tutor::ValueGenerator; +import lang::rascal::tutor::questions::Questions; +import lang::rascal::tutor::questions::ParseQuestions; +import lang::rascal::tutor::questions::ValueGenerator; int countGenAndUse((Cmd) `<EvalCmd c>`){ n = 0; @@ -196,7 +196,7 @@ str removeComments(Intro? intro){ } public str compileQuestions(loc qloc, PathConfig pcfg) { - pcfg = pathConfig(srcs=[|test-modules:///|]+pcfg.srcs,libs=pcfg.libs,bin=pcfg.bin,courses=pcfg.courses); + pcfg = pathConfig(srcs=[|test-modules:///|]+pcfg.srcs,libs=pcfg.libs,bin=pcfg.bin); return process(qloc, pcfg); } diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ValueGenerator.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ValueGenerator.rsc index 98814e2e836..44bac5b751b 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ValueGenerator.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/ValueGenerator.rsc @@ -7,7 +7,7 @@ import List; import Set; import String; import DateTime; -import lang::rascal::tutor::Questions; +import lang::rascal::tutor::questions::Questions; import Type; lexical IntCon = [0-9]+ !>> [0-9]; From f584a785359f75be0821379381513d07325cf1b8 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 29 Jun 2023 16:20:17 +0200 Subject: [PATCH 075/120] fixes #24 --- src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc index 33dd2481ce6..6796af49d07 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -48,7 +48,8 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC // First we handle the root concept { - <capitalize(src.file), "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/index.md"> + <capitalize(src.file), "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/index.md">, + <"course:<capitalize(src.file)>", "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/index.md"> } + // Then we handle the cases where the concept name is the same as the folder it is nested in: From e6d250cc757d36436039206fde2c01ff1885912d Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Sat, 1 Jul 2023 14:37:40 +0200 Subject: [PATCH 076/120] remove superfluous newlines in full function and test bodies --- .../tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index a53542c1ec3..cd6ce634d3f 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -120,7 +120,7 @@ list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, Pat [ out("## function <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), out("```rascal"), - *([out(defLine), empty() | ov <- overloads, str defLine <- split("\n", ov)][..-1]), + *([*[out(defLine) | str defLine <- split("\n", ov)], empty() | ov <- overloads]), out("```"), Output::empty(), *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo) From 1b4cd84d674916307bf2c5e2dc9556fe40d9cb4f Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Sat, 1 Jul 2023 14:38:38 +0200 Subject: [PATCH 077/120] dealing with package names with hyphens --- .../tutor/lang/rascal/tutor/Compiler.rsc | 34 +++++++-------- .../tutor/lang/rascal/tutor/Indexer.rsc | 41 ++++++++++--------- .../tutor/lang/rascal/tutor/Names.rsc | 5 +++ .../lang/rascal/tutor/apidoc/ExtractInfo.rsc | 2 +- 4 files changed, 44 insertions(+), 38 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 05803723298..d33b7a6863b 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -114,10 +114,10 @@ void storeImportantProjectMetaData(PathConfig pcfg) { " ); } -} +} void generatePackageIndex(PathConfig pcfg) { - targetFile = pcfg.bin + "Packages" + pcfg.packageName + "index.md"; + targetFile = pcfg.bin + "Packages" + package(pcfg.packageName) + "index.md"; if (pcfg.license?) { writeFile(targetFile.parent + "License.md", @@ -154,7 +154,7 @@ void generatePackageIndex(PathConfig pcfg) { 'Open-source software is [citeable](https://www.software.ac.uk/how-cite-software) output of research and development efforts. 'Citing software **recognizes** the associated investment and the quality of the result. 'If you use open-source software, it is becoming standard practise to recognize the work as - 'its authors have indicated below. In turn their effort might be **awarded** with renewed <if (pcfg.funding?) {>[funding](../../Packages/<pcfg.packageName>/Funding.md)<} else {>funding<}> for <pcfg.packageName> + 'its authors have indicated below. In turn their effort might be **awarded** with renewed <if (pcfg.funding?) {>[funding](../../Packages/<package(pcfg.packageName)>/Funding.md)<} else {>funding<}> for <pcfg.packageName> 'based on the evidence of your appreciation, and it may help their individual career perspectives. '::: ' @@ -190,13 +190,13 @@ void generatePackageIndex(PathConfig pcfg) { ' 'This is the documentation for version <pcfg.packageVersion> of <pcfg.packageName>. ' - '<if (src <- pcfg.srcs, src.file in {"src", "rascal", "api"}) {>* [API documentation](../../Packages/<pcfg.packageName>/API)<}> - '<for (src <- pcfg.srcs, src.file notin {"src", "rascal", "api"}) {>* [<capitalize(src.file)>](../../Packages/<pcfg.packageName>/<capitalize(src.file)>) + '<if (src <- pcfg.srcs, src.file in {"src", "rascal", "api"}) {>* [API documentation](../../Packages/<package(pcfg.packageName)>/API)<}> + '<for (src <- pcfg.srcs, src.file notin {"src", "rascal", "api"}) {>* [<capitalize(src.file)>](../../Packages/<package(pcfg.packageName)>/<capitalize(src.file)>) '<}>* [Stackoverflow questions](https://stackoverflow.com/questions/tagged/rascal+<pcfg.packageName>) - '<if (pcfg.license?) {>* [Open-source license](../../Packages/<pcfg.packageName>/License.md)<}> - '<if (pcfg.citation?) {>* How to [cite this software](../../Packages/<pcfg.packageName>/Citation.md)<}> - '<if (pcfg.funding?) {>* Follow the [money](../../Packages/<pcfg.packageName>/Funding.md) sources.<}> - '<if (dependencies != []) {>* Check the [dependencies](../../Packages/<pcfg.packageName>/Dependencies.md)<}> + '<if (pcfg.license?) {>* [Open-source license](../../Packages/<package(pcfg.packageName)>/License.md)<}> + '<if (pcfg.citation?) {>* How to [cite this software](../../Packages/<package(pcfg.packageName)>/Citation.md)<}> + '<if (pcfg.funding?) {>* Follow the [money](../../Packages/<package(pcfg.packageName)>/Funding.md) sources.<}> + '<if (dependencies != []) {>* Check the [dependencies](../../Packages/<package(pcfg.packageName)>/Dependencies.md)<}> '<if (pcfg.sources?) {>* [Source code](<"<pcfg.sources>"[1..-1]>)<}> '<if (pcfg.issues?) {>* [Issue tracker](<"<pcfg.issues>"[1..-1]>)<}> ' @@ -252,7 +252,7 @@ list[Message] compile(loc src, PathConfig pcfg, CommandExecutor exec, Index ind, else if (src.extension in {"png","jpg","svg","jpeg", "html", "js"}) { try { println("copying <src> [Asset]"); - copy(src, pcfg.bin + (pcfg.isPackageCourse ? "assets/Packages/<pcfg.packageName>" : "assets") + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, src).path); + copy(src, pcfg.bin + (pcfg.isPackageCourse ? "assets/Packages/<package(pcfg.packageName)>" : "assets") + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, src).path); return []; } @@ -288,7 +288,7 @@ list[Message] compileDirectory(loc d, PathConfig pcfg, CommandExecutor exec, Ind j.file = (j.file == j.parent[extension="md"].file) ? "index.md" : j.file; targetFile = pcfg.bin - + (pcfg.isPackageCourse ? "Packages/<pcfg.packageName>" : "") + + (pcfg.isPackageCourse ? "Packages/<package(pcfg.packageName)>" : "") + ((pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) ? "API" : capitalize(pcfg.currentRoot.file)) + relativize(pcfg.currentRoot, j)[extension="md"].path; @@ -345,7 +345,7 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) title = (d == pcfg.currentRoot && d.file in {"src","rascal","api"}) ? "API" : d.file; targetFile = pcfg.bin - + (pcfg.isPackageCourse ? "Packages/<pcfg.packageName>" : "") + + (pcfg.isPackageCourse ? "Packages/<package(pcfg.packageName)>" : "") + ((pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) ? "API" : capitalize(pcfg.currentRoot.file)) + relativize(pcfg.currentRoot, d).path + "index.md" @@ -361,7 +361,7 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) '<}>--- ' '<for (e <- d.ls, isDirectory(e) || e.extension in {"rsc", "md"}, e.file != "internal", !(e in pcfg.ignores)) {> - '* [<e[extension=""].file>](<p2r>/<if (pcfg.isPackageCourse) {>Packages/<pcfg.packageName>/<}><if (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><relativize(pcfg.currentRoot, e)[extension=isDirectory(e)?"":"md"].path>)<}>"); + '* [<e[extension=""].file>](<p2r>/<if (pcfg.isPackageCourse) {>Packages/<package(pcfg.packageName)>/<}><if (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><relativize(pcfg.currentRoot, e)[extension=isDirectory(e)?"":"md"].path>)<}>"); return []; } catch IO(msg): { return [error(msg, d)]; @@ -371,13 +371,13 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) @synopsis{Translates Rascal source files to docusaurus markdown.} list[Message] compileRascalFile(loc m, PathConfig pcfg, CommandExecutor exec, Index ind) { loc targetFile = pcfg.bin - + (pcfg.isPackageCourse ? "Packages/<pcfg.packageName>" : "") + + (pcfg.isPackageCourse ? "Packages/<package(pcfg.packageName)>" : "") + ((pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) ? "API" : capitalize(pcfg.currentRoot.file)) + relativize(pcfg.currentRoot, m)[extension="md"].path; errors = []; if (!exists(targetFile) || lastModified(targetFile) < lastModified(m)) { - str parentSlug = (|path:///| + (pcfg.isPackageCourse ? "Packages/<pcfg.packageName>" : "") + str parentSlug = (|path:///| + (pcfg.isPackageCourse ? "Packages/<package(pcfg.packageName)>" : "") + ((pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) ? "API" : capitalize(pcfg.currentRoot.file)) + relativize(pcfg.currentRoot, m).parent.path).path; @@ -420,7 +420,7 @@ list[Message] compileMarkdownFile(loc m, PathConfig pcfg, CommandExecutor exec, m.file = (m.file == m.parent[extension="md"].file) ? "index.md" : m.file; loc targetFile = pcfg.bin - + (pcfg.isPackageCourse ? "Packages/<pcfg.packageName>" : "") + + (pcfg.isPackageCourse ? "Packages/<package(pcfg.packageName)>" : "") + ((pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) ? "API" : capitalize(pcfg.currentRoot.file)) + relativize(pcfg.currentRoot, m)[extension="md"].path; @@ -752,7 +752,7 @@ list[Output] compileMarkdown([str first:/^\s*#+\s+<title:.*>$/, *str emptySectio = [] when !(/\S/ <- emptySection); @synopsis{this is when we have processed all the input lines} -list[Output] compileMarkdown([], int _/*line*/, int _/*offset*/, PathConfig _, CommandExecutor _, Index _, list[str] _) = []; +list[Output] compileMarkdown([], int _/*line*/, int _/*offset*/, PathConfig _, CommandExecutor _, Index _, list[str] _, int sidebar_position=-1) = []; @synopsis{all other lines are simply copied to the output stream} default list[Output] compileMarkdown([str head, *str tail], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc index 6796af49d07..d751368ba25 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -11,6 +11,9 @@ import Location; import lang::rascal::tutor::apidoc::DeclarationInfo; import lang::rascal::tutor::apidoc::ExtractInfo; import lang::rascal::tutor::Names; +// import Relation; +import Location; +import Set; public alias Index = rel[str reference, str url]; @@ -39,7 +42,7 @@ Index createConceptIndex(PathConfig pcfg) { } rel[str, str] createConceptIndex(list[loc] srcs, datetime lastModified, bool isPackageCourse, str packageName) - = {*createConceptIndex(src, lastModified, isPackageCourse, packageName) | src <- srcs}; + = {*createConceptIndex(src, lastModified, isPackageCourse, packageName) | src <- srcs, bprintln("Indexing <src>")}; @synopsis{creates a lookup table for concepts nested in a folder} rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageCourse, str packageName) @@ -48,8 +51,8 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC // First we handle the root concept { - <capitalize(src.file), "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/index.md">, - <"course:<capitalize(src.file)>", "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/index.md"> + <package(src.file), "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><package(src.file)><}>/index.md">, + <"course:<package(src.file)>", "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><package(src.file)><}>/index.md"> } + // Then we handle the cases where the concept name is the same as the folder it is nested in: @@ -77,7 +80,7 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC , f.parent.path != "/" , f.parent != src , f.parent.file == f[extension=""].file - , fr := "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<fragment(src, f)>" + , fr := "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<fragment(src, f)>" , cf := f[extension=""] } + @@ -106,10 +109,9 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC , f.parent.path != "/" , f.parent != src , f.parent.file != f[extension=""].file - , fr := "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<fragment(src, f)>" + , fr := "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<fragment(src, f)>" , cf := f[extension=""] } - + // Then we handle all folders. We assume all folders have an index.md (generated or manually provided) // This may generate some links exactly the same as above, and add some new ones. + @@ -132,17 +134,16 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC // `((Rascal:Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` <"<capitalize(src.file)>:<replaceAll(capitalize(relativize(src, f).path)[1..], "/", "-")>", fr> | loc f <- find(src, isDirectory) - , fr := "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<fragment(src, f)>" + , fr := "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<fragment(src, f)>" , f != src } + - - + // Now follow the index entries for image files: + // Now follow the index entries for image files: { <"<f.parent.file>-<f.file>", fr>, <f.file, fr>, <"<capitalize(src.file)>:<f.file>", fr> | loc f <- find(src, isImageFile), - fr := "/assets/<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}><relativize(src, f).path>" + fr := "/assets/<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}><relativize(src, f).path>" } + { // these are links to packages/folders/directories via module path prefixes, like `analysis::m3` <"<replaceAll(relativize(src, f).path[1..], "/", "::")>", fr>, @@ -154,7 +155,7 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC | loc f <- find(src, isDirectory) , /\/internal\// !:= f.path , f != src - , fr := "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<fragment(src, f)>" + , fr := "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<fragment(src, f)>" } + // Finally, the index entries for Rascal modules and declarations, as extracted from the source code: { // `((getDefaultPathConfig))` -> `Libary/util/Reflective#getDefaultPathConfig` @@ -174,31 +175,31 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC <"<capitalize(src.file)>:<item.kind>:<item.moduleName><sep><item.name>", fr > | item.name?, !(item is moduleInfo), sep <- {"::", "/", "-"}}, // ((Set)) -> `/Library/Set` - *{<item.moduleName, "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" >, - <"module:<item.moduleName>", "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" > + *{<item.moduleName, "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" >, + <"module:<item.moduleName>", "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" > | item is moduleInfo }, // `((Library:Set))` -> `/Library/Set` - *{<"<capitalize(src.file)>:<item.moduleName>", "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" >, - <"<capitalize(src.file)>:module:<item.moduleName>", "<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" > | item is moduleInfo} + *{<"<capitalize(src.file)>:<item.moduleName>", "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" >, + <"<capitalize(src.file)>:module:<item.moduleName>", "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" > | item is moduleInfo} | loc f <- find(src, isFreshRascalFile(lastModified)), list[DeclarationInfo] inf := safeExtract(f), item <- inf, - fr := "/<if (isPackageCourse) {>/Packages/<packageName><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" + fr := "/<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" } ; private bool isConceptFile(loc f) = f.extension in {"md"}; private bool(loc) isFreshConceptFile(datetime lM) - = bool (loc f) { - return isConceptFile(f) && lastModified(f) > lM; - }; + = bool (loc f) { return isConceptFile(f); + return isConceptFile(f) && lastModified(f) > lM; + }; private bool(loc) isFreshRascalFile(datetime lM) = bool (loc f) { return f.extension in {"rsc"} && lastModified(f) > lM; - }; + }; private bool isImageFile(loc f) = f.extension in {"png", "jpg", "svg", "jpeg"}; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc index a84b48586d4..33d5fc5f448 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc @@ -32,6 +32,11 @@ str fragment(loc root, loc concept) = fragment(root, concept.parent + "index.md" str modulePath(str moduleName) = "<replaceAll(moduleName, "::", "/")>"; str moduleFragment(str moduleName) = "#<replaceAll(moduleName, "::", "-")>"; + +@synopsis{capitalizes and removes hyphens} +default str package(str input) = input; +str package(str input:/^[a-z].*$/) = package(capitalize(input)); +str package(/^<prefix:[a-zA-Z\_0-9]*>\-<rest:.*>$/) = package("<prefix><capitalize(rest)>"); str removeSpaces(/^<prefix:.*><spaces:\s+><postfix:.*>$/) = removeSpaces("<prefix><capitalize(postfix)>"); diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc index 56281fb8e35..78544876ea3 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc @@ -6,7 +6,7 @@ import String; import lang::rascal::\syntax::Rascal; import ParseTree; import util::Reflective; - +import DateTime; import lang::rascal::tutor::apidoc::DeclarationInfo; @synopsis{Extract declaration information from a Rascal module at given location.} From d804d0c46abb2a6619615281fe59dddc4839ac40 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Sat, 1 Jul 2023 17:40:10 +0200 Subject: [PATCH 078/120] fixed debug statement --- src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc index d751368ba25..cd94224d1dc 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -192,7 +192,7 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC private bool isConceptFile(loc f) = f.extension in {"md"}; private bool(loc) isFreshConceptFile(datetime lM) - = bool (loc f) { return isConceptFile(f); + = bool (loc f) { return isConceptFile(f) && lastModified(f) > lM; }; From 8d91e3637d66c44853214502c26d405c0b36c01d Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Sat, 1 Jul 2023 18:17:59 +0200 Subject: [PATCH 079/120] removed extra line at the end of a function --- .../tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index cd6ce634d3f..46498cee0bc 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -120,7 +120,7 @@ list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, Pat [ out("## function <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), out("```rascal"), - *([*[out(defLine) | str defLine <- split("\n", ov)], empty() | ov <- overloads]), + *([*[out(defLine) | str defLine <- split("\n", ov)], empty() | ov <- overloads][..-1]), out("```"), Output::empty(), *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo) From 1b9a8dcc7c9171f203b4f43f40137dfc270c3fd0 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 3 Jul 2023 14:48:35 +0200 Subject: [PATCH 080/120] fixes #26 and fixes #27 --- .../lang/rascal/tutor/apidoc/ExtractInfo.rsc | 9 +++++---- .../rascal/tutor/apidoc/GenerateMarkdown.rsc | 17 +++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc index 78544876ea3..45439298c70 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc @@ -24,11 +24,11 @@ list[DeclarationInfo] extractModule(m: (Module) `<Header header> <Body body>`) { tags = getTagContents(header.tags); name = "<header.name.names[-1]>"; tls = [*extractImport(moduleName, imp) | imp <- header.imports] - + [ *extractTopLevel(moduleName, tl) | tl <- body.toplevels ]; + + [*extractTopLevel(moduleName, tl) | tl <- body.toplevels ]; synopsis = getSynopsis(tags); - return moduleInfo( + return [moduleInfo( moduleName=moduleName, name=name, src=m@\loc, @@ -36,7 +36,7 @@ list[DeclarationInfo] extractModule(m: (Module) `<Header header> <Body body>`) { docs=sortedDocTags(tags), demo=(/demo|examples/ := moduleName), dependencies=[trim("<d>") | d <- header.imports, !(d is \syntax)] - ) + tls; + )] + tls; } /********************************************************************/ @@ -88,7 +88,7 @@ str align({Variant "|"}+ variants){ list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<Tags tags> <Visibility visibility> data <UserType user> <CommonKeywordParameters commonKeywordParameters> ;`) { dtags = getTagContents(tags); adtName = "<user.name>"; - + return [dataInfo(moduleName=moduleName, name=adtName, signature="data <user> <commonKeywordParameters>", src=d@\loc, synopsis=getSynopsis(dtags), docs=sortedDocTags(dtags))]; } @@ -96,6 +96,7 @@ list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<Tags tags> list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<Tags tags> <Visibility visibility> data <UserType user> <CommonKeywordParameters commonKeywordParameters> = <{Variant "|"}+ variants> ;`) { dtags = getTagContents(tags); adtName = "<user.name>"; + infoVariants = [ genVariant(moduleName, variant) | variant <- variants ]; return dataInfo(moduleName=moduleName, name=adtName, signature="data <user> <commonKeywordParameters> <align(variants)>", diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 46498cee0bc..1e6f08a3a04 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -13,6 +13,8 @@ import lang::rascal::tutor::Indexer; import lang::rascal::tutor::Compiler; import lang::rascal::tutor::repl::TutorCommandExecutor; import lang::rascal::tutor::Names; +import IO; +import Node; @synopsis{Generate markdown documentation from the declarations extracted from a Rascal module.} @description{ @@ -41,11 +43,10 @@ list[Output] generateAPIMarkdown(str parent, loc moduleLoc, PathConfig pcfg, Com dtls = sort(dup(["<capitalize(pcfg.currentRoot.file)>:<i.kind>:<i.moduleName>::<i.name>" | DeclarationInfo i <- dinfo, !(i is moduleInfo)])); // TODO: this overloading collection should happen in ExtractInfo - res = [ - ]; + res = []; int i = 0; while (i < size(dinfo)) { - j = i + 1; + int j = i + 1; list[str] overloads = []; if (dinfo[i] has name) { @@ -55,10 +56,10 @@ list[Output] generateAPIMarkdown(str parent, loc moduleLoc, PathConfig pcfg, Com // then we do not get to see the other overloads with the current group. Rewrite to use a "group-by" query. // Also this looses any additional documentation tags for anything but the first overloaded declaration - while (j < size(dinfo) && dinfo[i].name == dinfo[j].name) { - // this loops eats the other declarations with the same name (if consecutive!) - overloads += ((isDemo && dinfo[j].fullFunction?) ? dinfo[j].fullFunction : dinfo[j].signature); - j += 1; + while (j < size(dinfo) && dinfo[i].name == dinfo[j].name && dinfo[j].synopsis=="" && getName(dinfo[i]) == getName(dinfo[j]) /* same kinds */) { + // this loops eats the other declarations with the same name (if consecutive!) + overloads += [((isDemo && dinfo[j].fullFunction?) ? dinfo[j].fullFunction : dinfo[j].signature)]; + j += 1; } } @@ -173,7 +174,7 @@ list[Output] declInfo2Doc(str parent, d:aliasInfo(), list[str] overloads, PathCo out("## alias <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), empty(), out("```rascal"), - *[out(removeNewlines(ov)), empty() | ov <- overloads], + *[out(removeNewlines(ov)), empty() | ov <- overloads][..-1], out("```"), empty(), *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo) From 26e81c03add24bbf3e7b70147d64f47532545c03 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 3 Jul 2023 15:05:03 +0200 Subject: [PATCH 081/120] fix root name for course folders named src/main/rascal --- src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc index cd94224d1dc..975dc212699 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -51,8 +51,9 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC // First we handle the root concept { - <package(src.file), "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><package(src.file)><}>/index.md">, - <"course:<package(src.file)>", "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><package(src.file)><}>/index.md"> + <RootName, "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<RootName>/index.md">, + <"course:<RootName>", "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<RootName>/index.md"> + | str RootName := ((isPackageCourse && src.file in {"src","rascal","api"}) ? "API" : package(src.file)) } + // Then we handle the cases where the concept name is the same as the folder it is nested in: From df91c5a08c0389e61d04ac32ef88487320753eb4 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 3 Jul 2023 15:35:54 +0200 Subject: [PATCH 082/120] moved position of synopsis in API docs --- .../rascal/tutor/apidoc/GenerateMarkdown.rsc | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 1e6f08a3a04..7c4053a9b1c 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -95,6 +95,7 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC Output::empty(), out("\<div class=\"theme-doc-version-badge badge badge--secondary\"\>rascal-<getRascalVersion()>\</div\><if (pcfg.isPackageCourse) {> \<div class=\"theme-doc-version-badge badge badge--secondary\"\><pcfg.packageName>-<pcfg.packageVersion>\</div\><}>"), Output::empty(), + *[out(synopsis.content) | synopsis:docTag(label="synopsis") <- d.docs], out("#### Usage"), Output::empty(), out("```rascal"), @@ -120,16 +121,19 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("## function <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), + *[Output::empty(), out(synopsis.content) | synopsis:docTag(label="synopsis") <- d.docs], + empty(), out("```rascal"), - *([*[out(defLine) | str defLine <- split("\n", ov)], empty() | ov <- overloads][..-1]), + *([ *[out(defLine) | str defLine <- split("\n", ov)], empty() | ov <- overloads][..-1]), out("```"), Output::empty(), - *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo) + *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo) ]; list[Output] declInfo2Doc(str parent, d:testInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("## test <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), + *[Output::empty(), out(synopsis.content) | synopsis:docTag(label="synopsis") <- d.docs], Output::empty(), out("```rascal"), *[out(defLine) | str defLine <- split("\n", d.fullTest)], @@ -144,6 +148,7 @@ list[Output] declInfo2Doc(str parent, d:testInfo(), list[str] overloads, PathCon list[Output] declInfo2Doc(str parent, d:dataInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("## data <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), + *[out(synopsis.content) | synopsis:docTag(label="synopsis") <- d.docs], empty(), *[ out("```rascal"), @@ -158,6 +163,7 @@ list[Output] declInfo2Doc(str parent, d:testInfo(), list[str] overloads, PathCon list[Output] declInfo2Doc(str parent, d:syntaxInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("## syntax <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), + *[Output::empty(), out(synopsis.content) | synopsis:docTag(label="synopsis") <- d.docs], empty(), *[ out("```rascal"), @@ -172,6 +178,7 @@ list[Output] declInfo2Doc(str parent, d:syntaxInfo(), list[str] overloads, PathC list[Output] declInfo2Doc(str parent, d:aliasInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("## alias <d.name> {<moduleFragment(d.moduleName)>-<d.name>}"), + *[Output::empty(), out(synopsis.content) | synopsis:docTag(label="synopsis") <- d.docs], empty(), out("```rascal"), *[out(removeNewlines(ov)), empty() | ov <- overloads][..-1], @@ -186,7 +193,8 @@ default list[Output] declInfo2Doc(str parent, DeclarationInfo d, list[str] overl list[Output] tags2Markdown(list[DocTag] tags, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ // every doc tag has its own header title, except the "doc" tag which may contain them all (backward compatibility) - *(l != "doc" ? [out("#### <capitalize(l)>"), empty()] : []), + // and description starts without a header to improver the ratio between content and structure in the documentation for smaller functions and modules + *(l notin {"doc", "description"} ? [out("#### <capitalize(l)>"), empty()] : []), // here is where we get the origin information into the right place for error reporting: *compileMarkdown(split("\n", c), s.begin.line, s.offset, pcfg, exec, ind, dtls), @@ -194,7 +202,7 @@ list[Output] tags2Markdown(list[DocTag] tags, PathConfig pcfg, CommandExecutor e empty() // this assumes that the doc tags have been ordered correctly already by the extraction stage - | docTag(label=str l, src=s, content=str c) <- tags + | docTag(label=str l, src=s, content=str c) <- tags, l != "synopsis" ]; public str basename(str cn){ From c99ce3b0b3c6b385ff5189efe855ba68b9e18192 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 3 Jul 2023 17:18:02 +0200 Subject: [PATCH 083/120] use less markup, more content by removing headers for synopsis and description where possible --- .../tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 7c4053a9b1c..4bfc89aa473 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -114,8 +114,8 @@ list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathC | d.dependencies != [] ], Output::empty(), - *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo), - out("") + *tags2Markdown(d.docs, pcfg, exec, ind, dtls, demo, descriptionHeader=((pcfg.sources? && pcfg.packageRoot?) || d.dependencies != [])), + Output::empty() ]; list[Output] declInfo2Doc(str parent, d:functionInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = @@ -190,11 +190,11 @@ list[Output] declInfo2Doc(str parent, d:aliasInfo(), list[str] overloads, PathCo default list[Output] declInfo2Doc(str parent, DeclarationInfo d, list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [err(info("No content generated for <d>", d.src))]; -list[Output] tags2Markdown(list[DocTag] tags, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) +list[Output] tags2Markdown(list[DocTag] tags, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool _demo, bool descriptionHeader=false) = [ // every doc tag has its own header title, except the "doc" tag which may contain them all (backward compatibility) // and description starts without a header to improver the ratio between content and structure in the documentation for smaller functions and modules - *(l notin {"doc", "description"} ? [out("#### <capitalize(l)>"), empty()] : []), + *(l notin {"doc", (!descriptionHeader) ? "description" : ""} ? [out("#### <capitalize(l)>"), empty()] : []), // here is where we get the origin information into the right place for error reporting: *compileMarkdown(split("\n", c), s.begin.line, s.offset, pcfg, exec, ind, dtls), From 37787b66620ffd732206b05cee7385b0d2264644 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 3 Jul 2023 17:18:18 +0200 Subject: [PATCH 084/120] removed debug function --- .../rascalmpl/tutor/lang/rascal/tutor/conversions/ADtoMD.rsc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/ADtoMD.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/ADtoMD.rsc index 9eaf539fc7d..07aa1e8ed8f 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/ADtoMD.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/ADtoMD.rsc @@ -5,11 +5,6 @@ import util::FileSystem; import IO; import String; -void ad2md() { - ad2md(); - ad2md(); -} - void ad2md(loc root) { for (f <- find(root, isSourceFile)) convertFile(f); From bed792cd66d58557ed3f9aeb88f7b204a9def38d Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 3 Jul 2023 17:48:01 +0200 Subject: [PATCH 085/120] working on split doc tag --- .../rascal/tutor/conversions/SplitDocTag.rsc | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/SplitDocTag.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/SplitDocTag.rsc index 408be729693..cba4a0146c9 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/SplitDocTag.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/SplitDocTag.rsc @@ -16,20 +16,31 @@ void rewriteDocTags(loc root) { locs = findDocTags(root); for (l <- locs) { - rewriteDocTag(l); + new = rewriteDocTag(l); + println("--------------------------------------------------------"); + println("old: <readFile(l)>"); + println("========================================================"); + println("new: <new>"); + println("--------------------------------------------------------"); + } } -void rewriteDocTag(loc t) { +str rewriteDocTag(loc t) { T = parse(#Tag, trim(readFile(t))); - + // drop the { } and the leading and trailing whitespace cleanContent = trim("<T.contents>"[1..-1]); newContent = ""; + if (/####/ !:= cleanContent) { + // this is a synopsis + return "@synopsis{<cleanContent>}"; + } + while (/^#### <heading:[A-Z][a-z]+>\s*<body:.*>\n\s*<rest:####.*>\s*$/m := cleanContent) { if (heading == "Synopsis") { - newContent = "@synopsis{<trim(body)>}<if (trim(body)[-1] != ".") {>.<}> + newContent = "@synopsis{<trim(body)>}<if (trim(rest)[-1] != ".") {>.<}>} '"; } else { newContent += "@<toLowerCase(heading)>{ @@ -54,9 +65,7 @@ void rewriteDocTag(loc t) { cleanContent = ""; } - println("REPORTING <t> - ' NEW : <newContent> - ' LEFT: <cleanContent>"); + return newContent; } public str example = From dafd70ef314bdd46fb5be2230540d01caf752f45 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 13 Jul 2023 10:22:51 +0200 Subject: [PATCH 086/120] fixes usethesource/rascal-website#25 --- .../rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc index 45439298c70..8eff56814ee 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc @@ -66,7 +66,7 @@ list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<Tags tags> list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<Tags tags> <Visibility visibility> alias <UserType user> = <Type base> ;`) { dtags = getTagContents(tags); - return [ aliasInfo(moduleName=moduleName, name="<user>", signature="<base>", src=d@\loc, synopsis=getSynopsis(dtags), docs=sortedDocTags(dtags))]; + return [ aliasInfo(moduleName=moduleName, name="<user.name>", signature="<base>", src=d@\loc, synopsis=getSynopsis(dtags), docs=sortedDocTags(dtags))]; } list[DeclarationInfo] extractDecl(str moduleName, d: (Declaration) `<Tags tags> <Visibility visibility> tag <Kind kind> <Name name> on <{Type ","}+ types> ;`) From 116290cf1fc0af97cd7e1cb5cecf386118efa4a6 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 13 Jul 2023 10:25:49 +0200 Subject: [PATCH 087/120] minor updates in the upgrading tools --- .../rascal/tutor/conversions/SplitDocTag.rsc | 107 ++++++++++++------ .../lang/rascal/tutor/conversions/TrimDoc.rsc | 42 +++++++ 2 files changed, 116 insertions(+), 33 deletions(-) create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/TrimDoc.rsc diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/SplitDocTag.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/SplitDocTag.rsc index cba4a0146c9..0fd189694cd 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/SplitDocTag.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/SplitDocTag.rsc @@ -5,6 +5,8 @@ import lang::rascal::\syntax::Rascal; import ParseTree; import IO; import String; +import analysis::diff::edits::TextEdits; +import util::IDEServices; list[loc] findDocTags(loc root) = [*findDocTags(parse(#start[Module], f).top) | loc f <- find(root, "rsc") ]; @@ -12,20 +14,31 @@ list[loc] findDocTags(loc root) list[loc] findDocTags(Module m) = [ t.src | /t:(Tag) `@doc <TagString _>` := m]; -void rewriteDocTags(loc root) { - locs = findDocTags(root); - - for (l <- locs) { - new = rewriteDocTag(l); - println("--------------------------------------------------------"); - println("old: <readFile(l)>"); - println("========================================================"); - println("new: <new>"); - println("--------------------------------------------------------"); +void editLibrary(loc root) { + for (loc f <- find(root, "rsc")) { + editModule(parse(#start[Module], f).top); } } +void editModule(loc example) = editModule(parse(#start[Module], example).top); +list[TextEdit] editsForModule(loc example) = rewriteDocTags(example); + +void editModule(Module m) { + edits = rewriteDocTags(m@\loc.top); + executeDocumentEdits([changed(m@\loc.top, edits)]); + return; +} + +list[TextEdit] rewriteDocTags(loc root) + = [replace(l, rewriteDocTag(l)) | l <- findDocTags(root)]; + +void doSomething() { + executeDocumentEdits([changed(|project://rascal/src/org/rascalmpl/library/Boolean.rsc|, [replace( + |project://rascal/src/org/rascalmpl/library/Boolean.rsc|(1618,210,<108,0>,<125,1>), + "\n@synopsis{\n\nConvert Boolean value to string.\n\n}\n@description{\n\nMaps `true` to `\"true\"` and `false` to `\"false\"`.\n\n}\n@examples{\n\n```rascal-shell\nimport Boolean;\ntoString(true);\ntoString(false);\n```\n}")])]); +} + str rewriteDocTag(loc t) { T = parse(#Tag, trim(readFile(t))); @@ -38,34 +51,62 @@ str rewriteDocTag(loc t) { return "@synopsis{<cleanContent>}"; } - while (/^#### <heading:[A-Z][a-z]+>\s*<body:.*>\n\s*<rest:####.*>\s*$/m := cleanContent) { - if (heading == "Synopsis") { - newContent = "@synopsis{<trim(body)>}<if (trim(rest)[-1] != ".") {>.<}>} - '"; - } else { - newContent += "@<toLowerCase(heading)>{ - '<body> - '} - '"; + output = for (line <- split("\n", cleanContent)) { + println("line: <line>"); + switch (line) { + case /^####\s+[Ss]ynopsis\s*$/: { + println("SYN!"); + append "@synopsis{"; + } + case /^####\s+<heading:[A-Z][a-z]+>\s*$/: { + println("DESC! <heading>"); + append "}"; + append "@<toLowerCase(heading)>{"; + } + default: + { + // println("normal: <line>"); + append line; + } } - cleanContent = trim(rest); } - if (/^####\s*<heading:[A-Z][a-z]+>\s*\n<body:.*>$/ := cleanContent) { - if (heading == "Synopsis") { - newContent = "@synopsis{<trim(body)><if (trim(body)[-1] != ".") {>.<}>} - '"; - } else { - println("doing <heading> with <body>"); - newContent += "@<toLowerCase(heading)>{ - '<trim(body)> - '} - '"; - } - cleanContent = ""; + return " + '<for (l <- output) {><l> + '<}>}"; +} + +void executeDocumentEdits(list[DocumentEdit] edits) { + for (e <- edits) { + executeDocumentEdit(e); + } +} + +void executeDocumentEdit(removed(loc f)) { + remove(f.top); +} + +void executeDocumentEdit(created(loc f)) { + writeFile(f, ""); +} + +void executeDocumentEdit(renamed(loc from, loc to)) { + move(from.top, to.top, overwrite=true); +} + +void executeDocumentEdit(changed(loc file, list[TextEdit] edits)) { + assert isSorted(edits, less=bool (TextEdit e1, TextEdit e2) { + return e1.range.offset < e2.range.offset; + }); + + str content = readFile(file); + + for (replace(loc range, str repl) <- reverse(edits)) { + assert range.top == file.top; + content = "<content[..range.offset]><repl><content[range.offset+range.length..]>"; } - return newContent; + writeFile(file.top, content); } public str example = diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/TrimDoc.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/TrimDoc.rsc new file mode 100644 index 00000000000..91249e1b0b5 --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/TrimDoc.rsc @@ -0,0 +1,42 @@ +module lang::rascal::tutor::conversions::TrimDoc + +import util::FileSystem; +import lang::rascal::\syntax::Rascal; +import ParseTree; +import IO; +import String; + +void editLibrary(loc root) { + for (loc f <- find(root, "rsc")) { + editModule(parse(#start[Module], f)); + } +} + +void editModule(loc example) = editModule(parse(#start[Module], example).top); + +void editModule(start[Module] m) { + n = rewriteDocTags(m); + writeFile(m@\loc.top, "<n>"); + return; +} + +start[Module] rewriteDocTags(start[Module] m) = visit(m) { + case (Tag) `@synopsis <TagString c>` + => [Tag] "@synopsis{<trim("<"<c>"[1..-1]>")>}" + case jc:(Tag) `@javaClass<TagString c>` => jc + case jc:(Tag) `@contributor<TagString c>` => jc + case jc:(Tag) `@license<TagString c>` => jc + case jc:(Tag) `@expected<TagString c>` + => [Tag] "@expected{<trim("<"<c>"[1..-1]>")>}" + case jc:(Tag) `@ignoreCompiler<TagString c>` + => [Tag] "@ignoreCompiler{<trim("<"<c>"[1..-1]>")>}" + case jc:(Tag) `@ignore<TagString c>` + => [Tag] "@ignore{<trim("<"<c>"[1..-1]>")>}" + case jc:(Tag) `@ignoreInterpreter<TagString c>` + => [Tag] "@ignoreInterpreter{<trim("<"<c>"[1..-1]>")>}" + case (Tag) `@<Name n> <TagString c>` + => [Tag] "@<n>{ + '<trim("<"<c>"[1..-1]>")> + '}" +}; + \ No newline at end of file From f063bc9018a5ac403d05fb885ef54af942fe111c Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 13 Jul 2023 13:15:27 +0200 Subject: [PATCH 088/120] bumped versions --- .../rascalmpl/tutor/lang/rascal/tutor/conversions/TrimDoc.rsc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/TrimDoc.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/TrimDoc.rsc index 91249e1b0b5..e7b10364ad2 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/TrimDoc.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/conversions/TrimDoc.rsc @@ -32,6 +32,8 @@ start[Module] rewriteDocTags(start[Module] m) = visit(m) { => [Tag] "@ignoreCompiler{<trim("<"<c>"[1..-1]>")>}" case jc:(Tag) `@ignore<TagString c>` => [Tag] "@ignore{<trim("<"<c>"[1..-1]>")>}" + case jc:(Tag) `@tries<TagString c>` + => [Tag] "@tries{<trim("<"<c>"[1..-1]>")>}" case jc:(Tag) `@ignoreInterpreter<TagString c>` => [Tag] "@ignoreInterpreter{<trim("<"<c>"[1..-1]>")>}" case (Tag) `@<Name n> <TagString c>` From 3425ff4a3b2cfd6679fd6a32382152ba272156b5 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 23 Aug 2023 16:38:48 +0200 Subject: [PATCH 089/120] bumped rascal dependency and fixed compilation issues --- .../tutor/lang/rascal/tutor/repl/TutorIDEServices.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java index ab9f363e49b..d1cd70fe586 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java @@ -45,7 +45,7 @@ public PrintWriter stderr() { } @Override - public void browse(URI uri) { + public void browse(URI uri, String title, int column) { } From dbfd4db0b9d02f1d931e409ebc977f6b580557cd Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 31 Aug 2023 08:07:36 +0200 Subject: [PATCH 090/120] removed commented code --- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 4efab1ebacc..13eeaec7c20 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -234,10 +234,6 @@ private static RemoteWebDriver getBrowser(ChromeDriverService service) { .addArguments("--user-data-dir=/tmp/rascal-config/google-chrome") .setLogLevel(ChromeDriverLogLevel.OFF) ; - - // ?ChromeProfile profile = options.getProfile(); - // profile.setPreference("layout.css.devPixelsPerPx", "3"); - // options = options.setProfile(profile); RemoteWebDriver driver = new RemoteWebDriver(service.getUrl(), options); driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(3)); From f53468059430a321fcffa9ff3918b0157f575851 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 1 Sep 2023 14:05:45 +0200 Subject: [PATCH 091/120] finetuning of the screenshot feature, to help with salix tooltips --- .../tutor/repl/TutorCommandExecutor.java | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 13eeaec7c20..0a56ee6b8ca 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -13,17 +13,23 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.time.Duration; +import java.time.temporal.TemporalUnit; import java.util.Arrays; import java.util.Base64; import java.util.HashMap; import java.util.Map; import org.openqa.selenium.By; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.OutputType; +import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriverLogLevel; import org.openqa.selenium.chrome.ChromeDriverService; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.remote.RemoteWebDriver; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; import org.rascalmpl.ideservices.IDEServices; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.env.GlobalEnvironment; @@ -201,14 +207,27 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce if (metadata.get("url") != null && driver != null) { try { + // load the page driver.get(metadata.get("url")); driver.manage().window().maximize(); - // waiting for a better solution - Thread.sleep(1000); + + // wait for page to render completely + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15)); + wait.until(webDriver -> "complete".equals(((JavascriptExecutor) webDriver) + .executeScript("return document.readyState"))); - String screenshot = driver.findElement(By.tagName("body")) - .getScreenshotAs(OutputType.BASE64); + // crop to the body + WebElement body = driver.findElement(By.tagName("body")); + driver.manage().window().setSize(body.getSize()); + // calm down again + wait.until(webDriver -> "complete".equals(((JavascriptExecutor) webDriver) + .executeScript("return document.readyState"))); + + // take the screenshot + String screenshot = body.getScreenshotAs(OutputType.BASE64); + + // store the screenshot as an output result.put("application/rascal+screenshot", screenshot); } catch (Throwable e) { @@ -231,14 +250,15 @@ private static RemoteWebDriver getBrowser(ChromeDriverService service) { ChromeOptions options = new ChromeOptions() .setHeadless(true) .setBinary(BROWSER_BINARY) + .addArguments("--headless", "--disable-gpu", "--window-size=1920,1200","--ignore-certificate-errors","--disable-extensions","--no-sandbox","--disable-dev-shm-usage") .addArguments("--user-data-dir=/tmp/rascal-config/google-chrome") .setLogLevel(ChromeDriverLogLevel.OFF) ; RemoteWebDriver driver = new RemoteWebDriver(service.getUrl(), options); - driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(3)); - driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); - driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(5)); + driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(30)); + driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(30)); + driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(60)); driver.manage().window().maximize(); return driver; From 38fdd996bcea2cb4409a7a883e8540c9e2187eab Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 1 Sep 2023 14:08:52 +0200 Subject: [PATCH 092/120] removed unnecessary cropping --- .../lang/rascal/tutor/repl/TutorCommandExecutor.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 0a56ee6b8ca..b80e10ae8d0 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -216,15 +216,8 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce wait.until(webDriver -> "complete".equals(((JavascriptExecutor) webDriver) .executeScript("return document.readyState"))); - // crop to the body - WebElement body = driver.findElement(By.tagName("body")); - driver.manage().window().setSize(body.getSize()); - - // calm down again - wait.until(webDriver -> "complete".equals(((JavascriptExecutor) webDriver) - .executeScript("return document.readyState"))); - // take the screenshot + WebElement body = driver.findElement(By.tagName("body")); String screenshot = body.getScreenshotAs(OutputType.BASE64); // store the screenshot as an output From 4c6c58dbec0e39048dfc251ed8507f9590291cd8 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 1 Sep 2023 14:42:23 +0200 Subject: [PATCH 093/120] rascal-prepare blocks now also take screenshots for invisible prepations of salix screenshots --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index d33b7a6863b..86ebf370a8c 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -857,6 +857,8 @@ list[Output] compileRascalShellPrepare(list[str] block, bool isContinued, int li stderr = output["application/rascal+stderr"]?""; stdout = output["application/rascal+stdout"]?""; html = output["text/html"]?""; + str shot = output["application/rascal+screenshot"]?""; + str png = output["image/png"]?""; if (filterErrors(stderr) != "" && /cancelled/ !:= stderr) { for (errLine <- split("\n", stderr)) { @@ -874,6 +876,14 @@ list[Output] compileRascalShellPrepare(list[str] block, bool isContinued, int li ' <stderr>", pcfg.currentFile(offset, 1, <lineOffset + lineOffsetHere, 0>, <lineOffset + lineOffsetHere, 1>), cause=stderr)); } + if (shot != "") { + loc targetFile = pcfg.bin + "assets" + capitalize(pcfg.currentRoot.file) + relativize(pcfg.currentRoot, pcfg.currentFile)[extension=""].path; + targetFile.file = targetFile.file + "_screenshot_<lineOffsetHere+lineOffset>.png"; + println("screenshot <targetFile>"); + writeBase64(targetFile, shot); + append OUT: out("![image](<relativize(pcfg.bin, targetFile).path>)"); + } + lineOffsetHere +=1; } } From df6e2fb29467d95dd5b95cf1df445457fbd9312a Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 5 Sep 2023 10:48:02 +0200 Subject: [PATCH 094/120] added hacks to give Index.rsc files a different name to avoid collisions with index.html and index.md --- .../rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 12 ++++++++++-- src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc | 5 +++-- .../tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc | 5 +++++ .../lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 2 +- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 86ebf370a8c..d708358262d 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -360,8 +360,9 @@ list[Message] generateIndexFile(loc d, PathConfig pcfg, int sidebar_position=-1) '<if (sidebar_position != -1) {>sidebar_position: <sidebar_position> '<}>--- ' - '<for (e <- d.ls, isDirectory(e) || e.extension in {"rsc", "md"}, e.file != "internal", !(e in pcfg.ignores)) {> - '* [<e[extension=""].file>](<p2r>/<if (pcfg.isPackageCourse) {>Packages/<package(pcfg.packageName)>/<}><if (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><relativize(pcfg.currentRoot, e)[extension=isDirectory(e)?"":"md"].path>)<}>"); + '<for (loc e <- d.ls, isDirectory(e) || e.extension in {"rsc", "md"}, e.file != "internal", !(e in pcfg.ignores), !(e.file in {"index.rsc", "Index.rsc"})) {> + '* [<e[extension=""].file>](<p2r>/<if (pcfg.isPackageCourse) {>Packages/<package(pcfg.packageName)>/<}><if (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><relativize(pcfg.currentRoot, e)[extension=isDirectory(e)?"":"md"].path>)<}> + '<if (loc e <- d.ls, e.file in {"index.rsc", "Index.rsc"}) {>* [<e[extension=""].file>](<p2r>/<if (pcfg.isPackageCourse) {>Packages/<package(pcfg.packageName)>/<}><if (pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) {>API<} else {><capitalize(pcfg.currentRoot.file)><}><relativize(pcfg.currentRoot, e).parent.path>/module_Index.md)<}>"); return []; } catch IO(msg): { return [error(msg, d)]; @@ -374,6 +375,13 @@ list[Message] compileRascalFile(loc m, PathConfig pcfg, CommandExecutor exec, In + (pcfg.isPackageCourse ? "Packages/<package(pcfg.packageName)>" : "") + ((pcfg.isPackageCourse && pcfg.currentRoot.file in {"src","rascal","api"}) ? "API" : capitalize(pcfg.currentRoot.file)) + relativize(pcfg.currentRoot, m)[extension="md"].path; + + if (targetFile.file in {"index.md", "Index.md"}) { + // that would overwrite the actual index. Some modules can be named "Index.rsc or index.rsc" + // this underscore prefix is also reflected in the index builder of course! + targetFile.file = "module_Index.md"; + } + errors = []; if (!exists(targetFile) || lastModified(targetFile) < lastModified(m)) { diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc index 33d5fc5f448..d7d7b9ffaa2 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc @@ -30,8 +30,9 @@ str fragment(loc root, loc concept) = fragment(root, concept + "index.md") str fragment(loc root, loc concept) = fragment(root, concept.parent + "index.md") when concept.parent?, concept.parent.file == concept[extension=""].file; -str modulePath(str moduleName) = "<replaceAll(moduleName, "::", "/")>"; -str moduleFragment(str moduleName) = "#<replaceAll(moduleName, "::", "-")>"; +str modulePath(/^<prefix:.*>::Index$/) = modulePath("<prefix>::module_Index"); +default str modulePath(str moduleName) = "<replaceAll(moduleName, "::", "/")>"; +default str moduleFragment(str moduleName) = "#<replaceAll(moduleName, "::", "-")>"; @synopsis{capitalizes and removes hyphens} default str package(str input) = input; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc index 8eff56814ee..c44f22db261 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc @@ -23,6 +23,11 @@ list[DeclarationInfo] extractModule(m: (Module) `<Header header> <Body body>`) { moduleName = "<header.name>"; tags = getTagContents(header.tags); name = "<header.name.names[-1]>"; + + if (name == "Index") { + name = "module_Index"; + } + tls = [*extractImport(moduleName, imp) | imp <- header.imports] + [*extractTopLevel(moduleName, tl) | tl <- body.toplevels ]; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 4bfc89aa473..3a35b680cd3 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -88,7 +88,7 @@ private map[str,str] escapes = ("\\": "\\\\", "\"": "\\\""); list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("---"), - out("title: \"module <d.name>\""), + out("title: \"module <d.moduleName>\""), out("id: <d.name>"), out("slug: <parent>/<d.name>"), out("---"), From 24a42332beaa27b2e73afa9a27f6630a858fa586 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 11 Sep 2023 09:42:47 +0200 Subject: [PATCH 095/120] organized imports --- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index b80e10ae8d0..21518fe96e7 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -13,14 +13,12 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.time.Duration; -import java.time.temporal.TemporalUnit; import java.util.Arrays; import java.util.Base64; import java.util.HashMap; import java.util.Map; import org.openqa.selenium.By; -import org.openqa.selenium.Dimension; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.OutputType; import org.openqa.selenium.WebElement; @@ -28,7 +26,6 @@ import org.openqa.selenium.chrome.ChromeDriverService; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.remote.RemoteWebDriver; -import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import org.rascalmpl.ideservices.IDEServices; import org.rascalmpl.interpreter.Evaluator; @@ -243,7 +240,7 @@ private static RemoteWebDriver getBrowser(ChromeDriverService service) { ChromeOptions options = new ChromeOptions() .setHeadless(true) .setBinary(BROWSER_BINARY) - .addArguments("--headless", "--disable-gpu", "--window-size=1920,1200","--ignore-certificate-errors","--disable-extensions","--no-sandbox","--disable-dev-shm-usage") + .addArguments("--headless", "--disable-gpu", "--window-size=1900,1200","--ignore-certificate-errors","--disable-extensions","--no-sandbox","--disable-dev-shm-usage") .addArguments("--user-data-dir=/tmp/rascal-config/google-chrome") .setLogLevel(ChromeDriverLogLevel.OFF) ; From 94cab174035340a7b1b091914f20d0f411a3ce1f Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 11 Sep 2023 09:43:19 +0200 Subject: [PATCH 096/120] simplified titles to quiet the package menu a bit --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index d708358262d..64e7228554a 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -122,7 +122,7 @@ void generatePackageIndex(PathConfig pcfg) { if (pcfg.license?) { writeFile(targetFile.parent + "License.md", "--- - 'title: <pcfg.packageName> open-source license + 'title: License '--- ' '<readFile(pcfg.license)>"); @@ -131,7 +131,7 @@ void generatePackageIndex(PathConfig pcfg) { if (pcfg.funding?) { writeFile(targetFile.parent + "Funding.md", "--- - 'title: Funding sources of <pcfg.packageName> + 'title: Funding '--- ' ':::info @@ -147,7 +147,7 @@ void generatePackageIndex(PathConfig pcfg) { if (pcfg.citation?) { writeFile(targetFile.parent + "Citation.md", "--- - 'title: Citing <pcfg.packageName> + 'title: Citation '--- ' ':::info @@ -166,7 +166,7 @@ void generatePackageIndex(PathConfig pcfg) { if (dependencies != []) { writeFile(targetFile.parent + "Dependencies.md", "--- - 'title: <pcfg.packageName> dependencies + 'title: Dependencies '--- ' 'These are compile-time and run-time dependencies of <pcfg.packageName>: From 5c01f6781768f3fe1d9bcb1b36f045699db90106 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 22 Sep 2023 09:56:45 +0200 Subject: [PATCH 097/120] removed remnants of test-modules:// --- .../tutor/lang/rascal/tutor/questions/QuestionCompiler.java | 1 - .../tutor/lang/rascal/tutor/questions/QuestionCompiler.rsc | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java index 42eeb5407a9..07793da3f33 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java @@ -37,7 +37,6 @@ public QuestionCompiler(PathConfig pcfg) { final ModuleEnvironment top = new ModuleEnvironment("***question compiler***", heap); eval = new Evaluator(vf, System.in, System.err, System.out, top, heap); eval.addRascalSearchPath(URIUtil.rootLocation("std")); - eval.addRascalSearchPath(URIUtil.rootLocation("test-modules")); eval.getConfiguration().setRascalJavaClassPathProperty(javaCompilerPathAsString(pcfg.getJavaCompilerPath())); } diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.rsc index 2abfcd9f302..e67f7ec29c0 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.rsc @@ -196,7 +196,7 @@ str removeComments(Intro? intro){ } public str compileQuestions(loc qloc, PathConfig pcfg) { - pcfg = pathConfig(srcs=[|test-modules:///|]+pcfg.srcs,libs=pcfg.libs,bin=pcfg.bin); + pcfg = pathConfig(srcs=[|memory://test-modules/|]+pcfg.srcs,libs=pcfg.libs,bin=pcfg.bin); return process(qloc, pcfg); } @@ -479,7 +479,7 @@ loc makeQuestion(int questionId, PathConfig pcfg){ remove(f); } } - mloc = |test-modules:///| + "Question<questionId>.rsc"; + mloc = |memory://test-modules/| + "Question<questionId>.rsc"; return mloc; } From 0bb668d41d714a32a536fd9f5df82420fd3263ca Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 22 Sep 2023 09:56:58 +0200 Subject: [PATCH 098/120] properly escape title strings --- .../tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc index 3a35b680cd3..7ada5224f57 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/GenerateMarkdown.rsc @@ -88,7 +88,7 @@ private map[str,str] escapes = ("\\": "\\\\", "\"": "\\\""); list[Output] declInfo2Doc(str parent, d:moduleInfo(), list[str] overloads, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, bool demo) = [ out("---"), - out("title: \"module <d.moduleName>\""), + out("title: \"module <"<[d.moduleName]>"[2..-2]>\""), // we make sure to escape backslashes here (e.g. lang::pico::\syntax::Main) out("id: <d.name>"), out("slug: <parent>/<d.name>"), out("---"), From 00c1536e6d3fea371839a6a7f5d82d4c96aad4f7 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 18 Oct 2023 16:16:59 +0200 Subject: [PATCH 099/120] added RELEASE-NOTES.md feature --- .../tutor/lang/rascal/tutor/Compiler.rsc | 24 +++++++++++++++---- .../tutor/lang/rascal/tutor/Names.rsc | 1 + 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 64e7228554a..9ce9ee20fd4 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -98,11 +98,15 @@ void storeImportantProjectMetaData(PathConfig pcfg) { } if (pcfg.citation? && exists(pcfg.citation)) { - copy(pcfg.citation, pcfg.bin + "CITATION.md"); + copy(pcfg.citation, pcfg.bin + "CITATION_<pcfg.packageName>.md"); } if (pcfg.funding? && exists(pcfg.funding)) { - copy(pcfg.funding, pcfg.bin + "FUNDING.md"); + copy(pcfg.funding, pcfg.bin + "FUNDING_<pcfg.packageName>.md"); + } + + if (pcfg.releaseNotes? && exists(pcfg.releaseNotes)) { + copy(pcfg.releaseNotes, pcfg.bin + "RELEASE-NOTES_<pcfg.packageName>.md"); } dependencies = [ f | f <- pcfg.classloaders, exists(f), f.extension=="jar"]; @@ -161,6 +165,15 @@ void generatePackageIndex(PathConfig pcfg) { '<readFile(pcfg.citation)>"); } + if (pcfg.releaseNotes?) { + writeFile(targetFile.parent + "RELEASE-NOTES.md", + "--- + 'title: Release notes + '--- + ' + '<readFile(pcfg.releaseNotes)>"); + } + dependencies = [ f | f <- pcfg.classloaders, exists(f), f.extension=="jar"]; if (dependencies != []) { @@ -183,7 +196,7 @@ void generatePackageIndex(PathConfig pcfg) { ); } - writeFile(targetFile, + writeFile(targetFile.parent + "RELEASE-NOTES.md", "--- 'title: <pcfg.packageName> '--- @@ -193,10 +206,11 @@ void generatePackageIndex(PathConfig pcfg) { '<if (src <- pcfg.srcs, src.file in {"src", "rascal", "api"}) {>* [API documentation](../../Packages/<package(pcfg.packageName)>/API)<}> '<for (src <- pcfg.srcs, src.file notin {"src", "rascal", "api"}) {>* [<capitalize(src.file)>](../../Packages/<package(pcfg.packageName)>/<capitalize(src.file)>) '<}>* [Stackoverflow questions](https://stackoverflow.com/questions/tagged/rascal+<pcfg.packageName>) + '<if (pcfg.releaseNotes?) {>* [Release notes](../../Packages/<package(pcfg.packageName)>/RELEASE-NOTES.md)<}> '<if (pcfg.license?) {>* [Open-source license](../../Packages/<package(pcfg.packageName)>/License.md)<}> '<if (pcfg.citation?) {>* How to [cite this software](../../Packages/<package(pcfg.packageName)>/Citation.md)<}> - '<if (pcfg.funding?) {>* Follow the [money](../../Packages/<package(pcfg.packageName)>/Funding.md) sources.<}> - '<if (dependencies != []) {>* Check the [dependencies](../../Packages/<package(pcfg.packageName)>/Dependencies.md)<}> + '<if (pcfg.funding?) {>* [Funding sources](../../Packages/<package(pcfg.packageName)>/Funding.md) sources.<}> + '<if (dependencies != []) {>* [Dependencies](../../Packages/<package(pcfg.packageName)>/Dependencies.md)<}> '<if (pcfg.sources?) {>* [Source code](<"<pcfg.sources>"[1..-1]>)<}> '<if (pcfg.issues?) {>* [Issue tracker](<"<pcfg.issues>"[1..-1]>)<}> ' diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc index d7d7b9ffaa2..5f57c13efc5 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Names.rsc @@ -15,6 +15,7 @@ data PathConfig( loc license=|cwd:///LICENSE.md|, loc citation=|cwd:///CITATION.md|, loc funding=|cwd:///FUNDING.md|, + loc releaseNotes=|cwd:///RELEASE-NOTES.md|, str packageVersion=getRascalVersion(), bool isPackageCourse=false ); From 973ad6f9f7570e3ec25f6ea1f39e27398a8a1119 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Wed, 18 Oct 2023 17:14:50 +0200 Subject: [PATCH 100/120] fixed release notes feature --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index 9ce9ee20fd4..bc222b06619 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -196,7 +196,7 @@ void generatePackageIndex(PathConfig pcfg) { ); } - writeFile(targetFile.parent + "RELEASE-NOTES.md", + writeFile(targetFile.parent + "index.md", "--- 'title: <pcfg.packageName> '--- From 8b9fe632a65d4f9f793eacf548cd91532eb053aa Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 20 Oct 2023 15:05:02 +0200 Subject: [PATCH 101/120] longer waits maybe help with the screenshot feature --- .../lang/rascal/tutor/repl/TutorCommandExecutor.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 21518fe96e7..8a82619f081 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -210,11 +210,12 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce // wait for page to render completely WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15)); - wait.until(webDriver -> "complete".equals(((JavascriptExecutor) webDriver) - .executeScript("return document.readyState"))); + wait.until(webDriver -> "complete".equals( + ((JavascriptExecutor) webDriver).executeScript("return document.readyState") + )); - // take the screenshot - WebElement body = driver.findElement(By.tagName("body")); + // take the screenshot, but wait for the body to appear first. + WebElement body = wait.until(webDriver -> driver.findElement(By.tagName("body"))); String screenshot = body.getScreenshotAs(OutputType.BASE64); // store the screenshot as an output From 051bebedbab5cb3833ab80f6c1c7a25be2f4c2f8 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 30 Oct 2023 09:21:47 +0100 Subject: [PATCH 102/120] added a wait loop for screenshots, which stops only when the last two screenshots taken are exactly equal --- .../rascal/tutor/repl/TutorCommandExecutor.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 8a82619f081..81d17c1d35c 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -17,6 +17,7 @@ import java.util.Base64; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.openqa.selenium.By; import org.openqa.selenium.JavascriptExecutor; @@ -214,12 +215,22 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce ((JavascriptExecutor) webDriver).executeScript("return document.readyState") )); - // take the screenshot, but wait for the body to appear first. + // also wait for the body to appear completely. WebElement body = wait.until(webDriver -> driver.findElement(By.tagName("body"))); - String screenshot = body.getScreenshotAs(OutputType.BASE64); + + String previousScreenshot; + String currentscreenshot = ""; + int max = 20; + + // keep taking shots until all visual elements have stopped moving + do { + TimeUnit.SECONDS.sleep(1); + previousScreenshot = currentscreenshot; + currentscreenshot = body.getScreenshotAs(OutputType.BASE64); + } while (previousScreenshot.equals(currentscreenshot) && max-- > 0); // store the screenshot as an output - result.put("application/rascal+screenshot", screenshot); + result.put("application/rascal+screenshot", currentscreenshot); } catch (Throwable e) { shellErrorOutput.write(e.getMessage().getBytes("UTF-8")); From 1132e39f3cb1ed64e8db6a1b4aae17f400a542e3 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 30 Oct 2023 15:06:38 +0100 Subject: [PATCH 103/120] tutor will search for chromedriver, chrome/Google Chrome for Testing in the environment PATH if -D options are not provided --- .../tutor/repl/TutorCommandExecutor.java | 51 ++++++++++++++++--- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 81d17c1d35c..cff7a6b3cdf 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -102,14 +102,25 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O repl.initialize(shellInputNotUsed, shellStandardOutput, shellErrorOutput, services); repl.setMeasureCommandTime(false); - if (DRIVER_BINARY != null && BROWSER_BINARY != null) { + String driver = DRIVER_BINARY; + String browser = BROWSER_BINARY; + + if (driver == null) { + driver = inferChromeDriverBinaryLocation(); + } + + if (browser == null) { + browser = inferChromeBrowserBinaryLocation(); + } + + if (driver != null && browser != null) { this.service = new ChromeDriverService.Builder() - .usingDriverExecutable(new File(DRIVER_BINARY)) + .usingDriverExecutable(new File(driver)) .usingAnyFreePort() .build(); this.service.start(); - this.driver = getBrowser(service); + this.driver = getBrowser(service, browser); } else { this.service = null; @@ -117,6 +128,34 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O } } + private String inferChromeBrowserBinaryLocation() throws IOException { + ISourceLocation pathBrowser = URIUtil.correctLocation("PATH", "", "Google Chrome for Testing"); + + if (URIResolverRegistry.getInstance().exists(pathBrowser)) { + System.err.println("driver exists: " + pathBrowser); + return URIResolverRegistry.getInstance().logicalToPhysical(pathBrowser).getPath(); + } + + pathBrowser = URIUtil.correctLocation("PATH", "", "chrome"); + if (URIResolverRegistry.getInstance().exists(pathBrowser)) { + System.err.println("driver exists: " + pathBrowser); + return URIResolverRegistry.getInstance().logicalToPhysical(pathBrowser).getPath(); + } + + return null; + } + + private String inferChromeDriverBinaryLocation() throws IOException { + ISourceLocation pathDriver = URIUtil.correctLocation("PATH", "", "chromedriver"); + + if (URIResolverRegistry.getInstance().exists(pathDriver)) { + System.err.println("driver exists: " + pathDriver); + return URIResolverRegistry.getInstance().logicalToPhysical(pathDriver).getPath(); + } + + return null; + } + private static ISourceLocation inferProjectRoot(ISourceLocation member) { ISourceLocation current = member; URIResolverRegistry reg = URIResolverRegistry.getInstance(); @@ -244,11 +283,7 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce return result; } - private static RemoteWebDriver getBrowser(ChromeDriverService service) { - if (BROWSER_BINARY == null || DRIVER_BINARY == null) { - return null; - } - + private static RemoteWebDriver getBrowser(ChromeDriverService service, String BROWSER_BINARY) { ChromeOptions options = new ChromeOptions() .setHeadless(true) .setBinary(BROWSER_BINARY) From 8921e1a89360332e0632270e1b25a707c89bdbfc Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 30 Oct 2023 15:15:35 +0100 Subject: [PATCH 104/120] printed a help message for people wanting to take screenshots --- .../lang/rascal/tutor/repl/TutorCommandExecutor.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index cff7a6b3cdf..ed3f07db786 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -125,9 +125,18 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O else { this.service = null; this.driver = null; + printInfoMessage(); } } + private void printInfoMessage() { + System.err.println("INFO: tutor screenshot feature is currently disabled. To enable:"); + System.err.println("\t* add the folder holding `chromedriver` to your PATH;"); + System.err.println("\t* add the foldering holding `chrome` or `Google Chrome for Testing` to your PATH;"); + System.err.println("\t* or use: `-Dwebdriver.chrome.browser=/path/to/chrome -Dwebdriver.chrome.driver/path/to/chromedriver`"); + System.err.println("INFO: chrome and the chromedriver need to be aligned exactly. See https://googlechromelabs.github.io/chrome-for-testing/"); + } + private String inferChromeBrowserBinaryLocation() throws IOException { ISourceLocation pathBrowser = URIUtil.correctLocation("PATH", "", "Google Chrome for Testing"); From b518435930b5f30a1f6d71b49b046f1c7b218e9e Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 1 Feb 2024 13:39:17 +0100 Subject: [PATCH 105/120] fixed error --- src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc index bc222b06619..3131a42534f 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Compiler.rsc @@ -607,7 +607,7 @@ list[Output] compileMarkdown([/^<prefix:.*>~<digits:[^~]*[^aeh-pr-vx0-9]+[^~]*>~ *compileMarkdown(["<prefix><digits><postfix>", *rest], line, offset, pcfg, exec, ind, dtls, sidebar_position=sidebar_position) ]; -@synopsis{Resolve [labeled]((links))} +@synopsis{Resolve labeled links} list[Output] compileMarkdown([/^<prefix:.*>\[<title:[^\]]*>\]\(\(<link:[A-Za-z0-9\-\ \t\.\:]+>\)\)<postfix:.*>$/, *str rest], int line, int offset, PathConfig pcfg, CommandExecutor exec, Index ind, list[str] dtls, int sidebar_position=-1) { resolution = ind[removeSpaces(link)]; p2r = pathToRoot(pcfg.currentRoot, pcfg.currentFile, pcfg.isPackageCourse); From 43dab84aa747dff0cbd2bbaf4dd3e187aaed5161 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Fri, 10 May 2024 14:06:32 +0200 Subject: [PATCH 106/120] added clear deprecated message to every @deprecated definition --- .../lang/rascal/tutor/apidoc/ExtractInfo.rsc | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc index c44f22db261..6835b14b45d 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/apidoc/ExtractInfo.rsc @@ -171,7 +171,19 @@ str getSynopsis(rel[str, DocTag] tags) { } if (docTag(content=str docContents) <- tags["synopsis"]) { - return trim(intercalate(" ", split("\n", docContents))); + if (docTag(content=str deprMessage) <- tags["deprecated"]) { + return "<trim(intercalate(" ", split("\n", docContents)))> + ' + ':::warning + '**deprecated: marked for future deletion** + '<deprMessage> + '::: + '"; + } + else { + return trim(intercalate(" ", split("\n", docContents))); + } + } else { return ""; @@ -179,7 +191,7 @@ str getSynopsis(rel[str, DocTag] tags) { } -bool isTutorTag(str label) = label in {"doc", "synopsis", "syntax", "types", "details", "description", "examples", "benefits", "pitfalls"}; +bool isTutorTag(str label) = label in {"doc", "synopsis", "syntax", "types", "details", "description", "examples", "benefits", "pitfalls", "deprecated"}; @synopsis{extracts the contents of _all_ tags from a declaration syntax tree and stores origin information} rel[str, DocTag] getTagContents(Tags tags){ From c4bbae407ea3e88bc52cf7bd16965cb042dcadb0 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Mon, 13 May 2024 14:34:13 +0200 Subject: [PATCH 107/120] fixed compilation problems due to evolved API around IRascalMonitor --- .../tutor/lang/rascal/tutor/questions/QuestionCompiler.java | 3 ++- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java | 2 +- .../tutor/lang/rascal/tutor/repl/TutorIDEServices.java | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java index 07793da3f33..3e1701ade87 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/questions/QuestionCompiler.java @@ -14,6 +14,7 @@ import java.io.File; +import org.rascalmpl.interpreter.ConsoleRascalMonitor; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.env.GlobalEnvironment; import org.rascalmpl.interpreter.env.ModuleEnvironment; @@ -35,7 +36,7 @@ public class QuestionCompiler { public QuestionCompiler(PathConfig pcfg) { final GlobalEnvironment heap = new GlobalEnvironment(); final ModuleEnvironment top = new ModuleEnvironment("***question compiler***", heap); - eval = new Evaluator(vf, System.in, System.err, System.out, top, heap); + eval = new Evaluator(vf, System.in, System.err, System.out, top, heap, new ConsoleRascalMonitor()); eval.addRascalSearchPath(URIUtil.rootLocation("std")); eval.getConfiguration().setRascalJavaClassPathProperty(javaCompilerPathAsString(pcfg.getJavaCompilerPath())); diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index ed3f07db786..1bcdacb68b3 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -70,7 +70,7 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O GlobalEnvironment heap = new GlobalEnvironment(); ModuleEnvironment root = heap.addModule(new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap)); IValueFactory vf = ValueFactoryFactory.getValueFactory(); - Evaluator eval = new Evaluator(vf, input, stderr, stdout, root, heap); + Evaluator eval = new Evaluator(vf, input, stderr, stdout, root, heap, services); eval.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); eval.setMonitor(services); diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java index d1cd70fe586..6159f672ae8 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java @@ -53,4 +53,9 @@ public void browse(URI uri, String title, int column) { public void edit(ISourceLocation path) { } + + @Override + public void endAllJobs() { + + } } From 5fc948a996dbf74b7c13a37d88ea6eb73fcab74b Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Thu, 13 Jun 2024 09:22:32 +0200 Subject: [PATCH 108/120] enabled progress reports during indexing --- .../tutor/lang/rascal/tutor/Indexer.rsc | 58 +++++++++++++++---- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc index 975dc212699..ec44be94c4b 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/Indexer.rsc @@ -4,6 +4,7 @@ import util::Reflective; import ValueIO; import String; import util::FileSystem; +import util::Monitor; import IO; import ValueIO; import Location; @@ -45,12 +46,34 @@ rel[str, str] createConceptIndex(list[loc] srcs, datetime lastModified, bool isP = {*createConceptIndex(src, lastModified, isPackageCourse, packageName) | src <- srcs, bprintln("Indexing <src>")}; @synopsis{creates a lookup table for concepts nested in a folder} -rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageCourse, str packageName) - = // first we collect index entries for concept names, each file is one concept which - // can be linked to in many different ways ranging from very short (handy but inexact) to very long (guaranteed to be exact.) +rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageCourse, str packageName) { + bool step(str label, loc file) { + jobStep(label, "<file.file>"); + return true; + } + + void \start(str label, int work) { + if (work > 0) { + jobStart(label, totalWork=work); + } + } + + // first we collect index entries for concept names, each file is one concept which + // can be linked to in many different ways ranging from very short (handy but inexact) to very long (guaranteed to be exact.) + conceptFiles = find(src, isFreshConceptFile(lastModified)); + \start("Indexing concepts", 2*size(conceptFiles)); + + imageFiles = find(src, isImageFile); + \start("Indexing images", size(imageFiles)); + + directoryIndexes = find(src, isDirectory); + \start("Indexing directories", 2*size(directoryIndexes)); + + rascalFiles = find(src, isFreshRascalFile(lastModified)); + \start("Indexing modules", size(rascalFiles)); // First we handle the root concept - { + result = { <RootName, "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<RootName>/index.md">, <"course:<RootName>", "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<RootName>/index.md"> | str RootName := ((isPackageCourse && src.file in {"src","rascal","api"}) ? "API" : package(src.file)) @@ -76,7 +99,8 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC // `((Rascal:Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` <"<capitalize(src.file)>:<replaceAll(capitalize(relativize(src, f.parent).path)[1..], "/", "-")>", fr> - | loc f <- find(src, isFreshConceptFile(lastModified)) + | loc f <- conceptFiles + , step("Indexing concepts", f) , f.parent? , f.parent.path != "/" , f.parent != src @@ -105,7 +129,8 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC // `((Rascal:Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` <"<capitalize(src.file)>:<replaceAll(capitalize(relativize(src, cf).path)[1..], "/", "-")>", fr> - | loc f <- find(src, isFreshConceptFile(lastModified)) + | loc f <- conceptFiles + , step("Indexing concepts", f) , f.parent? , f.parent.path != "/" , f.parent != src @@ -134,7 +159,8 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC // `((Rascal:Expressions-Values-Set-StrictSuperSet)) -> /Rascal/Expressions/Values/Set/StrictSuperSet/index.md` <"<capitalize(src.file)>:<replaceAll(capitalize(relativize(src, f).path)[1..], "/", "-")>", fr> - | loc f <- find(src, isDirectory) + | loc f <- directoryIndexes + , step("Indexing directories", f) , fr := "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<fragment(src, f)>" , f != src } @@ -143,7 +169,7 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC { <"<f.parent.file>-<f.file>", fr>, <f.file, fr>, <"<capitalize(src.file)>:<f.file>", fr> - | loc f <- find(src, isImageFile), + | loc f <- imageFiles, step("Indexing images", f), fr := "/assets/<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}><relativize(src, f).path>" } + { // these are links to packages/folders/directories via module path prefixes, like `analysis::m3` @@ -153,7 +179,8 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC <"<capitalize(src.file)>:<capitalize(replaceAll(relativize(src, f).path[1..], "/", "-"))>", fr>, <"<capitalize(src.file)>:package:<replaceAll(relativize(src, f).path[1..], "/", "::")>", fr>, <"<capitalize(src.file)>:<capitalize(replaceAll(relativize(src, f).path[1..], "/", "::"))>", fr> - | loc f <- find(src, isDirectory) + | loc f <- directoryIndexes + , step("Indexing directories", f) , /\/internal\// !:= f.path , f != src , fr := "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<fragment(src, f)>" @@ -185,10 +212,17 @@ rel[str, str] createConceptIndex(loc src, datetime lastModified, bool isPackageC *{<"<capitalize(src.file)>:<item.moduleName>", "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" >, <"<capitalize(src.file)>:module:<item.moduleName>", "<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>/API<} else {>/<capitalize(src.file)><}>/<modulePath(item.moduleName)>.md" > | item is moduleInfo} - | loc f <- find(src, isFreshRascalFile(lastModified)), list[DeclarationInfo] inf := safeExtract(f), item <- inf, + | loc f <- rascalFiles, step("Indexing modules", f), list[DeclarationInfo] inf := safeExtract(f), item <- inf, fr := "/<if (isPackageCourse) {>/Packages/<package(packageName)><}>/<if (isPackageCourse && src.file in {"src","rascal","api"}) {>API<} else {><capitalize(src.file)><}>/<modulePath(item.moduleName)>.md<moduleFragment(item.moduleName)>-<item.name>" - } - ; + }; + + jobEnd("Indexing modules"); + jobEnd("Indexing directories"); + jobEnd("Indexing concepts"); + jobEnd("Indexing images"); + + return result; +} private bool isConceptFile(loc f) = f.extension in {"md"}; From 830bac828d1c4bc8e3bb8f8b3bb24f66db404beb Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 16 Jul 2024 18:37:41 +0200 Subject: [PATCH 109/120] check tutor code as well --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index cdc0b1744ef..68c99c6b1eb 100644 --- a/pom.xml +++ b/pom.xml @@ -122,6 +122,7 @@ <bin>${project.build.outputDirectory}</bin> <srcs> <src>${project.basedir}/src/org/rascalmpl/library</src> + <src>${project.basedir}/src/org/rascalmpl/tutor</src> </srcs> <sourceLookup>|std:///|</sourceLookup> <funding>${project.basedir}/FUNDING</funding> From b5f4eecd2e34d1b10e7f06c48cfd872be59f19f8 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 16 Jul 2024 18:47:51 +0200 Subject: [PATCH 110/120] package rename --- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java | 2 +- .../lang/rascal/tutor/repl/TutorCommandExecutorCreator.java | 2 +- .../tutor/lang/rascal/tutor/repl/TutorIDEServices.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 1bcdacb68b3..1a26fc6fec6 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -1,4 +1,4 @@ -package lang.rascal.tutor.repl; +package org.rascalmpl.tutor.lang.rascal.tutor.repl; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutorCreator.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutorCreator.java index 1898a68e525..da4b336bb16 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutorCreator.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutorCreator.java @@ -10,7 +10,7 @@ * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package lang.rascal.tutor.repl; +package org.rascalmpl.tutor.lang.rascal.tutor.repl; import java.io.IOException; import java.net.URISyntaxException; diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java index 6159f672ae8..b5917a1c38e 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorIDEServices.java @@ -1,4 +1,4 @@ -package lang.rascal.tutor.repl; +package org.rascalmpl.tutor.lang.rascal.tutor.repl; import java.io.PrintWriter; import java.net.URI; From d714323ee8ae6c470e3d3053193ab47bc11f1335 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 16 Jul 2024 19:12:21 +0200 Subject: [PATCH 111/120] screenshot feature is not injected dynamically --- .../tutor/repl/ITutorScreenshotFeature.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/org/rascalmpl/tutor/lang/rascal/tutor/repl/ITutorScreenshotFeature.java diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/ITutorScreenshotFeature.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/ITutorScreenshotFeature.java new file mode 100644 index 00000000000..eea9729254d --- /dev/null +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/ITutorScreenshotFeature.java @@ -0,0 +1,30 @@ +package org.rascalmpl.tutor.lang.rascal.tutor.repl; + +/** + * A interface to be implemented by a depending project. The + * screenshot feature is injected into the tutor command executor + * by dynamic loading of the class mentioned in this resource: + * org/rascalmpl/tutor/screenshotter.config + * + * The goal is to not have dependencies on large projects + * like selenium and Chrome in the core of the rascal project. + * + * The tutor will work fine without a screenshotter, except that + * screenshots will not be included with the documentation. It is + * advisable to run the tutor using the rascal-maven-plugin, which + * makes sure that the screenshot feature is properly injected. + */ +public interface ITutorScreenshotFeature { + + /** + * The URL string is expected to be syntactically correct. + * Typically it is localhost:<port>, to point at the right + * server that is currently visualizizing something from the + * REPL or the IDEServices. + * + * @param url localhost url + * @return a base64 encoded PNG snapshot. + */ + String takeScreenshotAsBase64PNG(String url); + +} From 43803298a4f76a6e2c876165404df645907a0159 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 16 Jul 2024 19:13:33 +0200 Subject: [PATCH 112/120] removed unused code --- .../tutor/repl/TutorCommandExecutor.java | 155 +++--------------- 1 file changed, 26 insertions(+), 129 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 1a26fc6fec6..4410e27426c 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -9,25 +9,16 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; import java.net.URISyntaxException; +import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; -import java.time.Duration; import java.util.Arrays; import java.util.Base64; +import java.util.Enumeration; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.TimeUnit; - -import org.openqa.selenium.By; -import org.openqa.selenium.JavascriptExecutor; -import org.openqa.selenium.OutputType; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.chrome.ChromeDriverLogLevel; -import org.openqa.selenium.chrome.ChromeDriverService; -import org.openqa.selenium.chrome.ChromeOptions; -import org.openqa.selenium.remote.RemoteWebDriver; -import org.openqa.selenium.support.ui.WebDriverWait; import org.rascalmpl.ideservices.IDEServices; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.env.GlobalEnvironment; @@ -51,14 +42,11 @@ import io.usethesource.vallang.IValueFactory; public class TutorCommandExecutor { - private static final String BROWSER_BINARY = System.getProperty("webdriver.chrome.browser"); - private static final String DRIVER_BINARY = System.getProperty("webdriver.chrome.driver"); + private static final String SCREENSHOTTER_CONFIG = "org/rascalmpl/tutor/screenshotter.config"; private final RascalInterpreterREPL repl; private final ByteArrayOutputStream shellStandardOutput; private final ByteArrayOutputStream shellErrorOutput; - private final ChromeDriverService service; - private final RemoteWebDriver driver; - + private final ITutorScreenshotFeature screenshot; public TutorCommandExecutor(PathConfig pcfg) throws IOException, URISyntaxException{ shellStandardOutput = new ByteArrayOutputStream(); @@ -102,67 +90,23 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O repl.initialize(shellInputNotUsed, shellStandardOutput, shellErrorOutput, services); repl.setMeasureCommandTime(false); - String driver = DRIVER_BINARY; - String browser = BROWSER_BINARY; - - if (driver == null) { - driver = inferChromeDriverBinaryLocation(); - } - - if (browser == null) { - browser = inferChromeBrowserBinaryLocation(); - } - - if (driver != null && browser != null) { - this.service = new ChromeDriverService.Builder() - .usingDriverExecutable(new File(driver)) - .usingAnyFreePort() - .build(); - - this.service.start(); - this.driver = getBrowser(service, browser); - } - else { - this.service = null; - this.driver = null; - printInfoMessage(); - } - } - - private void printInfoMessage() { - System.err.println("INFO: tutor screenshot feature is currently disabled. To enable:"); - System.err.println("\t* add the folder holding `chromedriver` to your PATH;"); - System.err.println("\t* add the foldering holding `chrome` or `Google Chrome for Testing` to your PATH;"); - System.err.println("\t* or use: `-Dwebdriver.chrome.browser=/path/to/chrome -Dwebdriver.chrome.driver/path/to/chromedriver`"); - System.err.println("INFO: chrome and the chromedriver need to be aligned exactly. See https://googlechromelabs.github.io/chrome-for-testing/"); - } - - private String inferChromeBrowserBinaryLocation() throws IOException { - ISourceLocation pathBrowser = URIUtil.correctLocation("PATH", "", "Google Chrome for Testing"); - - if (URIResolverRegistry.getInstance().exists(pathBrowser)) { - System.err.println("driver exists: " + pathBrowser); - return URIResolverRegistry.getInstance().logicalToPhysical(pathBrowser).getPath(); - } - - pathBrowser = URIUtil.correctLocation("PATH", "", "chrome"); - if (URIResolverRegistry.getInstance().exists(pathBrowser)) { - System.err.println("driver exists: " + pathBrowser); - return URIResolverRegistry.getInstance().logicalToPhysical(pathBrowser).getPath(); - } - - return null; + this.screenshot = loadScreenShotter(); } - private String inferChromeDriverBinaryLocation() throws IOException { - ISourceLocation pathDriver = URIUtil.correctLocation("PATH", "", "chromedriver"); - - if (URIResolverRegistry.getInstance().exists(pathDriver)) { - System.err.println("driver exists: " + pathDriver); - return URIResolverRegistry.getInstance().logicalToPhysical(pathDriver).getPath(); - } - - return null; + private ITutorScreenshotFeature loadScreenShotter() { + try { + Enumeration<URL> resources = getClass().getClassLoader().getResources(SCREENSHOTTER_CONFIG); + + if (!resources.hasMoreElements()) { + return null; + } + + var content = new String(Prelude.consumeInputStream(resources.nextElement().openStream()), "UTF8"); + return (ITutorScreenshotFeature) getClass().getClassLoader().loadClass(content.trim()).getDeclaredConstructor().newInstance(); + } + catch (IOException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | ClassNotFoundException e) { + throw new Error("WARNING: Could not load screenshot feature from " + SCREENSHOTTER_CONFIG, e); + } } private static ISourceLocation inferProjectRoot(ISourceLocation member) { @@ -183,16 +127,6 @@ private static ISourceLocation inferProjectRoot(ISourceLocation member) { return current; } - - @Override - protected void finalize() throws Throwable { - if (driver != null) { - driver.quit(); - } - if (service != null) { - service.stop(); - } - } private String javaCompilerPathAsString(IList javaCompilerPath) { StringBuilder b = new StringBuilder(); @@ -251,34 +185,15 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce result.put(mimeType, uuencode(content)); } - if (metadata.get("url") != null && driver != null) { + if (metadata.get("url") != null && screenshot != null) { try { // load the page - driver.get(metadata.get("url")); - driver.manage().window().maximize(); - - // wait for page to render completely - WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15)); - wait.until(webDriver -> "complete".equals( - ((JavascriptExecutor) webDriver).executeScript("return document.readyState") - )); - - // also wait for the body to appear completely. - WebElement body = wait.until(webDriver -> driver.findElement(By.tagName("body"))); - - String previousScreenshot; - String currentscreenshot = ""; - int max = 20; + String pngImage = screenshot.takeScreenshotAsBase64PNG(metadata.get("url")); - // keep taking shots until all visual elements have stopped moving - do { - TimeUnit.SECONDS.sleep(1); - previousScreenshot = currentscreenshot; - currentscreenshot = body.getScreenshotAs(OutputType.BASE64); - } while (previousScreenshot.equals(currentscreenshot) && max-- > 0); - - // store the screenshot as an output - result.put("application/rascal+screenshot", currentscreenshot); + if (!pngImage.isEmpty()) { + result.put("application/rascal+screenshot", pngImage); + } + } catch (Throwable e) { shellErrorOutput.write(e.getMessage().getBytes("UTF-8")); @@ -292,24 +207,6 @@ public Map<String, String> eval(String line) throws InterruptedException, IOExce return result; } - private static RemoteWebDriver getBrowser(ChromeDriverService service, String BROWSER_BINARY) { - ChromeOptions options = new ChromeOptions() - .setHeadless(true) - .setBinary(BROWSER_BINARY) - .addArguments("--headless", "--disable-gpu", "--window-size=1900,1200","--ignore-certificate-errors","--disable-extensions","--no-sandbox","--disable-dev-shm-usage") - .addArguments("--user-data-dir=/tmp/rascal-config/google-chrome") - .setLogLevel(ChromeDriverLogLevel.OFF) - ; - - RemoteWebDriver driver = new RemoteWebDriver(service.getUrl(), options); - driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(30)); - driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(30)); - driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(60)); - driver.manage().window().maximize(); - - return driver; - } - public String uuencode(InputStream content) throws IOException { int BUFFER_SIZE = 3 * 512; Base64.Encoder encoder = Base64.getEncoder(); From 3e8464926b85b7c252f40087ee816f19edb42b99 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 16 Jul 2024 20:13:53 +0200 Subject: [PATCH 113/120] added tutor to Source folders --- META-INF/RASCAL.MF | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/META-INF/RASCAL.MF b/META-INF/RASCAL.MF index d3977080a63..a92afc8e206 100644 --- a/META-INF/RASCAL.MF +++ b/META-INF/RASCAL.MF @@ -1,5 +1,5 @@ Project-Name: rascal -Source: src/org/rascalmpl/library,test/org/rascalmpl/benchmark,test//org/rascalmpl/test/data +Source: src/org/rascalmpl/library,src/org/rascalmpl/tutor,test/org/rascalmpl/benchmark,test/org/rascalmpl/test/data Courses: src/org/rascalmpl/courses From c22ef4e1bdaa1e554414522f152cacc4a8f5b58d Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 16 Jul 2024 20:16:51 +0200 Subject: [PATCH 114/120] fixed javaClass reference --- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc index fab68a503e7..4d9e4c3fcc3 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc @@ -36,7 +36,7 @@ e.prompt() // finish the command we started e.eval(")") } -@javaClass{lang.rascal.tutor.repl.TutorCommandExecutorCreator} +@javaClass{org.rascalmpl.tutor.lang.rascal.tutor.repl.TutorCommandExecutorCreator} java CommandExecutor createExecutor(PathConfig pcfg); test bool executorSmokeTest() { From a1aa469afc023a7dae50362a98f780f5743d3b91 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 16 Jul 2024 20:30:48 +0200 Subject: [PATCH 115/120] screenshotter can throw IOExceptions --- .../lang/rascal/tutor/repl/ITutorScreenshotFeature.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/ITutorScreenshotFeature.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/ITutorScreenshotFeature.java index eea9729254d..db17bbb6efd 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/ITutorScreenshotFeature.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/ITutorScreenshotFeature.java @@ -1,5 +1,7 @@ package org.rascalmpl.tutor.lang.rascal.tutor.repl; +import java.io.IOException; + /** * A interface to be implemented by a depending project. The * screenshot feature is injected into the tutor command executor @@ -24,7 +26,8 @@ public interface ITutorScreenshotFeature { * * @param url localhost url * @return a base64 encoded PNG snapshot. + * @throws IOExceptions when unexpected things happen */ - String takeScreenshotAsBase64PNG(String url); + String takeScreenshotAsBase64PNG(String url) throws IOException; } From b5579e64e55dd4470d37bd0b4643a9ef6ae8b1f7 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 16 Jul 2024 21:15:20 +0200 Subject: [PATCH 116/120] simplified extension --- .../tutor/repl/TutorCommandExecutor.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 4410e27426c..45785dff381 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -95,16 +95,17 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O private ITutorScreenshotFeature loadScreenShotter() { try { - Enumeration<URL> resources = getClass().getClassLoader().getResources(SCREENSHOTTER_CONFIG); - - if (!resources.hasMoreElements()) { - return null; - } - - var content = new String(Prelude.consumeInputStream(resources.nextElement().openStream()), "UTF8"); - return (ITutorScreenshotFeature) getClass().getClassLoader().loadClass(content.trim()).getDeclaredConstructor().newInstance(); + return (ITutorScreenshotFeature) getClass() + .getClassLoader() + .loadClass("org.rascalmpl.tutor.Screenshotter") + .getDeclaredConstructor() + .newInstance(); } - catch (IOException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | ClassNotFoundException e) { + catch (ClassNotFoundException e) { + // that is normal; we just don't have the feature available. + return null; + } + catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { throw new Error("WARNING: Could not load screenshot feature from " + SCREENSHOTTER_CONFIG, e); } } From 29ecff60cd6219af4af9069ebc1485ce152cf706 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 16 Jul 2024 21:16:14 +0200 Subject: [PATCH 117/120] simplified --- .../tutor/lang/rascal/tutor/repl/ITutorScreenshotFeature.java | 3 +-- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/ITutorScreenshotFeature.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/ITutorScreenshotFeature.java index db17bbb6efd..02491cb229b 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/ITutorScreenshotFeature.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/ITutorScreenshotFeature.java @@ -5,8 +5,7 @@ /** * A interface to be implemented by a depending project. The * screenshot feature is injected into the tutor command executor - * by dynamic loading of the class mentioned in this resource: - * org/rascalmpl/tutor/screenshotter.config + * by dynamic loading this class: org.rascalmpl.tutor.Screenshotter * * The goal is to not have dependencies on large projects * like selenium and Chrome in the core of the rascal project. diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 45785dff381..1e885c069a4 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -42,7 +42,6 @@ import io.usethesource.vallang.IValueFactory; public class TutorCommandExecutor { - private static final String SCREENSHOTTER_CONFIG = "org/rascalmpl/tutor/screenshotter.config"; private final RascalInterpreterREPL repl; private final ByteArrayOutputStream shellStandardOutput; private final ByteArrayOutputStream shellErrorOutput; From 279b995ee1e7f4324de6bb3572b39a375e5d703b Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 16 Jul 2024 21:19:12 +0200 Subject: [PATCH 118/120] fixed compilation error --- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 1e885c069a4..23964c50557 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -105,7 +105,7 @@ private ITutorScreenshotFeature loadScreenShotter() { return null; } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { - throw new Error("WARNING: Could not load screenshot feature from " + SCREENSHOTTER_CONFIG, e); + throw new Error("WARNING: Could not load screenshot feature from org.rascalmpl.tutor.Screenshotter", e); } } From e0d34eb1a8470efe404997bf42496fdeb5c622b4 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 16 Jul 2024 21:30:47 +0200 Subject: [PATCH 119/120] cleanup --- .../tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java index 23964c50557..7237e684785 100644 --- a/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java +++ b/src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.java @@ -11,12 +11,10 @@ import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.net.URISyntaxException; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.Arrays; import java.util.Base64; -import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import org.rascalmpl.ideservices.IDEServices; From 224dcb94a3c753b329e8c6b7829ccef1da76f3b6 Mon Sep 17 00:00:00 2001 From: "Jurgen J. Vinju" <Jurgen.Vinju@cwi.nl> Date: Tue, 16 Jul 2024 21:35:38 +0200 Subject: [PATCH 120/120] fixed HEAD tag --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 68c99c6b1eb..43105b8bdc7 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ <scm> <developerConnection>scm:git:ssh://git@github.com/usethesource/rascal.git</developerConnection> - <tag>v0.40.0-BOOT1</tag> + <tag>HEAD</tag> </scm> <!-- dependency resolution configuration (usethesource) -->