Skip to content

Commit f9745ab

Browse files
authored
bugfix #107: Fix and improve expression parsing (#108)
* Remove expression detection from integer parsing * Make values starting with parentheses an expression type * Remove detection of expression type by tokens ahead * Look ahead of initally collected variable value to detect expression * Move making math value action to separate function * Prevent non-expression types from being used in expressions * Refactor parsing expressions * Fix wrapping references in expressions with parentheses * Improve expression parsing and wrap parsed references * Detect comments after variable values
1 parent 1ead5f7 commit f9745ab

File tree

4 files changed

+92
-54
lines changed

4 files changed

+92
-54
lines changed

action.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -831,7 +831,7 @@ func collectVersionDefinition() (minVersion float64, maxVersion float64) {
831831
advance()
832832
var valueType tokenType
833833
var version any
834-
collectIntegerValue(&valueType, &version, ' ')
834+
collectIntegerValue(&valueType, &version)
835835

836836
switch char {
837837
case '>':

parser.go

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ func parse() {
188188
switch {
189189
case char == ' ' || char == '\t' || char == '\n':
190190
advance()
191+
case isChar('/'):
192+
collectComment()
191193
case tokenAhead(Question):
192194
collectQuestion()
193195
case tokenAhead(Definition):
@@ -199,8 +201,6 @@ func parse() {
199201
case tokenAhead(Constant):
200202
advance()
201203
collectVariable(true)
202-
case isChar('/'):
203-
collectComment()
204204
case tokenAhead(Repeat):
205205
collectRepeat()
206206
case tokenAhead(RepeatWithEach):
@@ -293,12 +293,52 @@ func lookAheadUntil(until rune) string {
293293

294294
func collectVariableValue(constant bool, valueType *tokenType, value *any) {
295295
collectValue(valueType, value, '\n')
296+
if *valueType == Question {
297+
parserError(fmt.Sprintf("Illegal reference to import question '%s'. Shortcuts does not support import questions as variable values.", *value))
298+
}
296299

300+
var aheadOfValue = lookAheadUntil('\n')
301+
if strings.Contains(aheadOfValue, "//") || strings.Contains(aheadOfValue, "/*") {
302+
return
303+
}
304+
if containsExpressionTokens(aheadOfValue) {
305+
collectExpression(valueType, value)
306+
return
307+
}
297308
if constant && (*valueType == Arr || *valueType == Variable) {
298309
parserError(fmt.Sprintf("Type %v values cannot be constants.", *valueType))
299310
}
300-
if *valueType == Question {
301-
parserError(fmt.Sprintf("Illegal reference to import question '%s'. Shortcuts does not support import questions as variable values.", *value))
311+
}
312+
313+
func collectExpression(valueType *tokenType, value *any) {
314+
if !slices.Contains([]tokenType{Integer, Float, Variable}, *valueType) {
315+
parserError(fmt.Sprintf("Value of type '%s' not allowed in expression", *valueType))
316+
}
317+
if *valueType == Variable {
318+
var valueRef = *value
319+
*value = fmt.Sprintf("{%s}", valueRef.(varValue).value)
320+
} else {
321+
*value = fmt.Sprintf("%v", *value)
322+
}
323+
*valueType = Expression
324+
325+
for char != -1 && char != '\n' {
326+
switch {
327+
case char == ' ' || char == '+' || char == '-' || char == '*' || char == '/' || char == '%' || char == '(' || char == ')':
328+
*value = fmt.Sprintf("%s%c", *value, char)
329+
advance()
330+
case intChar(char):
331+
var intValueType tokenType
332+
var intValue any
333+
collectIntegerValue(&intValueType, &intValue)
334+
*value = fmt.Sprintf("%s%v", *value, intValue)
335+
default:
336+
var until = ' '
337+
var refType tokenType
338+
var refValue any
339+
collectReference(&refType, &refValue, &until)
340+
*value = fmt.Sprintf("%s{%s}", *value, refValue.(varValue).value)
341+
}
302342
}
303343
}
304344

@@ -313,7 +353,7 @@ func collectValue(valueType *tokenType, value *any, until rune) {
313353
if strings.Contains(ahead, ".") {
314354
*valueType = Float
315355
}
316-
collectIntegerValue(valueType, value, until)
356+
collectIntegerValue(valueType, value)
317357
case char == '"':
318358
collectStringValue(valueType, value)
319359
case char == '\'':
@@ -328,6 +368,10 @@ func collectValue(valueType *tokenType, value *any, until rune) {
328368
advance()
329369
*valueType = Dict
330370
*value = collectDictionary()
371+
case char == '(':
372+
*valueType = Integer
373+
*value = ""
374+
collectExpression(valueType, value)
331375
case tokenAhead(True):
332376
*valueType = Bool
333377
*value = true
@@ -339,9 +383,6 @@ func collectValue(valueType *tokenType, value *any, until rune) {
339383
advanceUntil(until)
340384
case strings.Contains(ahead, "("):
341385
collectActionValue(valueType, value)
342-
case containsTokens(&ahead, Plus, Minus, Multiply, Divide, Modulus):
343-
*valueType = Expression
344-
*value = collectUntil(until)
345386
default:
346387
collectReference(valueType, value, &until)
347388
}
@@ -1295,26 +1336,19 @@ func collectInteger() string {
12951336
return collection.String()
12961337
}
12971338

1298-
func collectIntegerValue(valueType *tokenType, value *any, until rune) {
1299-
var ahead = lookAheadUntil(until)
1300-
if !containsTokens(&ahead, Plus, Minus, Multiply, Divide, Modulus) {
1301-
var integerString = collectInteger()
1339+
func collectIntegerValue(valueType *tokenType, value *any) {
1340+
var integerString = collectInteger()
1341+
if *valueType == Integer {
1342+
var integer, convErr = strconv.Atoi(integerString)
1343+
handle(convErr)
13021344

1303-
if *valueType == Integer {
1304-
var integer, convErr = strconv.Atoi(integerString)
1305-
handle(convErr)
1306-
1307-
*value = integer
1308-
} else {
1309-
var float, floatErr = strconv.ParseFloat(integerString, 64)
1310-
handle(floatErr)
1345+
*value = integer
1346+
} else {
1347+
var float, floatErr = strconv.ParseFloat(integerString, 64)
1348+
handle(floatErr)
13111349

1312-
*value = float
1313-
}
1314-
return
1350+
*value = float
13151351
}
1316-
*valueType = Expression
1317-
*value = collectUntil(until)
13181352
}
13191353

13201354
func collectString() string {
@@ -1552,6 +1586,10 @@ func tokenAhead(token tokenType) bool {
15521586
return true
15531587
}
15541588

1589+
func containsExpressionTokens(str string) bool {
1590+
return containsTokens(&str, Plus, Minus, Multiply, Divide, Modulus, LeftParen, RightParen)
1591+
}
1592+
15551593
func containsTokens(str *string, v ...tokenType) bool {
15561594
for _, aheadToken := range v {
15571595
if strings.Contains(*str, string(aheadToken)) {

shortcutgen.go

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -252,45 +252,43 @@ func makeBoolValue(reference *map[string]any, value *any) {
252252
}, reference))
253253
}
254254

255-
var formattedExpression []string
256-
257255
func makeExpressionValue(reference *map[string]any, value *any) {
258-
formattedExpression = []string{}
259256
var expression = fmt.Sprintf("%s", *value)
260257
var expressionParts = strings.Split(expression, " ")
261258
if len(expressionParts) == 3 && containsTokens(&expression, Plus, Minus, Multiply, Divide) {
262-
var operandOne string
263-
var operandTwo string
259+
makeMathValue(reference, expression, expressionParts)
260+
return
261+
}
264262

265-
var operation = expressionParts[1]
266-
expressionParts = strings.Split(expression, operation)
267-
operandOne = strings.Trim(expressionParts[0], " ")
268-
operandTwo = strings.Trim(expressionParts[1], " ")
269-
wrapVariableReference(&operandOne)
270-
wrapVariableReference(&operandTwo)
263+
addStdAction("calculateexpression", attachReferenceToParams(&map[string]any{
264+
"Input": attachmentValues(expression),
265+
}, reference))
266+
}
271267

272-
addStdAction("math", attachReferenceToParams(&map[string]any{
273-
"WFScientificMathOperation": attachmentValues(operation),
274-
"WFMathOperation": attachmentValues(operation),
275-
"WFInput": attachmentValues(operandOne),
276-
"WFMathOperand": attachmentValues(operandTwo),
277-
}, reference))
268+
func makeMathValue(reference *map[string]any, expression string, expressionParts []string) {
269+
var operandOne string
270+
var operandTwo string
278271

279-
return
280-
}
272+
var operation = expressionParts[1]
273+
expressionParts = strings.Split(expression, operation)
281274

282-
expressionParts = strings.Split(expression, " ")
283-
for _, part := range expressionParts {
284-
var p = part
285-
wrapVariableReference(&p)
286-
formattedExpression = append(formattedExpression, p)
287-
}
275+
operandOne = strings.Trim(expressionParts[0], " ")
276+
operandTwo = strings.Trim(expressionParts[1], " ")
288277

289-
expression = strings.Join(formattedExpression, " ")
278+
switch operation {
279+
case "*":
280+
operation = "×"
281+
case "/":
282+
operation = "÷"
283+
}
290284

291-
addStdAction("calculateexpression", attachReferenceToParams(&map[string]any{
292-
"Input": attachmentValues(expression),
285+
addStdAction("math", attachReferenceToParams(&map[string]any{
286+
"WFMathOperation": operation,
287+
"WFInput": attachmentValues(operandOne),
288+
"WFMathOperand": attachmentValues(operandTwo),
293289
}, reference))
290+
291+
return
294292
}
295293

296294
func makeDictionaryValue(value *any) map[string]any {

token.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ const (
103103
Multiply tokenType = "*"
104104
Divide tokenType = "/"
105105
Modulus tokenType = "%"
106+
LeftParen tokenType = "("
107+
RightParen tokenType = ")"
106108
LeftBrace tokenType = "{"
107109
RightBrace tokenType = "}"
108110
Colon tokenType = ":"

0 commit comments

Comments
 (0)