const Cast = require('../util/cast.js'); const MathUtil = require('../util/math-util.js'); const SandboxRunner = require('../util/sandboxed-javascript-runner.js'); const { validateRegex } = require('../util/json-block-utilities'); class Scratch3OperatorsBlocks { constructor (runtime) { /** * The runtime instantiating this block package. * @type {Runtime} */ this.runtime = runtime; } /** * Retrieve the block primitives implemented by this package. * @return {object.} Mapping of opcode to Function. */ // getPrimitives () { return { operator_add: this.add, operator_subtract: this.subtract, operator_multiply: this.multiply, operator_divide: this.divide, operator_power: this.power, operator_lt: this.lt, operator_equals: this.equals, operator_notequal: this.notequals, operator_gt: this.gt, operator_ltorequal: this.ltorequal, operator_gtorequal: this.gtorequal, operator_and: this.and, operator_nand: this.nand, operator_nor: this.nor, operator_xor: this.xor, operator_xnor: this.xnor, operator_or: this.or, operator_not: this.not, operator_random: this.random, operator_join: this.join, operator_join3: this.join3, operator_letter_of: this.letterOf, operator_length: this.length, operator_contains: this.contains, operator_mod: this.mod, operator_round: this.round, operator_mathop: this.mathop, operator_advlog: this.advlog, operator_regexmatch: this.regexmatch, operator_replaceAll: this.replaceAll, operator_replaceFirst: this.replaceFirst, operator_getLettersFromIndexToIndexInText: this.getLettersFromIndexToIndexInText, operator_getLettersFromIndexToIndexInTextFixed: this.getLettersFromIndexToIndexInTextFixed, operator_readLineInMultilineText: this.readLineInMultilineText, operator_newLine: this.newLine, operator_tabCharacter: this.tabCharacter, operator_stringify: this.stringify, operator_boolify: this.boolify, operator_lerpFunc: this.lerpFunc, operator_advMath: this.advMath, operator_advMathExpanded: this.advMathExpanded, operator_constrainnumber: this.constrainnumber, operator_trueBoolean: this.true, operator_falseBoolean: this.false, operator_randomBoolean: this.randomBoolean, operator_indexOfTextInText: this.indexOfTextInText, operator_lastIndexOfTextInText: this.lastIndexOfTextInText, operator_toUpperLowerCase: this.toCase, operator_character_to_code: this.charToCode, operator_code_to_character: this.codeToChar, operator_textStartsOrEndsWith: this.textStartsOrEndsWith, operator_countAppearTimes: this.countAppearTimes, operator_textIncludesLetterFrom: this.textIncludesLetterFrom, operator_javascript_output: this.javascriptOutput, operator_javascript_boolean: this.javascriptBoolean }; } javascriptOutput (args) { return new Promise((resolve, reject) => { const js = Cast.toString(args.JS); SandboxRunner.execute(js).then(result => { resolve(result.value) }) }) } javascriptBoolean(args) { return new Promise((resolve, reject) => { const js = Cast.toString(args.JS); SandboxRunner.execute(js).then(result => { resolve(result.value === true) }) }) } charToCode (args) { const char = Cast.toString(args.ONE); if (!char) return NaN; return char.charCodeAt(0); } codeToChar (args) { const code = Cast.toNumber(args.ONE); return String.fromCharCode(code); } toCase (args) { const text = Cast.toString(args.TEXT); switch (args.OPTION) { case 'upper': return text.toUpperCase(); case 'lower': return text.toLowerCase(); } } indexOfTextInText (args) { const lookfor = Cast.toString(args.TEXT1); const searchin = Cast.toString(args.TEXT2); let index = 0; if (searchin.includes(lookfor)) { index = searchin.indexOf(lookfor) + 1; } return index; } lastIndexOfTextInText (args) { const lookfor = Cast.toString(args.TEXT1); const searchin = Cast.toString(args.TEXT2); let index = 0; if (searchin.includes(lookfor)) { index = searchin.lastIndexOf(lookfor) + 1; } return index; } textStartsOrEndsWith (args) { const text = Cast.toString(args.TEXT1); const startsOrEnds = Cast.toString(args.OPTION); const withh = Cast.toString(args.TEXT2); return (startsOrEnds === "starts") ? (text.startsWith(withh)) : (text.endsWith(withh)); } countAppearTimes (args) { const text = Cast.toString(args.TEXT2); const otherText = Cast.toString(args.TEXT1); const aray = text.split(otherText); if (aray.length <= 1) { return 0; } return aray.length - 1; } textIncludesLetterFrom (args) { const text = Cast.toString(args.TEXT1); const from = Cast.toString(args.TEXT2); let includes = false; const aray = from.split(""); aray.forEach(i => { if (text.includes(i)) includes = true; }) return includes; } true () { return true; } false () { return false; } randomBoolean () { return Boolean(Math.round(Math.random())); } constrainnumber (args) { return Math.min(Math.max(args.min, args.inp), args.max); } lerpFunc (args) { const one = Cast.toNumber(args.ONE); const two = Cast.toNumber(args.TWO); const amount = Cast.toNumber(args.AMOUNT); return ((two - one) * amount) + one; } advMath (args) { const one = isNaN(Cast.toNumber(args.ONE)) ? 0 : Cast.toNumber(args.ONE); const two = isNaN(Cast.toNumber(args.TWO)) ? 0 : Cast.toNumber(args.TWO); const operator = Cast.toString(args.OPTION); switch (operator) { case "^": return one ** two; case "root": return one ** 1 / two; case "log": return Math.log(two) / Math.log(one); default: return 0; } } advMathExpanded (args) { const one = Cast.toNumber(args.ONE); const two = Cast.toNumber(args.TWO); const three = Cast.toNumber(args.THREE); const operator = Cast.toString(args.OPTION); switch (operator) { case "root": return one * Math.pow(three, 1 / two); case "log": return one * Math.log(three) / Math.log(two); default: return 0; } } stringify (args) { return Cast.toString(args.ONE); } boolify (args) { return Cast.toBoolean(args.ONE); } newLine () { return "\n"; } tabCharacter () { return "\t"; } readLineInMultilineText (args) { const line = (Cast.toNumber(args.LINE) ? Cast.toNumber(args.LINE) : 1) - 1; const text = Cast.toString(args.TEXT); const readline = text.split("\n")[line] || ""; return readline; } getLettersFromIndexToIndexInTextFixed (args) { const index1 = (Cast.toNumber(args.INDEX1) ? Cast.toNumber(args.INDEX1) : 1) - 1; const index2 = (Cast.toNumber(args.INDEX2) ? Cast.toNumber(args.INDEX2) : 1); const string = Cast.toString(args.TEXT); const substring = string.substring(index1, index2); return substring; } getLettersFromIndexToIndexInText (args) { const index1 = (Cast.toNumber(args.INDEX1) ? Cast.toNumber(args.INDEX1) : 1) - 1; const index2 = (Cast.toNumber(args.INDEX2) ? Cast.toNumber(args.INDEX2) : 1) - 1; const string = Cast.toString(args.TEXT); const substring = string.substring(index1, index2); return substring; } replaceAll (args) { return Cast.toString(args.text).replaceAll(args.term, args.res); } replaceFirst (args) { return Cast.toString(args.text).replace(args.term, args.res); } regexmatch (args) { if (!validateRegex(args.reg, args.regrule)) return "[]"; const regex = new RegExp(args.reg, args.regrule); const matches = args.text.match(regex); return JSON.stringify(matches ? matches : []); } add (args) { return Cast.toNumber(args.NUM1) + Cast.toNumber(args.NUM2); } subtract (args) { return Cast.toNumber(args.NUM1) - Cast.toNumber(args.NUM2); } multiply (args) { return Cast.toNumber(args.NUM1) * Cast.toNumber(args.NUM2); } divide (args) { return Cast.toNumber(args.NUM1) / Cast.toNumber(args.NUM2); } power (args) { return Math.pow(Cast.toNumber(args.NUM1), Cast.toNumber(args.NUM2)); } lt (args) { return Cast.compare(args.OPERAND1, args.OPERAND2) < 0; } equals (args) { return Cast.compare(args.OPERAND1, args.OPERAND2) === 0; } notequals (args) { return !this.equals(args); } gt (args) { return Cast.compare(args.OPERAND1, args.OPERAND2) > 0; } gtorequal (args) { return !this.lt(args); } ltorequal (args) { return !this.gt(args); } and (args) { return Cast.toBoolean(args.OPERAND1) && Cast.toBoolean(args.OPERAND2); } nand (args) { return !(Cast.toBoolean(args.OPERAND1) && Cast.toBoolean(args.OPERAND2)); } nor (args) { return !(Cast.toBoolean(args.OPERAND1) || Cast.toBoolean(args.OPERAND2)); } xor (args) { const op1 = Cast.toBoolean(args.OPERAND1); const op2 = Cast.toBoolean(args.OPERAND2); return (op1 ? !op2 : op2); } xnor (args) { return !this.xor(args); } or (args) { return Cast.toBoolean(args.OPERAND1) || Cast.toBoolean(args.OPERAND2); } not (args) { return !Cast.toBoolean(args.OPERAND); } random (args) { return this._random(args.FROM, args.TO); } _random (from, to) { // used by compiler const nFrom = Cast.toNumber(from); const nTo = Cast.toNumber(to); const low = nFrom <= nTo ? nFrom : nTo; const high = nFrom <= nTo ? nTo : nFrom; if (low === high) return low; // If both arguments are ints, truncate the result to an int. if (Cast.isInt(from) && Cast.isInt(to)) { return low + Math.floor(Math.random() * ((high + 1) - low)); } return (Math.random() * (high - low)) + low; } join (args) { return Cast.toString(args.STRING1) + Cast.toString(args.STRING2); } join3 (args) { return Cast.toString(args.STRING1) + Cast.toString(args.STRING2) + Cast.toString(args.STRING3); } letterOf (args) { const index = Cast.toNumber(args.LETTER) - 1; const str = Cast.toString(args.STRING); // Out of bounds? if (index < 0 || index >= str.length) { return ''; } return str.charAt(index); } length (args) { return Cast.toString(args.STRING).length; } contains (args) { const format = function (string) { return Cast.toString(string).toLowerCase(); }; return format(args.STRING1).includes(format(args.STRING2)); } mod (args) { const n = Cast.toNumber(args.NUM1); const modulus = Cast.toNumber(args.NUM2); let result = n % modulus; // Scratch mod uses floored division instead of truncated division. if (result / modulus < 0) result += modulus; return result; } round (args) { return Math.round(Cast.toNumber(args.NUM)); } mathop (args) { const operator = Cast.toString(args.OPERATOR).toLowerCase(); const n = Cast.toNumber(args.NUM); switch (operator) { case 'abs': return Math.abs(n); case 'floor': return Math.floor(n); case 'ceiling': return Math.ceil(n); case 'sqrt': return Math.sqrt(n); case 'sin': return Math.round(Math.sin((Math.PI * n) / 180) * 1e10) / 1e10; case 'cos': return Math.round(Math.cos((Math.PI * n) / 180) * 1e10) / 1e10; case 'tan': return MathUtil.tan(n); case 'asin': return (Math.asin(n) * 180) / Math.PI; case 'acos': return (Math.acos(n) * 180) / Math.PI; case 'atan': return (Math.atan(n) * 180) / Math.PI; case 'ln': return Math.log(n); case 'log': return Math.log(n) / Math.LN10; case 'log2': return Math.log2(n); case 'e ^': return Math.exp(n); case '10 ^': return Math.pow(10, n); } return 0; } advlog (args) { return (Math.log(Cast.toNumber(args.NUM2)) / Math.log(Cast.toNumber(args.NUM1))); } } module.exports = Scratch3OperatorsBlocks;