/* eslint-disable max-lines */ /* eslint-disable sort-keys */ // Maybe later /* eslint-disable complexity */ // Temporary ignore while switching linter import {flatMap, last, mergeSets} from '../core/ArrayUtilities.js'; const CM_ERROR = {type: 'error line-error', suggest: false, then: {'': 0}}; function textTo(exit, suggest = false) { return { type: 'string', suggest, then: Object.assign({'': 0}, exit), }; } function suggestionsEqual(a, b) { return ( (a.v === b.v) && (a.prefix === b.prefix) && (a.suffix === b.suffix) && (a.q === b.q) ); } const AGENT_INFO_TYPES = [ 'database', 'red', ]; const makeCommands = ((() => { /* * The order of commands inside "then" blocks directly influences the * order they are displayed to the user in autocomplete menus. * This relies on the fact that common JS engines maintain insertion * order in objects, though this is not guaranteed. It could be switched * to use Map objects instead for strict compliance, at the cost of * extra syntax. */ function agentListTo(exit, next = 1) { return { type: 'variable', suggest: {known: 'Agent'}, then: Object.assign({}, exit, { '': 0, ',': {type: 'operator', then: {'': next}}, }), }; } const end = {type: '', suggest: '\n', then: {}}; const hiddenEnd = {type: '', suggest: false, then: {}}; const textToEnd = textTo({'\n': end}); const colonTextToEnd = { type: 'operator', then: {'': textToEnd, '\n': hiddenEnd}, }; const aliasListToEnd = agentListTo({ '\n': end, 'as': {type: 'keyword', then: { '': {type: 'variable', suggest: {known: 'Agent'}, then: { '': 0, ',': {type: 'operator', then: {'': 3}}, '\n': end, }}, }}, }); const agentListToText = agentListTo({':': colonTextToEnd}); const agentToOptText = { type: 'variable', suggest: {known: 'Agent'}, then: { '': 0, ':': {type: 'operator', then: { '': textToEnd, '\n': hiddenEnd, }}, '\n': end, }, }; const referenceName = { ':': {type: 'operator', then: { '': textTo({ 'as': {type: 'keyword', then: { '': { type: 'variable', suggest: {known: 'Agent'}, then: { '': 0, '\n': end, }, }, }}, }), }}, }; const refDef = {type: 'keyword', then: Object.assign({ 'over': {type: 'keyword', then: { '': agentListTo(referenceName), }}, }, referenceName)}; const divider = { '\n': end, ':': {type: 'operator', then: { '': textToEnd, '\n': hiddenEnd, }}, 'with': {type: 'keyword', suggest: ['with height '], then: { 'height': {type: 'keyword', then: { '': {type: 'number', suggest: ['6 ', '30 '], then: { '\n': end, ':': {type: 'operator', then: { '': textToEnd, '\n': hiddenEnd, }}, }}, }}, }}, }; function simpleList(type, keywords, exit) { const first = {}; const recur = Object.assign({}, exit); keywords.forEach((keyword) => { first[keyword] = {type, then: recur}; recur[keyword] = 0; }); return first; } function optionalKeywords(type, keywords, then) { const result = Object.assign({}, then); keywords.forEach((keyword) => { result[keyword] = {type, then}; }); return result; } const agentInfoList = optionalKeywords( 'keyword', ['a', 'an'], simpleList('keyword', AGENT_INFO_TYPES, {'\n': end}) ); function makeSideNote(side) { return { type: 'keyword', suggest: [side + ' of ', side + ': '], then: { 'of': {type: 'keyword', then: { '': agentListToText, }}, ':': {type: 'operator', then: { '': textToEnd, }}, '': agentListToText, }, }; } function makeOpBlock({exit, sourceExit, blankExit}) { const op = {type: 'operator', then: { '+': CM_ERROR, '-': CM_ERROR, '*': CM_ERROR, '!': CM_ERROR, '': exit, }}; return { '+': {type: 'operator', then: { '+': CM_ERROR, '-': CM_ERROR, '*': op, '!': CM_ERROR, '': exit, }}, '-': {type: 'operator', then: { '+': CM_ERROR, '-': CM_ERROR, '*': op, '!': {type: 'operator', then: { '+': CM_ERROR, '-': CM_ERROR, '*': CM_ERROR, '!': CM_ERROR, '': exit, }}, '': exit, }}, '*': {type: 'operator', then: Object.assign({ '+': op, '-': op, '*': CM_ERROR, '!': CM_ERROR, '': exit, }, sourceExit || exit)}, '!': op, '': blankExit || exit, }; } function makeCMConnect(arrows) { const connect = { type: 'keyword', then: Object.assign({}, makeOpBlock({ exit: agentToOptText, sourceExit: { ':': colonTextToEnd, '\n': hiddenEnd, }, }), { '...': {type: 'operator', then: { '': { type: 'variable', suggest: {known: 'DelayedAgent'}, then: { '': 0, ':': CM_ERROR, '\n': end, }, }, }}, }), }; const connectors = {}; arrows.forEach((arrow) => (connectors[arrow] = connect)); const labelIndicator = { type: 'operator', override: 'Label', then: {}, }; const hiddenLabelIndicator = { type: 'operator', suggest: false, override: 'Label', then: {}, }; const firstAgent = { type: 'variable', suggest: {known: 'Agent'}, then: Object.assign({ '': 0, }, connectors, { ':': labelIndicator, }), }; const firstAgentDelayed = { type: 'variable', suggest: {known: 'DelayedAgent'}, then: Object.assign({ '': 0, ':': hiddenLabelIndicator, }, connectors), }; const firstAgentNoFlags = Object.assign({}, firstAgent, { then: Object.assign({}, firstAgent.then, { 'is': {type: 'keyword', then: agentInfoList}, }), }); return Object.assign({ '...': {type: 'operator', then: { '': firstAgentDelayed, }}, }, makeOpBlock({ exit: firstAgent, sourceExit: Object.assign({ '': firstAgent, ':': hiddenLabelIndicator, }, connectors), blankExit: firstAgentNoFlags, })); } const commonGroup = {type: 'keyword', then: { '': textToEnd, ':': {type: 'operator', then: { '': textToEnd, }}, '\n': end, }}; const BASE_THEN = { 'title': {type: 'keyword', then: { '': textToEnd, }}, 'theme': {type: 'keyword', then: { '': { type: 'string', suggest: { global: 'themes', suffix: '\n', }, then: { '': 0, '\n': end, }, }, }}, 'headers': {type: 'keyword', then: { 'none': {type: 'keyword', then: {}}, 'cross': {type: 'keyword', then: {}}, 'box': {type: 'keyword', then: {}}, 'fade': {type: 'keyword', then: {}}, 'bar': {type: 'keyword', then: {}}, }}, 'terminators': {type: 'keyword', then: { 'none': {type: 'keyword', then: {}}, 'cross': {type: 'keyword', then: {}}, 'box': {type: 'keyword', then: {}}, 'fade': {type: 'keyword', then: {}}, 'bar': {type: 'keyword', then: {}}, }}, 'divider': {type: 'keyword', then: Object.assign({ 'line': {type: 'keyword', then: divider}, 'space': {type: 'keyword', then: divider}, 'delay': {type: 'keyword', then: divider}, 'tear': {type: 'keyword', then: divider}, }, divider)}, 'define': {type: 'keyword', then: { '': aliasListToEnd, 'as': CM_ERROR, }}, 'begin': {type: 'keyword', then: { '': aliasListToEnd, 'reference': refDef, 'as': CM_ERROR, }}, 'end': {type: 'keyword', then: { '': aliasListToEnd, 'as': CM_ERROR, '\n': end, }}, 'if': commonGroup, 'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: { 'if': {type: 'keyword', suggest: 'if: ', then: { '': textToEnd, ':': {type: 'operator', then: { '': textToEnd, }}, }}, '\n': end, }}, 'repeat': commonGroup, 'group': commonGroup, 'note': {type: 'keyword', then: { 'over': {type: 'keyword', then: { '': agentListToText, }}, 'left': makeSideNote('left'), 'right': makeSideNote('right'), 'between': {type: 'keyword', then: { '': agentListTo({':': CM_ERROR}, agentListToText), }}, }}, 'state': {type: 'keyword', suggest: 'state over ', then: { 'over': {type: 'keyword', then: { '': { type: 'variable', suggest: {known: 'Agent'}, then: { '': 0, ',': CM_ERROR, ':': colonTextToEnd, }, }, }}, }}, 'text': {type: 'keyword', then: { 'left': makeSideNote('left'), 'right': makeSideNote('right'), }}, 'autolabel': {type: 'keyword', then: { 'off': {type: 'keyword', then: {}}, '': textTo({'\n': end}, [ {v: '