/* credits brunt of the work by increpare (www.increpare.com) all open source mit license blah blah testers: none, yet code used colors used color values for named colours from arne, mostly (and a couple from a 32-colour palette attributed to him) http://androidarts.com/palette/16pal.htm the editor is a slight modification of codemirro (codemirror.net), which is crazy awesome. for post-launch credits, check out activty on github.com/increpare/PuzzleScript */ var compiling = false; var errorStrings = [];//also stores warning strings var errorCount=0;//only counts errors var twiddleable_params = ['background_color','text_color','key_repeat_interval','realtime_interval','again_interval','flickscreen','zoomscreen','smoothscreen','noundo','norestart','message_text_align']; //Please note that you have to add these twiddleable properties in more locations that just here! function logErrorCacheable(str, lineNumber,urgent) { if (compiling||urgent) { if (lineNumber === undefined) { return logErrorNoLine(str,urgent); } var errorString = ' line ' + lineNumber.toString() + ' : ' + '' + str + ''; if (errorStrings.indexOf(errorString) >= 0 && !urgent) { //do nothing, duplicate error } else { consolePrint(errorString); errorStrings.push(errorString); errorCount++; } } } function logError(str, lineNumber,urgent) { if (compiling||urgent) { if (lineNumber === undefined) { return logErrorNoLine(str,urgent); } var errorString = ' line ' + lineNumber.toString() + ' : ' + '' + str + ''; if (errorStrings.indexOf(errorString) >= 0 && !urgent) { //do nothing, duplicate error } else { consolePrint(errorString,true); errorStrings.push(errorString); errorCount++; } } } function logWarning(str, lineNumber,urgent) { if (compiling||urgent) { if (lineNumber === undefined) { return logWarningNoLine(str,urgent); } var errorString = ' line ' + lineNumber.toString() + ' : ' + '' + str + ''; if (errorStrings.indexOf(errorString) >= 0 && !urgent) { //do nothing, duplicate error } else { consolePrint(errorString,true); errorStrings.push(errorString); } } } function logWarningNoLine(str,urgent) { if (compiling||urgent) { var errorString = '' + str + ''; if (errorStrings.indexOf(errorString) >= 0 && !urgent) { //do nothing, duplicate error } else { consolePrint(errorString,true); errorStrings.push(errorString); } errorCount++; } } function logErrorNoLine(str,urgent) { if (compiling||urgent) { var errorString = '' + str + ''; if (errorStrings.indexOf(errorString) >= 0 && !urgent) { //do nothing, duplicate error } else { consolePrint(errorString,true); errorStrings.push(errorString); } errorCount++; } } function logBetaMessage(str,urgent){ if (compiling||urgent) { var errorString = '' + str + ''; if (errorStrings.indexOf(errorString) >= 0 && !urgent) { //do nothing, duplicate error } else { consoleError(errorString); errorStrings.push(errorString); } } } function blankLineHandle(state) { if (state.section === 'levels') { if (state.levels[state.levels.length - 1].length > 0) { state.levels.push([]); } } else if (state.section === 'objects') { state.objects_section = 0; } } //for IE support if (typeof Object.assign != 'function') { (function () { Object.assign = function (target) { 'use strict'; // We must check against these specific cases. if (target === undefined || target === null) { throw new TypeError('Cannot convert undefined or null to object'); } var output = Object(target); for (var index = 1; index < arguments.length; index++) { var source = arguments[index]; if (source !== undefined && source !== null) { for (var nextKey in source) { if (source.hasOwnProperty(nextKey)) { output[nextKey] = source[nextKey]; } } } } return output; }; })(); } var codeMirrorFn = function() { 'use strict'; function searchStringInArray(str, strArray) { for (var j = 0; j < strArray.length; j++) { if (strArray[j] === str) { return j; } } return -1; } function isMatrixLine(str) { for (var j = 0; j < str.length; j++) { if (str.charAt(j) !== '.' && str.charAt(j) !== '0') { return false; } } return true; } function checkNameNew(state,candname) { if (state.objects[candname] !== undefined) { logError('Object "' + candname + '" defined multiple times.', state.lineNumber); return 'ERROR'; } for (var i=0;i', 'moving','stationary','parallel','perpendicular', 'no']; //var logicWords = ['all', 'no', 'on', 'some']; var sectionNames = ['objects', 'legend', 'sounds', 'collisionlayers', 'rules', 'winconditions', 'levels']; var commandwords = ["sfx0","sfx1","sfx2","sfx3","sfx4","sfx5","sfx6","sfx7","sfx8","sfx9","sfx10","cancel","checkpoint","restart","win","message","again","undo", "nosave","quit","zoomscreen","flickscreen","smoothscreen","again_interval","realtime_interval","key_repeat_interval",'noundo','norestart','background_color','text_color','goto','message_text_align']; var reg_commands = /[\p{Z}\s]*(sfx0|sfx1|sfx2|sfx3|Sfx4|sfx5|sfx6|sfx7|sfx8|sfx9|sfx10|cancel|checkpoint|restart|win|message|again|undo|nosave)[\p{Z}\s]*/u; var reg_name = /[\p{L}\p{N}_]+[\p{Z}\s]*/u;///\w*[a-uw-zA-UW-Z0-9_]/; var reg_number = /[\d]+/; var reg_soundseed = /\d+\b/; var reg_spriterow = /[\.0-9]{5}[\p{Z}\s]*/u; var reg_sectionNames = /(objects|collisionlayers|legend|sounds|rules|winconditions|levels)(?![\p{L}\p{N}_])[\p{Z}\s]*/u; var reg_equalsrow = /[\=]+/; var reg_notcommentstart = /[^\(]+/; var reg_csv_separators = /[ \,]*/; var reg_soundverbs = /(move|action|create|destroy|cantmove|undo|restart|titlescreen|startgame|cancel|endgame|startlevel|endlevel|showmessage|closemessage|sfx0|sfx1|sfx2|sfx3|sfx4|sfx5|sfx6|sfx7|sfx8|sfx9|sfx10)\b[\p{Z}\s]*/u; var reg_directions = /^(action|up|down|left|right|\^|v|\<|\>|moving|stationary|parallel|perpendicular|horizontal|orthogonal|vertical|no|randomdir|random)$/; var reg_loopmarker = /^(startloop|endloop)$/; var reg_ruledirectionindicators = /^(up|down|left|right|horizontal|vertical|orthogonal|late|rigid)$/; var reg_sounddirectionindicators = /[\p{Z}\s]*(up|down|left|right|horizontal|vertical|orthogonal)(?![\p{L}\p{N}_])[\p{Z}\s]*/u; var reg_winconditionquantifiers = /^(all|any|no|some)$/; var reg_keywords = /(checkpoint|objects|collisionlayers|legend|sounds|rules|winconditions|\.\.\.|levels|up|down|left|right|^|\||\[|\]|v|\>|\<|no|horizontal|orthogonal|vertical|any|all|no|some|moving|stationary|parallel|perpendicular|action|nosave)/; var preamble_params = ['title','author','homepage','background_color','text_color','key_repeat_interval','realtime_interval','again_interval','flickscreen','zoomscreen','smoothscreen','color_palette','youtube', 'sprite_size','level_select_unlocked_ahead','level_select_solve_symbol','custom_font', 'mouse_left','mouse_drag','mouse_right','mouse_rdrag','mouse_up','mouse_rup','local_radius','font_size', 'tween_length', "tween_easing", "tween_snap", "message_text_align", "text_controls", "text_message_continue", "level_select_unlocked_rollover", "sitelock_origin_whitelist", "sitelock_hostname_whitelist"]; var preamble_keywords = ['run_rules_on_level_start','norepeat_action','require_player_movement','debug','verbose_logging','throttle_movement','noundo','noaction','norestart','scanline', 'case_sensitive','level_select','continue_is_level_select','level_select_lock','settings', 'runtime_metadata_twiddling', 'runtime_metadata_twiddling_debug', 'smoothscreen_debug','skip_title_screen','nokeyboard']; var keyword_array = ['checkpoint','objects', 'collisionlayers', 'legend', 'sounds', 'rules', '...','winconditions', 'levels','|','[',']','up', 'down', 'left', 'right', 'late','rigid', '^','v','\>','\<','no','randomdir','random', 'horizontal', 'vertical','any', 'all', 'no', 'some', 'moving','stationary','parallel','perpendicular','action','nosave','message','global','zoomscreen','flickscreen','smoothscreen','noundo','norestart','background_color','text_color','goto','message_text_align']; // var keywordRegex = new RegExp("\\b(("+cons.join(")|(")+"))$", 'i'); var fullSpriteMatrix = [ '00000', '00000', '00000', '00000', '00000' ]; return { copyState: function(state) { var objectsCopy = {}; for (var i in state.objects) { if (state.objects.hasOwnProperty(i)) { var o = state.objects[i]; objectsCopy[i] = { colors: o.colors.concat([]), lineNumber : o.lineNumber, spritematrix: o.spritematrix.concat([]) } } } var collisionLayersCopy = []; for (var i = 0; i < state.collisionLayers.length; i++) { collisionLayersCopy.push(state.collisionLayers[i].concat([])); } var legend_synonymsCopy = []; var legend_aggregatesCopy = []; var legend_propertiesCopy = []; var soundsCopy = []; var levelsCopy = []; var winConditionsCopy = []; for (var i = 0; i < state.legend_synonyms.length; i++) { legend_synonymsCopy.push(state.legend_synonyms[i].concat([])); } for (var i = 0; i < state.legend_aggregates.length; i++) { legend_aggregatesCopy.push(state.legend_aggregates[i].concat([])); } for (var i = 0; i < state.legend_properties.length; i++) { legend_propertiesCopy.push(state.legend_properties[i].concat([])); } for (var i = 0; i < state.sounds.length; i++) { soundsCopy.push(state.sounds[i].concat([])); } for (var i = 0; i < state.levels.length; i++) { levelsCopy.push(state.levels[i].concat([])); } for (var i = 0; i < state.winconditions.length; i++) { winConditionsCopy.push(state.winconditions[i].concat([])); } var original_case_namesCopy = Object.assign({},state.original_case_names); var nstate = { lineNumber: state.lineNumber, objects: objectsCopy, collisionLayers: collisionLayersCopy, commentLevel: state.commentLevel, section: state.section, visitedSections: state.visitedSections.concat([]), objects_candname: state.objects_candname, objects_section: state.objects_section, objects_spritematrix: state.objects_spritematrix.concat([]), tokenIndex: state.tokenIndex, currentSection: state.currentSection, legend_synonyms: legend_synonymsCopy, legend_aggregates: legend_aggregatesCopy, legend_properties: legend_propertiesCopy, sounds: soundsCopy, rules: state.rules.concat([]), names: state.names.concat([]), winconditions: winConditionsCopy, original_case_names : original_case_namesCopy, abbrevNames: state.abbrevNames.concat([]), metadata : state.metadata.concat([]), sprite_size : state.sprite_size, case_sensitive : state.case_sensitive, levels: levelsCopy, STRIDE_OBJ : state.STRIDE_OBJ, STRIDE_MOV : state.STRIDE_MOV }; return nstate; }, blankLine: function(state) { if (state.section === 'levels') { if (state.levels[state.levels.length - 1].length > 0) { state.levels.push([]); } } }, token: function(stream, state) { var mixedCase = stream.string; var sol = stream.sol(); if (sol) { if(!state.case_sensitive) { stream.string = stream.string.toLowerCase(); } state.tokenIndex=0; /* if (state.lineNumber==undefined) { state.lineNumber=1; } else { state.lineNumber++; }*/ } function registerOriginalCaseName(candname){ function escapeRegExp(str) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); } var nameFinder; if(state.case_sensitive) { nameFinder = new RegExp("\\b"+escapeRegExp(candname)+"\\b"); } else { nameFinder = new RegExp("\\b"+escapeRegExp(candname)+"\\b","i"); } var match = mixedCase.match(nameFinder); if (match!=null){ state.original_case_names[candname] = match[0]; } } stream.eatWhile(/[ \t]/); //////////////////////////////// // COMMENT PROCESSING BEGIN //////////////////////////////// //NESTED COMMENTS var ch = stream.peek(); if (ch === '(' && state.tokenIndex !== -4) { // tokenIndex -4 indicates message command stream.next(); state.commentLevel++; } else if (ch === ')') { stream.next(); if (state.commentLevel > 0) { state.commentLevel--; if (state.commentLevel === 0) { return 'comment'; } } } if (state.commentLevel > 0) { while (true) { stream.eatWhile(/[^\(\)]+/); if (stream.eol()) { break; } ch = stream.peek(); if (ch === '(') { state.commentLevel++; } else if (ch === ')') { state.commentLevel--; } stream.next(); if (state.commentLevel === 0) { break; } } return 'comment'; } stream.eatWhile(/[ \t]/); if (sol && stream.eol()) { return blankLineHandle(state); } // if (sol) { //MATCH '==="s AT START OF LINE if (sol && stream.match(reg_equalsrow, true)) { return 'EQUALSBIT'; } //MATCH SECTION NAME if (sol && stream.match(reg_sectionNames, true)) { state.section = stream.string.slice(0, stream.pos).trim().toLowerCase(); if (state.visitedSections.indexOf(state.section) >= 0) { logError('cannot duplicate sections (you tried to duplicate \"' + state.section + '").', state.lineNumber); } state.visitedSections.push(state.section); var sectionIndex = sectionNames.indexOf(state.section); if (sectionIndex == 0) { state.objects_section = 0; if (state.visitedSections.length > 1) { logError('section "' + state.section + '" must be the first section', state.lineNumber); } } else if (state.visitedSections.indexOf(sectionNames[sectionIndex - 1]) == -1) { if (sectionIndex===-1) { logError('no such section as "' + state.section + '".', state.lineNumber); } else { logError('section "' + state.section + '" is out of order, must follow "' + sectionNames[sectionIndex - 1] + '".', state.lineNumber); } } if (state.section === 'sounds') { //populate names from rules for (var n in state.objects) { if (state.objects.hasOwnProperty(n)) { /* if (state.names.indexOf(n)!==-1) { logError('Object "'+n+'" has been declared to be multiple different things',state.objects[n].lineNumber); }*/ state.names.push(n); } } //populate names from legends for (var i = 0; i < state.legend_synonyms.length; i++) { var n = state.legend_synonyms[i][0]; /* if (state.names.indexOf(n)!==-1) { logError('Object "'+n+'" has been declared to be multiple different things',state.legend_synonyms[i].lineNumber); } */ state.names.push(n); } for (var i = 0; i < state.legend_aggregates.length; i++) { var n = state.legend_aggregates[i][0]; /* if (state.names.indexOf(n)!==-1) { logError('Object "'+n+'" has been declared to be multiple different things',state.legend_aggregates[i].lineNumber); } */ state.names.push(n); } for (var i = 0; i < state.legend_properties.length; i++) { var n = state.legend_properties[i][0]; /* if (state.names.indexOf(n)!==-1) { logError('Object "'+n+'" has been declared to be multiple different things',state.legend_properties[i].lineNumber); } */ state.names.push(n); } } else if (state.section === 'levels') { //populate character abbreviations for (var n in state.objects) { if (state.objects.hasOwnProperty(n) && n.length == 1) { state.abbrevNames.push(n); } } for (var i = 0; i < state.legend_synonyms.length; i++) { if (state.legend_synonyms[i][0].length == 1) { state.abbrevNames.push(state.legend_synonyms[i][0]); } } for (var i = 0; i < state.legend_aggregates.length; i++) { if (state.legend_aggregates[i][0].length == 1) { state.abbrevNames.push(state.legend_aggregates[i][0]); } } } return 'HEADER'; } else { if (state.section === undefined) { logError('must start with section "OBJECTS"', state.lineNumber); } } if (stream.eol()) { return null; } //if color is set, try to set matrix //if can't set matrix, try to parse name //if color is not set, try to parse color switch (state.section) { case 'objects': { var tryParseName = function() { //LOOK FOR NAME var match_name = sol ? stream.match(reg_name, true) : stream.match(/[^\p{Z}\s\()]+[\p{Z}\s]*/u,true); if (match_name == null) { stream.match(reg_notcommentstart, true); if (stream.pos>0){ logWarning('Unknown junk in object section (possibly: the main names for objects have to be words containing only the letters a-z0.9 - if you want to call them something like ",", do it in the legend section).',state.lineNumber); } return 'ERROR'; } else { var candname = match_name[0].trim(); if (state.objects[candname] !== undefined) { logError('Object "' + candname + '" defined multiple times.', state.lineNumber); return 'ERROR'; } for (var i=0;i=0) { logWarning('You named an object "' + candname + '", but this is a keyword. Don\'t do that!', state.lineNumber); } if (sol) { state.objects_candname = candname; registerOriginalCaseName(candname); state.objects[state.objects_candname] = { lineNumber: state.lineNumber, colors: [], spritematrix: [], cloneSprite: "" }; } else { //console.log(candname +" == "+ state.objects_candname); if (candname.substring(0, 5) == "copy:" && candname.length > 5) { var cloneName = candname.substring(5); if (state.objects[state.objects_candname].cloneSprite != "") { logError("You already assigned a sprite parent for " + cloneName + ", you can't have more than one!", state.lineNumber); return 'ERROR'; } else if (cloneName == state.objects_candname) { logError("You attempted to set the sprite parent for " + cloneName + " to " + cloneName + "! Please don't, and keep the recursion in check.", state.lineNumber) return 'ERROR'; } else { state.objects[state.objects_candname].cloneSprite = cloneName; //state.objects_section = 1; return "SPRITEPARENT"; } } //set up alias registerOriginalCaseName(candname); var synonym = [candname,state.objects_candname]; synonym.lineNumber = state.lineNumber; state.legend_synonyms.push(synonym); } if(state.case_sensitive) { if((candname.toLowerCase() == "player" && candname != "player") || (candname.toLowerCase() == "background" && candname != "background")) { // setup aliases for special objects var synonym = [candname.toLowerCase(),state.objects_candname]; synonym.lineNumber = state.lineNumber; state.legend_synonyms.push(synonym); } } state.objects_section = 1; return 'NAME'; } }; if (sol && state.objects_section == 2) { state.objects_section = 3; } if (sol && state.objects_section == 1) { state.objects_section = 2; } switch (state.objects_section) { case 0: case 1: { state.objects_spritematrix = []; return tryParseName(); break; } case 2: { //LOOK FOR COLOR stream.string = stream.string.toLowerCase(); state.tokenIndex = 0; var match_color = stream.match(reg_color, true); if (match_color == null) { var str = stream.match(reg_name, true) || stream.match(reg_notcommentstart, true); logError('Was looking for color for object ' + state.objects_candname + ', got "' + str + '" instead.', state.lineNumber); return null; } else { if (state.objects[state.objects_candname].colors === undefined) { state.objects[state.objects_candname].colors = [match_color[0].trim()]; } else { state.objects[state.objects_candname].colors.push(match_color[0].trim()); } var candcol = match_color[0].trim().toLowerCase(); if (candcol in colorPalettes.arnecolors) { return 'COLOR COLOR-' + candcol.toUpperCase(); } else if (candcol==="transparent") { return 'COLOR FADECOLOR'; } else { return 'MULTICOLOR'+match_color[0]; } } break; } case 3: { var ch = stream.eat(/[.\d]/); var spritematrix = state.objects_spritematrix; if (ch === undefined) { if (spritematrix.length === 0) { return tryParseName(); } logError('Unknown junk in spritematrix for object ' + state.objects_candname + '.', state.lineNumber); stream.match(reg_notcommentstart, true); return null; } if (sol) { spritematrix.push(''); } var o = state.objects[state.objects_candname]; spritematrix[spritematrix.length - 1] += ch; if (spritematrix[spritematrix.length-1].length>state.sprite_size){ logError('Sprites must be ' + state.sprite_size + ' wide and ' + state.sprite_size + ' high.', state.lineNumber); stream.match(reg_notcommentstart, true); return null; } o.spritematrix = state.objects_spritematrix; if (spritematrix.length === state.sprite_size && spritematrix[spritematrix.length - 1].length == state.sprite_size) { state.objects_section = 0; } if (ch!=='.') { var n = parseInt(ch); if (n>=o.colors.length) { logError("Trying to access color number "+n+" from the color palette of sprite " +state.objects_candname+", but there are only "+o.colors.length+" defined in it.",state.lineNumber); return 'ERROR'; } if (isNaN(n)) { logError('Invalid character "' + ch + '" in sprite for ' + state.objects_candname, state.lineNumber); return 'ERROR'; } return 'COLOR BOLDCOLOR COLOR-' + o.colors[n].toUpperCase(); } return 'COLOR FADECOLOR'; } default: { window.console.logError("EEK shouldn't get here."); } } break; } case 'sounds': { if (sol) { var ok = true; var splits = reg_notcommentstart.exec(stream.string)[0].split(/[\p{Z}\s]/u).filter(function(v) {return v !== ''}); splits.push(state.lineNumber); state.sounds.push(splits); } candname = stream.match(reg_soundverbs, true); if (candname!==null) { return 'SOUNDVERB'; } candname = stream.match(reg_sounddirectionindicators,true); if (candname!==null) { return 'DIRECTION'; } candname = stream.match(reg_soundseed, true); if (candname !== null) { state.tokenIndex++; return 'SOUND'; } candname = stream.match(/[^\[\|\]\p{Z}\s]*/u, true); if (candname!== null ) { var m = candname[0].trim(); if (state.names.indexOf(m)>=0) { return 'NAME'; } } else { //can we ever get here? candname = stream.match(reg_notcommentstart, true); } logError('unexpected sound token "'+candname+'".' , state.lineNumber); stream.match(reg_notcommentstart, true); return 'ERROR'; break; } case 'collisionlayers': { if (sol) { //create new collision layer state.collisionLayers.push([]); state.tokenIndex=0; } var match_name = stream.match(reg_name, true); if (match_name === null) { //then strip spaces and commas var prepos=stream.pos; stream.match(reg_csv_separators, true); if (stream.pos==prepos) { logError("error detected - unexpected character " + stream.peek(),state.lineNumber); stream.next(); } return null; } else { //have a name: let's see if it's valid var candname = match_name[0].trim(); var substitutor = function(n) { if(!state.case_sensitive) { n = n.toLowerCase(); } if (n in state.objects) { return [n]; } for (var i=0;i0&&state.collisionLayers[state.collisionLayers.length-1].length>0) { logError("Background must be in a layer by itself.",state.lineNumber); } state.tokenIndex=1; } else if (state.tokenIndex!==0) { logError("Background must be in a layer by itself.",state.lineNumber); } var ar = substitutor(candname); if (state.collisionLayers.length===0) { logError("no layers found.",state.lineNumber); return 'ERROR'; } var foundOthers=[]; for (var i=0;i=0){ if (j!=state.collisionLayers.length-1){ foundOthers.push(j); } } } } if (foundOthers.length>0){ var warningStr = 'Object "'+candname+'" included in multiple collision layers ( layers '; for (var i=0;i0) { return 'NAME'; } else { return 'ERROR'; } } break; } case 'legend': { if (sol) { //step 1 : verify format var longer = stream.string.replace('=', ' = '); longer = reg_notcommentstart.exec(longer)[0]; var splits = longer.split(/[\p{Z}\s]/u).filter(function(v) { return v !== ''; }); var ok = true; if (splits.length>0) { var candname = splits[0]; if(!state.case_sensitive) { candname = candname.toLowerCase(); } if (keyword_array.indexOf(candname)>=0) { logWarning('You named an object "' + candname + '", but this is a keyword. Don\'t do that!', state.lineNumber); } if (splits.indexOf(candname, 2)>=2) { logError("You can't define object " + candname.toUpperCase() + " in terms of itself!", state.lineNumber); // ok = false; } checkNameNew(state,candname); } if (!ok) { } else if (splits.length < 3) { ok = false; } else if (splits[1] !== '=') { ok = false; } /*else if (splits[0].charAt(splits[0].length - 1) == 'v') { logError('names cannot end with the letter "v", because it\'s is used as a direction.', state.lineNumber); stream.match(reg_notcommentstart, true); return 'ERROR'; } */ else if (splits.length === 3) { var synonym = [splits[0], splits[2]]; if(!state.case_sensitive) { synonym[1] = synonym[1].toLowerCase(); } synonym.lineNumber = state.lineNumber; registerOriginalCaseName(splits[0]); state.legend_synonyms.push(synonym); } else if (splits.length % 2 === 0) { ok = false; } else { var lowertoken = splits[3].toLowerCase(); if (lowertoken === 'and') { var substitutor = function(n) { if(!state.case_sensitive) { n = n.toLowerCase(); } if (n in state.objects) { return [n]; } for (var i=0;i[\p{Z}\s]*/u, true)) { return 'ARROW'; } if (ch === '[' || ch === '|' || ch === ']' || ch==='+') { if (ch!=='+') { state.tokenIndex = 1; } stream.next(); stream.match(/[\p{Z}\s]*/u, true); return 'BRACKET'; } else { var m = stream.match(/[^\[\|\]\p{Z}\s]*/u, true)[0].trim(); if (state.tokenIndex===0&®_loopmarker.exec(m)) { return 'BRACKET'; } else if (state.tokenIndex === 0 && reg_ruledirectionindicators.exec(m)) { stream.match(/[\p{Z}\s]*/u, true); return 'DIRECTION'; } else if (state.tokenIndex === 1 && reg_directions.exec(m)) { stream.match(/[\p{Z}\s]*/u, true); return 'DIRECTION'; } else { if (state.names.indexOf(m) >= 0) { if (sol) { logError('Identifiers cannot appear outside of square brackets in rules, only directions can.', state.lineNumber); return 'ERROR'; } else { stream.match(/[\p{Z}\s]*/u, true); return 'NAME'; } } m = m.toLowerCase(); if (m==='...') { return 'DIRECTION'; } else if (m==='rigid') { return 'DIRECTION'; } else if (m==='random') { return 'DIRECTION'; } else if (m==='global') { return 'DIRECTION'; }else if (commandwords.indexOf(m)>=0) { if (m==='message' || m==='goto' || twiddleable_params.includes(m)) { state.tokenIndex=-4; } return 'COMMAND'; } else { logError('Name "' + m + '", referred to in a rule, does not exist.', state.lineNumber); return 'ERROR'; } } } break; } case 'winconditions': { if (sol) { var tokenized = reg_notcommentstart.exec(stream.string); var splitted = tokenized[0].split(/[\p{Z}\s]/u); var filtered = splitted.filter(function(v) {return v !== ''}); filtered.push(state.lineNumber); state.winconditions.push(filtered); state.tokenIndex = -1; } state.tokenIndex++; var match = stream.match(/[\p{Z}\s]*[\p{L}\p{N}_]+[\p{Z}\s]*/u); if (match === null) { logError('incorrect format of win condition.', state.lineNumber); stream.match(reg_notcommentstart, true); return 'ERROR'; } else { var candword = match[0].trim(); if (state.tokenIndex === 0) { if (reg_winconditionquantifiers.exec(candword)) { return 'LOGICWORD'; } else { return 'ERROR'; } } else if (state.tokenIndex === 2) { if (candword != 'on') { logError('Expecting the word "ON" but got "'+candword.toUpperCase()+"'.", state.lineNumber); return 'ERROR'; } else { return 'LOGICWORD'; } } else if (state.tokenIndex === 1 || state.tokenIndex === 3) { if (state.names.indexOf(candword)===-1) { logError('Error in win condition: "' + candword + '" is not a valid object name.', state.lineNumber); return 'ERROR'; } else { return 'NAME'; } } } break; } case 'levels': { if (sol) { if (stream.match(/[\p{Z}\s]*message\b[\p{Z}\s]*/u, true)) { state.tokenIndex = 1;//1/2/3/4 = message/level/section/goto var newdat = ['message', mixedCase.slice(stream.pos).trim(), state.lineNumber, state.currentSection]; if (state.levels[state.levels.length - 1].length == 0) { state.levels.splice(state.levels.length - 1, 0, newdat); } else { state.levels.push(newdat); } return 'MESSAGE_VERB';//a duplicate of the previous section as a legacy thing for #589 } else if (stream.match(/[\p{Z}\s]*message[\p{Z}\s]*/u, true)) {//duplicating previous section because of #589 logWarning("You probably meant to put a space after 'message' innit. That's ok, I'll still interpret it as a message, but you probably want to put a space there.",state.lineNumber); state.tokenIndex = 1;//1/2/3/4 = message/level/section/goto var newdat = ['message', mixedCase.slice(stream.pos).trim(), state.lineNumber, state.currentSection]; if (state.levels[state.levels.length - 1].length == 0) { state.levels.splice(state.levels.length - 1, 0, newdat); } else { state.levels.push(newdat); } return 'MESSAGE_VERB'; } else if (stream.match(/\s*section\s*/i, true)) { state.tokenIndex = 3;//1/2/3/4 = message/level/section/goto state.currentSection = mixedCase.slice(stream.pos).trim(); return 'SECTION_VERB'; } else if (stream.match(/\s*goto\s*/i, true)) { state.tokenIndex = 4;//1/2/3/4 = message/level/section/goto var newdat = ['goto', mixedCase.slice(stream.pos).trim(), state.lineNumber, state.currentSection]; if (state.levels[state.levels.length - 1].length == 0) { state.levels.splice(state.levels.length - 1, 0, newdat); } else { state.levels.push(newdat); } return 'GOTO_VERB'; } else { var line = stream.match(reg_notcommentstart, false)[0].trim(); state.tokenIndex = 2; var lastlevel = state.levels[state.levels.length - 1]; if (lastlevel[0] == '\n') { state.levels.push([state.lineNumber, state.currentSection, line]); } else { if (lastlevel.length==0) { lastlevel.push(state.lineNumber); lastlevel.push(state.currentSection); } lastlevel.push(line); if (lastlevel.length>2) { if (line.length!=lastlevel[2].length) { logWarning("Maps must be rectangular, yo (In a level, the length of each row must be the same).",state.lineNumber); } } } } } else { if (state.tokenIndex == 1) { stream.skipToEnd(); return 'MESSAGE'; } else if (state.tokenIndex == 3) { stream.skipToEnd(); return 'SECTION'; } else if (state.tokenIndex == 4) { stream.skipToEnd(); return 'GOTO'; } } if (state.tokenIndex === 2 && !stream.eol()) { var ch = stream.peek(); stream.next(); if (state.abbrevNames.indexOf(ch) >= 0) { return 'LEVEL'; } else { logError('Key "' + ch + '" not found. Do you need to add it to the legend, or define a new object?', state.lineNumber); return 'ERROR'; } } break; } default://if you're in the preamble { if (sol) { state.tokenIndex=0; } if (state.tokenIndex==0) { var match = stream.match(/[\p{Z}\s]*[\p{L}\p{N}_]+[\p{Z}\s]*/u); if (match!==null) { var token = match[0].trim(); if (sol) { if (preamble_params.indexOf(token)>=0) { if (token==='youtube' || token==='author' || token==='homepage' || token==='title' || token==='custom_font' || token==='text_controls' || token==='text_message_continue') { stream.string=mixedCase; } var m2 = stream.match(reg_notcommentstart, false); if(m2!=null) { if(token=='sprite_size') { state.sprite_size = parseInt(m2[0].trim(), 10); } else { state.metadata.push(token); state.metadata.push(m2[0].trim()); } } else { logError('MetaData "'+token+'" needs a value.',state.lineNumber); } state.tokenIndex=1; return 'METADATA'; } else if (preamble_keywords.indexOf(token)>=0) { if(token == 'case_sensitive') { state.case_sensitive = true; } state.metadata.push(token); state.metadata.push("true"); state.tokenIndex=-1; return 'METADATA'; } else { logError('Unrecognised stuff in the prelude.', state.lineNumber); return 'ERROR'; } } else if (state.tokenIndex==-1) { logError('MetaData "'+token+'" has no parameters.',state.lineNumber); return 'ERROR'; } return 'METADATA'; } } else { stream.match(reg_notcommentstart, true); return "METADATATEXT"; } break; } } }; if (stream.eol()) { return null; } if (!stream.eol()) { stream.next(); return null; } }, startState: function() { return { /* permanently useful */ objects: {}, /* for parsing */ lineNumber: 0, commentLevel: 0, section: '', visitedSections: [], objects_candname: '', objects_section: 0, //whether reading name/color/spritematrix objects_spritematrix: [], collisionLayers: [], tokenIndex: 0, currentSection: null, legend_synonyms: [], legend_aggregates: [], legend_properties: [], sounds: [], rules: [], names: [], winconditions: [], metadata: [], sprite_size: 5, case_sensitive: false, original_case_names: {}, abbrevNames: [], levels: [[]], subsection: '' }; } }; }; window.CodeMirror.defineMode('puzzle', codeMirrorFn);