Skip to content

Commit

Permalink
Merge pull request #392 from Mitcheljager/feat/parameter-objects
Browse files Browse the repository at this point in the history
feat(editor): Parameter Objects
  • Loading branch information
Mitcheljager authored Dec 23, 2023
2 parents 5a84f34 + 249048d commit 955b3d8
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 11 deletions.
5 changes: 5 additions & 0 deletions app/assets/stylesheets/elements/_codemirror.scss
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@
background: transparent;
}

.cm-lintRange-warning {
box-shadow: 0 3px 0 $orange;
background: transparent;
}

.cm-lint-marker-error::before {
content: "!";
display: block;
Expand Down
9 changes: 6 additions & 3 deletions app/javascript/src/components/editor/Editor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@
if (v.args?.length) {
// Add detail arguments in autocomplete results
const detail = v.args.map(a => `${ toCapitalize(a.name) }`).join(", ")
const detail = v.args.map(a => `${ toCapitalize(a.name) }`)
const joinedDetail = detail.join(", ")
params.detail_full = detail
params.detail = `(${ detail.slice(0, 30) }${ detail.length > 30 ? "..." : "" })`
params.detail_full = joinedDetail
params.detail = `(${ joinedDetail.slice(0, 30) }${ joinedDetail.length > 30 ? "..." : "" })`
// Add apply values when selecting autocomplete, filling in default args
const lowercaseDefaults = Object.keys(defaults).map(k => k.toLowerCase())
Expand All @@ -93,6 +94,8 @@
return toCapitalize(string)
})
params.parameter_keys = detail
params.parameter_defaults = apply
params.apply = `${ v["en-US"] }(${ apply.join(", ") })`
// Add arguments to info box
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/src/lib/OWLanguageLegacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const octal = /^\-?0o[0-7][0-7_]*/
const hexadecimal = /^\-?0x[\dA-Fa-f][\dA-Fa-f_]*(?:(?:\.[\dA-Fa-f][\dA-Fa-f_]*)?[Pp]\-?\d[\d_]*)?/
const decimal = /^\-?\d[\d_]*(?:\.\d[\d_]*)?(?:[Ee]\-?\d[\d_]*)?/
const identifier = /^\$\d+|(`?)[_A-Za-z][_A-Za-z$0-9]*\1/
const actionsValuesIdentifier = /^\s*(?=[A-Z])\b[A-Z][\w\s-]*/g
const actionsValuesIdentifier = /^\s*(?=[A-Z])\b[A-Z][\w\s-]*(?!:)/g
const phraseIdentifier = /^\s*(?=[A-Z]).+?(?= [+\-*%=|&<>~^?!]|[\(\)\{\}:;,./\n\[\]]|\s==)/
const customKeywords = /(?<!\w)@(?:else if|\w+)/

Expand Down
31 changes: 24 additions & 7 deletions app/javascript/src/lib/OWLanguageLinter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getClosingBracket, getPhraseFromPosition, splitArgumentsString } from "../utils/parse"
import { completionsMap, subroutinesMap, workshopConstants } from "../stores/editor"
import { get } from "svelte/store"
import { getFirstParameterObject } from "../utils/compiler/parameterObjects"

let diagnostics = []

Expand Down Expand Up @@ -103,6 +104,7 @@ function findIncorrectArgsLength(content) {
if (item.label != name) continue

let message = ""
let severity = "error"

if (item.args_unlimited) continue

Expand All @@ -126,22 +128,37 @@ function findIncorrectArgsLength(content) {

const splitContent = splitArgumentsString(argumentsString)

if (item.args_min_length && splitContent.length >= item.args_min_length && splitContent.length <= item.args_length) break
if (!item.args_min_length && splitContent.length == item.args_length) break
// Arguments string is a parameter object
if (argumentsString.trim()[0] === "{") {
const parameterObject = getFirstParameterObject(content.slice(match.index, closing + 1))

const expectedString = `${ item.args_min_length ? "Atleast" : "" } ${ item.args_min_length || item.args_length }`
const maxString = `${ item.args_min_length ? ` (${ item.args_length } max)` : "" }`
const givenString = `${ splitContent.length } given`
if (!parameterObject) break

const invalidArgument = Object.keys(parameterObject.given).filter(i => i && !parameterObject.phraseParameters.includes(i))
if (invalidArgument?.length) {
message = `Argument(s) "${ invalidArgument.join(", ") }" are not valid for "${ name }"`
severity = "warning"
} else break
} else {
// Argument string is a regular list of arguments
if (item.args_min_length && splitContent.length >= item.args_min_length && splitContent.length <= item.args_length) break
if (!item.args_min_length && splitContent.length == item.args_length) break

const expectedString = `${ item.args_min_length ? "Atleast" : "" } ${ item.args_min_length || item.args_length }`
const maxString = `${ item.args_min_length ? ` (${ item.args_length } max)` : "" }`
const givenString = `${ splitContent.length } given`

message = `${ expectedString } Argument(s) expected${ maxString }, ${ givenString }`
}

message = `${ expectedString } Argument(s) expected${ maxString }, ${ givenString }`
}

if (!message) break

diagnostics.push({
from: match.index + match.match.length - name.length,
to: match.index + match.match.length,
severity: "error",
severity,
message: message
})

Expand Down
2 changes: 2 additions & 0 deletions app/javascript/src/utils/compiler/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { removeComments } from "./comments"
import { evaluateConditionals } from "./conditionals"
import { evaluateEachLoops } from "./each"
import { evaluateForLoops } from "./for"
import { evaluateParameterObjects } from "./parameterObjects"
import { extractAndInsertMixins } from "./mixins"
import { compileSubroutines } from "./subroutines"
import { convertTranslations } from "./translations"
Expand All @@ -23,6 +24,7 @@ export function compile(overwriteContent = null) {

joinedItems = joinedItems.replace(settings, "")
joinedItems = extractAndInsertMixins(joinedItems)
joinedItems = evaluateParameterObjects(joinedItems)
joinedItems = evaluateForLoops(joinedItems)
joinedItems = evaluateEachLoops(joinedItems)
joinedItems = evaluateConditionals(joinedItems)
Expand Down
90 changes: 90 additions & 0 deletions app/javascript/src/utils/compiler/parameterObjects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { completionsMap } from "../../stores/editor"
import { getClosingBracket, getPhraseFromIndex, replaceBetween, splitArgumentsString } from "../parse"
import { get } from "svelte/store"

export function evaluateParameterObjects(joinedItems) {
let moreAvailableObjects = true
let safety = 0
let startFromIndex = 0

while (moreAvailableObjects) {
const parameterObject = getFirstParameterObject(joinedItems, startFromIndex)

if (!parameterObject || safety > 1000) {
moreAvailableObjects = false
continue
}

if (!parameterObject.phraseParameters.length) {
startFromIndex = parameterObject.start
continue
}

joinedItems = replaceParameterObject(joinedItems, parameterObject)
safety++
}

return joinedItems
}

/**
* Find the first matching parameter object in a given string. Parameter objects are a special format that allow the user to give only specific sets of parameters rather than having to write them all out.
* @param {string} content Content to search for parameter objects in.
* @param {*} startFromIndex Skip over previous results. This is used when the regex format was found without matches phrases to skip over previous results.
* @returns {object|null} Object containing details about the parameter object and matching phrase.
*/
export function getFirstParameterObject(content, startFromIndex = 0) {
content = content.slice(startFromIndex)

const regex = /[a-z]\s*\(\s*{/
const match = content.match(regex)

if (!match) return null

let end = getClosingBracket(content, "{", "}", match.index - 1)
if (end === -1) end = match.index + match.length

const start = match.index + match[0].indexOf("{")
const phrase = getPhraseFromIndex(content, match.index)
const completion = get(completionsMap).find(item => item.args_length && item.label.replace(" ", "") === phrase.replace(" ", ""))
const string = content.slice(match.index + match[0].length, end).trim()
const splitParameters = splitArgumentsString(string)
const given = {}

splitParameters.forEach(item => {
const [key, value] = item.split(/:(.*)/s)
given[key.trim()] = (value || "").trim()
})

const result = { start: start + startFromIndex, end: end + startFromIndex, given }

// Return a false object to replace contents of unfound phrase
if (!completion) return {
...result,
phraseParameters: [],
phraseDefaults: []
}

return {
...result,
phraseParameters: completion.parameter_keys,
phraseDefaults: completion.parameter_defaults
}
}

/**
* Replaces and returns a parameter object in the given string.
* @param {string} content String the parameter object will be replaced in.
* @param {object} parameterObject Object containing all data needed to replace the expected string. This object contains a start index, end index, given parameters, parameter keys, and parameter defaults.
* @returns {string} String with parameter object replaced with Workshop code.
*/
export function replaceParameterObject(content, parameterObject) {
const { start, end, given, phraseParameters, phraseDefaults } = parameterObject

if (end > content.length - 1) return content

const parameters = phraseDefaults.map((parameterDefault, i) => given[phraseParameters[i]] || parameterDefault)
content = replaceBetween(content, parameters.join(", "), start, end + 1)

return content
}
7 changes: 7 additions & 0 deletions app/javascript/src/utils/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ export function getPhraseFromPosition(line, position) {
}
}

export function getPhraseFromIndex(text, index) {
const start = getPhraseEnd(text, index, -1)
const end = getPhraseEnd(text, index, 1)

return text.slice(start, end + 1).trim()
}

export function removeSurroundingParenthesis(source) {
const openMatch = /^[\s\n]*\(/.exec(source)
const closeMatch = /\)[\s\n]*$/.exec(source)
Expand Down
Loading

0 comments on commit 955b3d8

Please sign in to comment.