diff --git a/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala b/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala index 60b605af0..47dac5295 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala @@ -109,21 +109,21 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: val (reply, stderr) = host.query(queryStr, !expectRuntimeOrCodeGenErrors && fixme.isUnset && todo.isUnset) reply match case ReplHost.Result(content, stdout) => - if silent.isUnset then - stdout match - case None | Some("") => - case Some(str) => - str.splitSane('\n').foreach: line => - output(s"> ${line}") - content match - case "undefined" => - case "null" => + stdout match + case None | Some("") => + case Some(str) => + str.splitSane('\n').foreach: line => + output(s"> ${line}") + content match + case "undefined" => + case "null" => + case _ => + expect.get match + case S(expected) if content != expected => raise: + ErrorReport(msg"Expected: ${expected}, got: ${content}" -> N :: Nil, + source = Diagnostic.Source.Runtime) case _ => - expect.get match - case S(expected) if content != expected => raise: - ErrorReport(msg"Expected: ${expected}, got: ${content}" -> N :: Nil, - source = Diagnostic.Source.Runtime) - case _ => output(s"$prefix= ${content}") + if silent.isUnset then output(s"$prefix= ${content}") case ReplHost.Empty => case ReplHost.Unexecuted(message) => ??? case ReplHost.Error(isSyntaxError, message, otherOutputs) => @@ -162,7 +162,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker: if ts.trmImplTree.exists(_.k.isInstanceOf[syntax.ValLike]) => S((nme, ts)) case _ => N case _ => N - definedValues.toSeq.sortBy(_._1).foreach: (nme, sym) => + if silent.isUnset then definedValues.toSeq.sortBy(_._1).foreach: (nme, sym) => val le = codegen.Return(codegen.Value.Ref(sym), implct = true) val je = nestedScp.givenIn: jsb.block(le) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 72539f36c..2177fc78e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -22,7 +22,7 @@ object Elaborator: ",", "+", "-", "*", "/", "%", "==", "!=", "<", "<=", ">", ">=", - "===", + "===", "!==", "&&", "||") private val unaryOps = Set("-", "+", "!", "~") private val anyOps = Set("super") diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala index 7d28a9e7e..cbb73aa0c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala @@ -286,7 +286,11 @@ class ParseRules(using State): case (body, _) => Open(body)}*), modified(`abstract`, Kw(`class`)(typeDeclBody(Cls))), modified(`mut`), - modified(`do`), + Kw(`do`): + ParseRule(s"`do` keyword")( + exprOrBlk(ParseRule(s"`do` body")(End(()))): + case (body, ()) => Tree.Modified(`do`, N, body) + *), modified(`virtual`), modified(`override`), modified(`declare`), diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index 02bf86ff7..78bbc5e1c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -580,7 +580,8 @@ abstract class Parser( case _ => S(expr(prec)) Spread(if dotDotDot then Keyword.`...` else Keyword.`..`, S(loc), bod) case (tok, loc) :: _ => - TODO(tok) + err((msg"Expected an expression; found new line instead" -> S(loc) :: Nil)) + errExpr case Nil => err((msg"Expected an expression; found end of input instead" -> lastLoc :: Nil)) errExpr diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 9804ddb34..0b81c7e0a 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -110,18 +110,33 @@ const Predef$class = class Predef { return f5.call(receiver1, ...args); }; } - print(x4) { + pass1(f6) { + return (...xs) => { + return f6(xs[0]) ?? null; + }; + } + pass2(f7) { + return (...xs) => { + return f7(xs[0], xs[1]) ?? null; + }; + } + pass3(f8) { + return (...xs) => { + return f8(xs[0], xs[1], xs[2]) ?? null; + }; + } + print(...xs) { let tmp; - tmp = String(x4); - return console.log(tmp) ?? null; + tmp = xs.map(String) ?? null; + return console.log(...tmp) ?? null; } - tupleSlice(xs, i, j) { + tupleSlice(xs1, i, j) { let tmp; - tmp = xs.length - j; - return globalThis.Array.prototype.slice.call(xs, i, tmp) ?? null; + tmp = xs1.length - j; + return globalThis.Array.prototype.slice.call(xs1, i, tmp) ?? null; } - tupleGet(xs1, i1) { - return globalThis.Array.prototype.at.call(xs1, i1); + tupleGet(xs2, i1) { + return globalThis.Array.prototype.at.call(xs2, i1); } stringStartsWith(string, prefix) { return string.startsWith(prefix) ?? null; diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index ef863de44..59eac3fbd 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -15,8 +15,12 @@ fun (.) passTo(receiver, f)(...args) = f(receiver, ...args) fun (|>.) call(receiver, f)(...args) = f.call(receiver, ...args) -fun print(x) = - console.log(String(x)) +fun pass1(f)(...xs) = f(xs.0) +fun pass2(f)(...xs) = f(xs.0, xs.1) +fun pass3(f)(...xs) = f(xs.0, xs.1, xs.2) + +fun print(...xs) = + console.log(...xs.map(String)) val assert = console.assert diff --git a/hkmc2/shared/src/test/mlscript-compile/Str.mjs b/hkmc2/shared/src/test/mlscript-compile/Str.mjs index dff6c0547..cf22ef318 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Str.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Str.mjs @@ -1,9 +1,12 @@ const Str$class = class Str { constructor() {} - concat(a, b) { + concat2(a, b) { return a + b; } - string(value) { + concat(...xs) { + return xs.join("") ?? null; + } + from(value) { return globalThis.String(value) ?? null; } toString() { return "Str"; } diff --git a/hkmc2/shared/src/test/mlscript-compile/Str.mls b/hkmc2/shared/src/test/mlscript-compile/Str.mls index 40a5b72cd..ae09f4657 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Str.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Str.mls @@ -1,6 +1,10 @@ module Str with ... -fun (~) concat(a, b) = a + b -fun string(value) = globalThis.String(value) +fun (~) concat2(a, b) = a + b + +fun concat(...xs) = xs.join("") + +fun from(value) = globalThis.String(value) + diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs index 38945b0eb..b6d459dbf 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs @@ -39,11 +39,11 @@ class Accounting { let scrut, tmp, tmp1, tmp2, tmp3, tmp4; scrut = this.balance > 10000; if (scrut) { - tmp = Str.concat("> **\u2757\uFE0F** Unspent balance of ", this.name); - tmp1 = Str.concat(tmp, ": `"); + tmp = Str.concat2("> **\u2757\uFE0F** Unspent balance of ", this.name); + tmp1 = Str.concat2(tmp, ": `"); tmp2 = this$Accounting.display(this.balance); - tmp3 = Str.concat(tmp1, tmp2); - tmp4 = Str.concat(tmp3, "`"); + tmp3 = Str.concat2(tmp1, tmp2); + tmp4 = Str.concat2(tmp3, "`"); return this$Accounting.warnings.push(tmp4) ?? null; } else { return null; @@ -64,42 +64,42 @@ class Accounting { } wln(txt1) { let tmp; - tmp = Str.concat(txt1, "\n"); + tmp = Str.concat2(txt1, "\n"); return fs.appendFileSync(this.fileName, tmp); } init() { let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13; tmp = this.wln(""); - tmp1 = Str.concat("|", "Year"); - tmp2 = Str.concat(tmp1, "|"); + tmp1 = Str.concat2("|", "Year"); + tmp2 = Str.concat2(tmp1, "|"); tmp3 = this$Accounting.lines.map((x) => { return x.name; }) ?? null; tmp4 = tmp3.join("|") ?? null; - tmp5 = Str.concat(tmp2, tmp4); - tmp6 = Str.concat(tmp5, "|"); + tmp5 = Str.concat2(tmp2, tmp4); + tmp6 = Str.concat2(tmp5, "|"); tmp7 = this.wln(tmp6); - tmp8 = Str.concat("|", "---"); - tmp9 = Str.concat(tmp8, "|"); + tmp8 = Str.concat2("|", "---"); + tmp9 = Str.concat2(tmp8, "|"); tmp10 = this$Accounting.lines.map((x) => { return "--:"; }) ?? null; tmp11 = tmp10.join("|") ?? null; - tmp12 = Str.concat(tmp9, tmp11); - tmp13 = Str.concat(tmp12, "|"); + tmp12 = Str.concat2(tmp9, tmp11); + tmp13 = Str.concat2(tmp12, "|"); return this.wln(tmp13); } snapShot(label) { let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; tmp = String(label) ?? null; - tmp1 = Str.concat("|", tmp); - tmp2 = Str.concat(tmp1, "|"); + tmp1 = Str.concat2("|", tmp); + tmp2 = Str.concat2(tmp1, "|"); tmp3 = this$Accounting.lines.map((x) => { return this$Accounting.display(x.balance); }) ?? null; tmp4 = tmp3.join("|") ?? null; - tmp5 = Str.concat(tmp2, tmp4); - tmp6 = Str.concat(tmp5, "|"); + tmp5 = Str.concat2(tmp2, tmp4); + tmp6 = Str.concat2(tmp5, "|"); return this.wln(tmp6); } wrapUp() { @@ -112,14 +112,14 @@ class Accounting { }) ?? null; tmp2 = this.wln("### Remaining Available Funds"); tmp3 = this.wln(""); - tmp4 = Str.concat("|", "Summary"); - tmp5 = Str.concat(tmp4, "| |"); + tmp4 = Str.concat2("|", "Summary"); + tmp5 = Str.concat2(tmp4, "| |"); tmp6 = this.wln(tmp5); - tmp7 = Str.concat("|", "---"); - tmp8 = Str.concat(tmp7, "|--:|"); + tmp7 = Str.concat2("|", "---"); + tmp8 = Str.concat2(tmp7, "|--:|"); tmp9 = this.wln(tmp8); - tmp10 = Str.concat("|", "Matchable"); - tmp11 = Str.concat(tmp10, "|"); + tmp10 = Str.concat2("|", "Matchable"); + tmp11 = Str.concat2(tmp10, "|"); tmp12 = this$Accounting.lines.filter((x) => { return x.isMatchable; }) ?? null; @@ -130,11 +130,11 @@ class Accounting { return a + b; }, 0); tmp15 = this$Accounting.display(tmp14); - tmp16 = Str.concat(tmp11, tmp15); - tmp17 = Str.concat(tmp16, "|"); + tmp16 = Str.concat2(tmp11, tmp15); + tmp17 = Str.concat2(tmp16, "|"); tmp18 = this.wln(tmp17); - tmp19 = Str.concat("|", "Non-matchable"); - tmp20 = Str.concat(tmp19, "|"); + tmp19 = Str.concat2("|", "Non-matchable"); + tmp20 = Str.concat2(tmp19, "|"); tmp21 = this$Accounting.lines.filter((x) => { return Predef.not(x.isMatchable); }) ?? null; @@ -145,8 +145,8 @@ class Accounting { return a + b; }, 0); tmp24 = this$Accounting.display(tmp23); - tmp25 = Str.concat(tmp20, tmp24); - tmp26 = Str.concat(tmp25, "|"); + tmp25 = Str.concat2(tmp20, tmp24); + tmp26 = Str.concat2(tmp25, "|"); return this.wln(tmp26); } toString() { return "Report(" + this.fileName + ")"; } @@ -171,7 +171,7 @@ class Accounting { tmp1 = report.init() ?? null; tmp2 = k(report) ?? null; tmp3 = report.wrapUp() ?? null; - tmp4 = Str.concat("Report written to ", filename); + tmp4 = Str.concat2("Report written to ", filename); return Predef.print(tmp4); } toString() { return "Accounting"; } diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mjs b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mjs new file mode 100644 index 000000000..e8063c6a6 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mjs @@ -0,0 +1,58 @@ +import Str from "./../Str.mjs"; +import Predef from "./../Predef.mjs"; +function CSV(strDelimiter1) { return new CSV.class(strDelimiter1); } +CSV.class = class CSV { + constructor(strDelimiter) { + this.strDelimiter = strDelimiter; + let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + tmp = this.strDelimiter || ","; + this.strDelimiter = tmp; + tmp1 = "(\\" + this.strDelimiter; + tmp2 = tmp1 + "|\\r?\\n|\\r|^)"; + tmp3 = "([^\"\\" + this.strDelimiter; + tmp4 = tmp3 + "\\r\\n]*))"; + tmp5 = "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" + tmp4; + tmp6 = tmp2 + tmp5; + tmp7 = new RegExp(tmp6, "gi"); + this.objPattern = tmp7; + } + toArrays(strData) { + let arrData, arrMatches, scrut, strMatchedDelimiter, scrut1, strMatchedValue, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; + arrData = [ + [] + ]; + tmp7: while (true) { + arrMatches = this.objPattern.exec(strData) ?? null; + scrut = arrMatches !== null; + if (scrut) { + strMatchedDelimiter = arrMatches[1]; + tmp = strMatchedDelimiter != this.strDelimiter; + scrut1 = strMatchedDelimiter.length && tmp; + if (scrut1) { + tmp1 = arrData.push([]) ?? null; + } else { + tmp1 = null; + } + scrut2 = arrMatches[2]; + if (scrut2) { + tmp2 = new RegExp("\"\"", "g"); + tmp3 = arrMatches[2].replace(tmp2, "\""); + } else { + tmp3 = arrMatches[3]; + } + strMatchedValue = tmp3; + tmp4 = arrData.length - 1; + tmp5 = arrData.at(tmp4) ?? null; + tmp6 = tmp5.push(strMatchedValue) ?? null; + continue tmp7; + } else { + tmp6 = null; + } + break; + } + return arrData; + } + toString() { return "CSV(" + this.strDelimiter + ")"; } +}; +null +export default CSV; diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls new file mode 100644 index 000000000..b8b1250cc --- /dev/null +++ b/hkmc2/shared/src/test/mlscript-compile/apps/CSV.mls @@ -0,0 +1,155 @@ + +// Adapted from https://www.bennadel.com/blog/1504-ask-ben-parsing-csv-strings-with-javascript-exec-regular-expression-command.htm +// (JS code commented at bottom) + +import "../Str.mls" +open Str + +import "../Predef.mls" +open Predef + + +// The default delimiter is the comma, but this can be overriden here. +class CSV(strDelimiter) with ... + + +// Check to see if the delimiter is defined. If not, then default to comma. +set strDelimiter ||= "," + +// TODO allow better code layout/formatting +val objPattern = new RegExp of + // Delimiters. + "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" + + // Quoted fields. + "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" + + // Standard fields. + "([^\"\\" + strDelimiter + "\\r\\n]*))" + "gi" + +// This will parse a delimited string into an array of arrays. +fun toArrays(strData) = + + // Create an array to hold our data. Give the array a default empty first row. + let arrData = [[]] + + // Keep looping over the regular expression matches until we can no longer find a match. + while + let arrMatches = objPattern.exec(strData) + arrMatches !== null do + + // Get the delimiter that was found. + let strMatchedDelimiter = arrMatches.1 + + // Check to see if the given delimiter has a length (is not the start of string) + // and if it matches field delimiter. + // If id does not, then we know that this delimiter is a row delimiter. + if strMatchedDelimiter.length && strMatchedDelimiter != strDelimiter do + // Since we have reached a new row of data, add an empty row to our data array. + arrData.push([]) + + // Now that we have our delimiter out of the way, + // let's check to see which kind of value we captured (quoted or unquoted). + let strMatchedValue = + if arrMatches.2 + then + // We found a quoted value. When we capture this value, unescape any double quotes. + arrMatches.2.replace of + new RegExp("\"\"", "g") + "\"" + else + // We found a non-quoted value. + arrMatches.3 + + arrData.at(arrData.length - 1).push(strMatchedValue) + + arrData + + +/* + // This will parse a delimited string into an array of + // arrays. The default delimiter is the comma, but this + // can be overriden in the second argument. + function CSVToArray( strData, strDelimiter ){ + // Check to see if the delimiter is defined. If not, + // then default to comma. + strDelimiter = (strDelimiter || ","); + + // Create a regular expression to parse the CSV values. + var objPattern = new RegExp( + ( + // Delimiters. + "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" + + + // Quoted fields. + "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" + + + // Standard fields. + "([^\"\\" + strDelimiter + "\\r\\n]*))" + ), + "gi" + ); + + + // Create an array to hold our data. Give the array + // a default empty first row. + var arrData = [[]]; + + // Create an array to hold our individual pattern + // matching groups. + var arrMatches = null; + + + // Keep looping over the regular expression matches + // until we can no longer find a match. + while (arrMatches = objPattern.exec( strData )){ + + // Get the delimiter that was found. + var strMatchedDelimiter = arrMatches[ 1 ]; + + // Check to see if the given delimiter has a length + // (is not the start of string) and if it matches + // field delimiter. If id does not, then we know + // that this delimiter is a row delimiter. + if ( + strMatchedDelimiter.length && + (strMatchedDelimiter != strDelimiter) + ){ + + // Since we have reached a new row of data, + // add an empty row to our data array. + arrData.push( [] ); + + } + + + // Now that we have our delimiter out of the way, + // let's check to see which kind of value we + // captured (quoted or unquoted). + if (arrMatches[ 2 ]){ + + // We found a quoted value. When we capture + // this value, unescape any double quotes. + var strMatchedValue = arrMatches[ 2 ].replace( + new RegExp( "\"\"", "g" ), + "\"" + ); + + } else { + + // We found a non-quoted value. + var strMatchedValue = arrMatches[ 3 ]; + + } + + + // Now that we have our value string, let's add + // it to the data array. + arrData[ arrData.length - 1 ].push( strMatchedValue ); + } + + // Return the parsed data. + return( arrData ); + } +*/ + + diff --git a/hkmc2/shared/src/test/mlscript/apps/CSVTest.mls b/hkmc2/shared/src/test/mlscript/apps/CSVTest.mls new file mode 100644 index 000000000..0a5e1eaa0 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/apps/CSVTest.mls @@ -0,0 +1,40 @@ +:js + +import "../../mlscript-compile/apps/CSV.mls" + + +let csv = CSV() +//│ > CSV { +//│ > strDelimiter: ',', +//│ > objPattern: /(\,|\r?\n|\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^"\,\r\n]*))/gi +//│ csv = } + +csv.strDelimiter +//│ = ',' + +csv.objPattern +//│ = /(\,|\r?\n|\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^"\,\r\n]*))/gi + +csv.toArrays("a,b,c") +//│ = [ [ 'a', 'b', 'c' ] ] + +csv.toArrays("a,b,c\n1,2,3\n4,5,6") +//│ = [ [ 'a', 'b', 'c' ], [ '1', '2', '3' ], [ '4', '5', '6' ] ] + +csv.toArrays("a,b,c\n\n1,2,3\n4,5,6\n") +//│ > [ +//│ > [ 'a', 'b', 'c' ], +//│ > [ '' ], +//│ > [ '1', '2', '3' ], +//│ > [ '4', '5', '6' ], +//│ > [ '' ] +//│ = ] + +csv.toArrays(",") +//│ = [ [ '' ] ] + +csv.toArrays(",,") +//│ = [ [ '', '' ] ] + + + diff --git a/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls new file mode 100644 index 000000000..0c1ab109f --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/backlog/ToTriage.mls @@ -0,0 +1,112 @@ +:js +:todo + + +// ——— ——— ——— + +"a" + + "b" +//│ = 'ab' + +// FIXME +"a" + + // a +"b" +//│ = 'b' + +// FIXME +// :dp +"a" + +// a +"b" +//│ ╔══[PARSE ERROR] Expected start of statement in this position; found newline instead +//│ ║ l.20: // a +//│ ║ ^ +//│ ║ l.21: "b" +//│ ╙── + +// ——— ——— ——— + +true or true +//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(BoolLit(true),keyword 'or',BoolLit(true)) (of class hkmc2.syntax.Tree$InfixApp) + +:fixme +while false or false do print("ok") +//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(BoolLit(false),keyword 'or',BoolLit(false)) (of class hkmc2.syntax.Tree$InfixApp) + +// ——— ——— ——— + +let g = [0] +//│ g = [ 0 ] + +:fixme +set g.0 += 1 // FIXME +//│ /!!!\ Uncaught error: scala.MatchError: LetLike(keyword 'set',App(Ident(+=),Tup(List(Sel(Ident(g),Ident(0)), IntLit(1)))),None,None) (of class hkmc2.syntax.Tree$LetLike) + +:fixme +set g.0 = g.0 + 1 +//│ > try { let selRes, tmp, tmp1; selRes = this.g[0]; if (selRes === undefined) { throw new this.Error("Access to required field '0' yielded 'undefined'"); } else { tmp = selRes; } tmp1 = tmp + 1; this.g.0 = tmp1; null } catch (e) { console.log('\u200B' + e + '\u200B'); } +//│ > ^^ +//│ ═══[COMPILATION ERROR] [Uncaught SyntaxError] Unexpected number + +// ——— ——— ——— + +:todo // TODO instrument Predef +:re +id(1, 2) +//│ = 1 + +// ——— ——— ——— + +:todo // TODO confusing formatting should not be allowed +:e +let x = if true +then 1 +else 0 +//│ x = 1 + +// ——— ——— ——— + +// TODO we should give reasonable meaning to `==` +// notably taking into account arrays and data classes; +// and deprecated `===` (notably does not work for arrays) + +undefined == null +//│ = true + +undefined === null +//│ = false + +// ——— ——— ——— + +// FIXME not sanitized? +:ssjs +Num +//│ JS: +//│ this.Num + +// ——— ——— ——— + +Infinity +//│ = Infinity + +:sjs +val Infinity = 1 +//│ JS (unsanitized): +//│ this.Infinity = 1; null +//│ Infinity = Infinity + +:sjs +Infinity +//│ JS (unsanitized): +//│ this.Infinity +//│ = Infinity + +module Test with + val Infinity = 1 + +Test.Infinity +//│ = 1 + +// ——— ——— ——— + diff --git a/hkmc2/shared/src/test/mlscript/backlog/UCS.mls b/hkmc2/shared/src/test/mlscript/backlog/UCS.mls new file mode 100644 index 000000000..e9c48312b --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/backlog/UCS.mls @@ -0,0 +1,97 @@ +:js +:todo + + +// ——— ——— ——— + +:fixme +while + let arrMatches = true + arrMatches + do + print(arrMatches) +//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(IfLike(keyword 'while',None,Block(List(LetLike(keyword 'let',Ident(arrMatches),Some(BoolLit(true)),None), Ident(arrMatches)))),keyword 'do',Block(List(App(Ident(print),Tup(List(Ident(arrMatches))))))) (of class hkmc2.syntax.Tree$InfixApp) + +// ——— ——— ——— + +let arrMatches = () + +// * Note: produces a match error +:fixme +if + arrMatches !== null + do + () +//│ ═══[ERROR] Unrecognized term split (null). +//│ ═══[RUNTIME ERROR] Error: match error + +// * This one works: +if + arrMatches !== null do + () + +// ——— ——— ——— + +:fixme +if + true and + true do + print("ok") +//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing + +if + true and + true do + print("ok") +//│ > ok + +:fixme +if + true + and + true do + print("ok") +//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing + +if + true + and true do + print("ok") +//│ > ok + +if + true + and true + and true do + print("ok") +//│ > ok + +:fixme +if + true + and true + let x = 1 + and true do + print("ok") +//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(InfixApp(IntLit(1),keyword 'and',BoolLit(true)),keyword 'do',Block(List(App(Ident(print),Tup(List(StrLit(ok))))))) (of class hkmc2.syntax.Tree$InfixApp) + +// ——— ——— ——— + +let audits = new Set() +//│ audits = Set(0) {} + +:fixme +if audits.has(1) === true do + print("ok") +else + 1 +//│ ╔══[ERROR] The following branches are unreachable. +//│ ╟── Because the previous split is full. +//│ ║ l.84: if audits.has(1) === true do +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.85: print("ok") +//│ ╙── ^^^^^^^^^^^^^ +//│ = 1 + +// ——— ——— ——— + diff --git a/hkmc2/shared/src/test/mlscript/basics/PredefTest.mls b/hkmc2/shared/src/test/mlscript/basics/PredefTest.mls index c09f0f1cb..dea0b142e 100644 --- a/hkmc2/shared/src/test/mlscript/basics/PredefTest.mls +++ b/hkmc2/shared/src/test/mlscript/basics/PredefTest.mls @@ -35,3 +35,7 @@ new Predef.Test //│ ═══[RUNTIME ERROR] Error: Access to required field 'x' yielded 'undefined' +print(1, 2, 3) +//│ > 1 2 3 + + diff --git a/hkmc2/shared/src/test/mlscript/basics/StrTest.mls b/hkmc2/shared/src/test/mlscript/basics/StrTest.mls new file mode 100644 index 000000000..c0edf0b59 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/basics/StrTest.mls @@ -0,0 +1,55 @@ +:js + +import "../../mlscript-compile/Str.mls" + +open Str + + +concat of "a", "b", "c" +//│ = 'abc' + +concat of + "a" + "b" + "c" +//│ = 'abc' + + +// TODO (~) should be sanitized + +(~)("a", "b", "c") +//│ = 'ab' + +(~)("a") +//│ = 'a' + +~"a" +//│ = 'a' + +:w +~("a", "b") +//│ ╔══[WARNING] Pure expression in statement position +//│ ║ l.30: ~("a", "b") +//│ ╙── ^^^ +//│ = 'b' + +:pe +~ of "a", "b" +//│ ╔══[PARSE ERROR] Expected start of statement in this position; found 'of' keyword instead +//│ ║ l.37: ~ of "a", "b" +//│ ╙── ^^ +//│ ╔══[PARSE ERROR] Expected end of input; found literal instead +//│ ║ l.37: ~ of "a", "b" +//│ ╙── ^^^ + +(~) of "a", "b" +//│ = 'ab' + + +fun test(...xs) = + concat of ...xs, "!" + +test("a", "b", "c") +//│ = 'abc!' + + diff --git a/hkmc2/shared/src/test/mlscript/basics/WeirdCalls.mls b/hkmc2/shared/src/test/mlscript/basics/WeirdCalls.mls new file mode 100644 index 000000000..8e92d728c --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/basics/WeirdCalls.mls @@ -0,0 +1,75 @@ +:js + + +// TODO: should report that such `print` reference does not do anything in statement position +print +( + "A" +) +//│ = 'A' + +:fixme +print + ( + "A" + ) +//│ ╔══[ERROR] Illegal juxtaposition right-hand side. +//│ ║ l.14: "A" +//│ ╙── ^^^ +//│ = [Function: print] + +:pe +print of +( + "A" +) +//│ ╔══[PARSE ERROR] Expected an expression; found new line instead +//│ ║ l.22: print of +//│ ║ ^ +//│ ║ l.23: ( +//│ ╙── + +:pe +print of +("A") +//│ ╔══[PARSE ERROR] Expected an expression; found new line instead +//│ ║ l.33: print of +//│ ║ ^ +//│ ║ l.34: ("A") +//│ ╙── + +print of + ( + "A" + ) +//│ > A + +:pe +print of + ( + "A" +) +//│ ╔══[PARSE ERROR] Mistmatched closing indentation +//│ ║ l.50: "A" +//│ ║ ^ +//│ ║ l.51: ) +//│ ║ +//│ ╟── does not correspond to opening parenthesis +//│ ║ l.49: ( +//│ ╙── ^ +//│ ╔══[PARSE ERROR] Unexpected closing parenthesis +//│ ║ l.51: ) +//│ ╙── ^ +//│ > A + +print of ( + "A" +) +//│ > A + +print( + "A" +) +//│ > A + + diff --git a/hkmc2/shared/src/test/mlscript/codegen/Do.mls b/hkmc2/shared/src/test/mlscript/codegen/Do.mls index cd7759be9..523c491b3 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Do.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Do.mls @@ -20,18 +20,32 @@ do 1 //│ ║ l.18: do 1 //│ ╙── ^ +do + let hello = 1 + () + +// FIXME shouldn't be accessible +:todo +:re +print(globalThis.hello) +//│ > 1 + :ucs desugared val f = case 0 then "null" do console.log("non-null") 1 then "unit" - _ then "other" + let res = "other" + do print(res) + _ then res //│ Desugared: //│ > if //│ > caseScrut is 0 then "null" //│ > let $doTemp = globalThis:import#Prelude#666(.)console‹member:console›(.)log("non-null") //│ > caseScrut is 1 then "unit" -//│ > else "other" +//│ > let res = "other" +//│ > let $doTemp = member:Predef#666(.)print‹member:print›(res#666) +//│ > else res#666 //│ f = [Function: tmp] f(0) @@ -43,5 +57,7 @@ f(1) f(2) //│ > non-null +//│ > other //│ = 'other' + diff --git a/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls b/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls index f99b0fe27..877cefcdd 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/GlobalThis.mls @@ -2,7 +2,7 @@ :silent -globalThis +let g = globalThis // FIXME prevent rebinding of this name diff --git a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls index 4b8658f03..10f50b438 100644 --- a/hkmc2/shared/src/test/mlscript/decls/Prelude.mls +++ b/hkmc2/shared/src/test/mlscript/decls/Prelude.mls @@ -8,9 +8,11 @@ declare class Bool declare class Int declare class Num declare class Str +declare class Set declare class Error(msg) declare class String +declare class RegExp // declare module Math // TODO: list members declare val Math // so we can, eg, `open { pow }` in the meantime @@ -18,6 +20,7 @@ declare val Math // so we can, eg, `open { pow }` in the meantime declare val console declare val process declare val fs +declare val Infinity declare module Predef with diff --git a/hkmc2/shared/src/test/mlscript/interop/Array.mls b/hkmc2/shared/src/test/mlscript/interop/Array.mls new file mode 100644 index 000000000..13a01bbf7 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/interop/Array.mls @@ -0,0 +1,38 @@ +:js + + +let arr = [true, false] +//│ arr = [ true, false ] + + +// * Array methods annoyingly supply extra arguments, such as the index and the array itself +:re +arr.map(_ === false) +//│ ═══[RUNTIME ERROR] Error: Function expected 1 arguments but got 3 + +:todo +arr.map((e, ...) => e === false) +//│ /!!!\ Uncaught error: scala.MatchError: Spread(keyword '...',Some(Loc(12,15,Array.mls:+14)),None) (of class hkmc2.syntax.Tree$Spread) + +arr.map(pass1(_ === false)) +//│ = [ false, true ] + + +[1,3,2].map((x, _, _) => x + 1).sort() +//│ = [ 2, 3, 4 ] + +:fixme +[1,3,2].map((x, _, _) => x + 1) + .sort() +//│ ╔══[PARSE ERROR] Expected an expression; found new line instead +//│ ║ l.26: .sort() +//│ ╙── ^^^^^ +//│ ╔══[PARSE ERROR] Unexpected selector here +//│ ║ l.26: .sort() +//│ ╙── ^^^^^ +//│ ╔══[ERROR] Illegal juxtaposition right-hand side. +//│ ║ l.26: .sort() +//│ ╙── ^^^^^ +//│ = [ 2, 4, 3 ] + + diff --git a/hkmc2/shared/src/test/mlscript/interop/Number.mls b/hkmc2/shared/src/test/mlscript/interop/Number.mls new file mode 100644 index 000000000..e1082dd9c --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/interop/Number.mls @@ -0,0 +1,15 @@ +:js + + + +globalThis.Number("0.50") +//│ = 0.5 + + +:fixme +let num = _ globalThis.Number() +//│ ═══[ERROR] Illegal position for '_' placeholder. + + + + diff --git a/hkmc2/shared/src/test/mlscript/parser/Of.mls b/hkmc2/shared/src/test/mlscript/parser/Of.mls index 399b480cd..1d81d1c56 100644 --- a/hkmc2/shared/src/test/mlscript/parser/Of.mls +++ b/hkmc2/shared/src/test/mlscript/parser/Of.mls @@ -77,6 +77,13 @@ of 1 :fixme print of 123 -//│ /!!!\ Uncaught error: scala.NotImplementedError: NEWLINE (of class NEWLINE$) +//│ ╔══[PARSE ERROR] Expected an expression; found new line instead +//│ ║ l.78: print of +//│ ║ ^ +//│ ║ l.79: 123 +//│ ╙── +//│ Parsed: +//│ App(Ident(print),Tup(List(Error()))) +//│ IntLit(123) diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/EitherOrBoth.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/EitherOrBoth.mls index 71b517f9d..9f9619e5c 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/EitherOrBoth.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/EitherOrBoth.mls @@ -73,6 +73,6 @@ fun isBoth[A, B](eob: EitherOrBoth[A, B]): Bool = fun eobToString[A, B](eob: EitherOrBoth[A, B]): Str = if eob is - Left(left) then "Left(" ~ string(left) ~ ")" - Right(right) then "Right(" ~ string(right) ~ ")" - Both(left, right) then "Both(" ~ string(left) ~ ", " ~ string(right) ~ ")" + Left(left) then "Left(" ~ Str.from(left) ~ ")" + Right(right) then "Right(" ~ Str.from(right) ~ ")" + Both(left, right) then "Both(" ~ Str.from(left) ~ ", " ~ Str.from(right) ~ ")" diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/ListFold.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/ListFold.mls index edbde8138..3a4f3b50e 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/ListFold.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/ListFold.mls @@ -24,10 +24,10 @@ fun join(sep) = fun aux(acc, xs) = if xs is Nil then acc - Cons(x, xs') then aux(acc ~ sep ~ string(x), xs') + Cons(x, xs') then aux(acc ~ sep ~ Str.from(x), xs') (xs) => if xs is - Cons(x, xs') then aux(string(x), xs') + Cons(x, xs') then aux(Str.from(x), xs') Nil then "" join(", ")(1 :: 2 :: 3 :: Nil) diff --git a/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls b/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls index 0f11085ce..c81261579 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/examples/Permutations.mls @@ -23,10 +23,10 @@ fun join(sep) = fun aux(acc, xs) = if xs is Nil then acc - Cons(x, xs') then aux(acc ~ sep ~ string(x), xs') + Cons(x, xs') then aux(acc ~ sep ~ Str.from(x), xs') (xs) => if xs is - Cons(x, xs') then aux(string(x), xs') + Cons(x, xs') then aux(Str.from(x), xs') Nil then "" fun showList(xs) = "[" ~ join(", ")(xs) ~ "]" fun foldLeft(f)(z) = diff --git a/out/apps/comp3031-fall24.md b/out/apps/comp3031-fall24.md new file mode 100644 index 000000000..f957e827f --- /dev/null +++ b/out/apps/comp3031-fall24.md @@ -0,0 +1,42 @@ +# Grades + +| # | id | uid | score | grade | name | +|---|---|---:|---|---|---| +| 1| 21064231| yhliaf | 98.88 | A+ | LI, Yu Hong Harry | +| 2| 20966509| ltching | 98.05 | A+ | CHING, Long Tin | +| 3| 20950108| hlyual | 94.55 | A+ | YU, Hon Lam | +| 4| 20844646| qduanaa | 93 | A+ | DUAN, Qinkai | +| 5| 20824086| yharimoto | 91 | A+ | HARIMOTO, Yuken | +| 6| 20992302| tkorganbayev | 84.75 | A | KORGANBAYEV, Taimas | +| 7| 20965646| scyeungaf | 82.35 | A | YEUNG, Sin Chun | +| 8| 20949965| cnleungaf | 81.9 | A | LEUNG, Cheuk Nang | +| 9| 21160047| rblazek | 81.83 | A | BLAZEK, Richard | +| 10| 20988222| rbaishuak | 81.1 | A | BAISHUAK, Raiymbek | +| 11| 20963258| kytsuiad | 76.93 | B+ | TSUI, Ka Yi | +| 12| 21010747| bsakenov | 74.95 | B+ | SAKENOV, Batyrkhan | +| 13| 20759023| orbhandari | 74.9 | B+ | BHANDARI, Om Raj | +| 14| 21022180| msakhmoldin | 74.9 | B+ | SAKHMOLDIN, Mukhammadarif | +| 15| 20493849| zzhangcy | 74.7 | B | ZHANG, Zidi | +| 16| 20944264| ygubb | 73.35 | B | GU, Yuyang | +| 17| 20726313| hhpark | 71.78 | B | PARK, Hyun Hu | +| 18| 20862296| ttchui | 69.68 | AU | CHUI, Tsz Tou | +| 19| 20920921| cmcheungaj | 65.4 | B | CHEUNG, Cheuk Man | +| 20| 21161596| rdgao | 63.1 | B | GAO, Richard Daniel | +| 21| 20862105| lklamad | 62.88 | B | LAM, Leung Kin | +| 22| 20957065| thkwokah | 61.75 | B | KWOK, Tsz Hin | +| 23| 20964563| yliner | 59.25 | C+ | LIN, Yi | +| 24| 20855956| tykongab | 57.5 | C+ | KONG, Tsz Yui | +| 25| 20853087| hflamag | 56 | C+ | LAM, Hung Fai | +| 26| 21160059| kajoyce | 51.93 | C+ | JOYCE, Kitty Amelia | +| 27| 20966303| kclambd | 49.23 | C | LAM, King Cheuk | +| 28| 20860200| ynwongaf | 46.25 | C | WONG, Yin Nok | +| 29| 20825676| suryanarayan | 42 | C | SURYA-NARAYAN, . | +| 30| 20865169| lcman | 41.33 | D | MAN, Lai Chuen | +| 31| 20817514| ghanab | 39.8 | D | HAN, Gyungchae | +| 32| 21066552| ksngah | 38.48 | D | NG, King Sum | +| 33| 20759566| yzhongbd | 33.33 | F | ZHONG, Yingqi | +| 34| 20855449| sltong | 26.25 | F | TONG, Sui Lam | +| 35| 20803288| mytoab | 22.38 | F | TO, Ming Yan | +| 36| 20855023| phtaiaa | 2.25 | F | TAI, Pui Hong | +| 37| 21039767| hbaeaa | 0 | F | BAE, Hangyeol | +| 38| 21160853| mmehari | 0 | F | MEHARI, Mathias |