mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-05-03 03:00:14 -04:00
rewriting the compiler to use half-expression assignment
This commit is contained in:
@@ -30,7 +30,7 @@ lexer: new Lexer()
|
||||
# Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison
|
||||
# compiler.
|
||||
exports.compile: compile: (code, options) ->
|
||||
options ||= {}
|
||||
options: or {}
|
||||
try
|
||||
(parser.parse lexer.tokenize code).compile options
|
||||
catch err
|
||||
@@ -68,7 +68,7 @@ exports.extend: (func) ->
|
||||
parser.lexer: {
|
||||
lex: ->
|
||||
token: @tokens[@pos] or [""]
|
||||
@pos += 1
|
||||
@pos: + 1
|
||||
this.yylineno: token[2]
|
||||
this.yytext: token[1]
|
||||
token[0]
|
||||
|
||||
@@ -94,7 +94,7 @@ compile_stdio: ->
|
||||
code: ''
|
||||
process.stdio.open()
|
||||
process.stdio.addListener 'data', (string) ->
|
||||
code += string if string
|
||||
code: + string if string
|
||||
process.stdio.addListener 'close', ->
|
||||
compile_script 'stdio', code
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ helpers.count: count: (string, letter) ->
|
||||
num: 0
|
||||
pos: string.indexOf(letter)
|
||||
while pos isnt -1
|
||||
num += 1
|
||||
num: + 1
|
||||
pos: string.indexOf(letter, pos + 1)
|
||||
num
|
||||
|
||||
@@ -60,27 +60,27 @@ helpers.del: del: (obj, key) ->
|
||||
# contents of the string. This method allows us to have strings within
|
||||
# interpolations within strings, ad infinitum.
|
||||
helpers.balanced_string: balanced_string: (str, delimited, options) ->
|
||||
options ||= {}
|
||||
options: or {}
|
||||
slash: delimited[0][0] is '/'
|
||||
levels: []
|
||||
i: 0
|
||||
while i < str.length
|
||||
if levels.length and starts str, '\\', i
|
||||
i += 1
|
||||
i: + 1
|
||||
else
|
||||
for pair in delimited
|
||||
[open, close]: pair
|
||||
if levels.length and starts(str, close, i) and levels[levels.length - 1] is pair
|
||||
levels.pop()
|
||||
i += close.length - 1
|
||||
i += 1 unless levels.length
|
||||
i: + close.length - 1
|
||||
i: + 1 unless levels.length
|
||||
break
|
||||
else if starts str, open, i
|
||||
levels.push(pair)
|
||||
i += open.length - 1
|
||||
i: + open.length - 1
|
||||
break
|
||||
break if not levels.length or slash and starts str, '\n', i
|
||||
i += 1
|
||||
i: + 1
|
||||
if levels.length
|
||||
return false if slash
|
||||
throw new Error "SyntaxError: Unterminated ${levels.pop()[0]} starting on line ${@line + 1}"
|
||||
|
||||
@@ -99,7 +99,7 @@ exports.Lexer: class Lexer
|
||||
tag: id.toUpperCase() if not accessed and include(KEYWORDS, id)
|
||||
@identifier_error id if include RESERVED, id
|
||||
tag: 'LEADING_WHEN' if tag is 'WHEN' and include LINE_BREAK, @tag()
|
||||
@i += id.length
|
||||
@i: + id.length
|
||||
if not accessed
|
||||
tag: id: CONVERSIONS[id] if include COFFEE_ALIASES, id
|
||||
return @tag_half_assignment(tag) if @prev() and @prev()[0] is 'ASSIGN' and include HALF_ASSIGNMENTS, tag
|
||||
@@ -110,7 +110,7 @@ exports.Lexer: class Lexer
|
||||
number_token: ->
|
||||
return false unless number: @match NUMBER, 1
|
||||
@token 'NUMBER', number
|
||||
@i += number.length
|
||||
@i: + number.length
|
||||
true
|
||||
|
||||
# Matches strings, including multi-line strings. Ensures that quotation marks
|
||||
@@ -121,8 +121,8 @@ exports.Lexer: class Lexer
|
||||
@balanced_token(['"', '"'], ['${', '}']) or
|
||||
@balanced_token ["'", "'"]
|
||||
@interpolate_string string.replace(STRING_NEWLINES, " \\\n")
|
||||
@line += count string, "\n"
|
||||
@i += string.length
|
||||
@line: + count string, "\n"
|
||||
@i: + string.length
|
||||
true
|
||||
|
||||
# Matches heredocs, adjusting indentation to the correct level, as heredocs
|
||||
@@ -132,8 +132,8 @@ exports.Lexer: class Lexer
|
||||
quote: match[1].substr(0, 1)
|
||||
doc: @sanitize_heredoc match[2] or match[4], quote
|
||||
@interpolate_string "$quote$doc$quote"
|
||||
@line += count match[1], "\n"
|
||||
@i += match[1].length
|
||||
@line: + count match[1], "\n"
|
||||
@i: + match[1].length
|
||||
true
|
||||
|
||||
# Matches JavaScript interpolated directly into the source via backticks.
|
||||
@@ -141,7 +141,7 @@ exports.Lexer: class Lexer
|
||||
return false unless starts @chunk, '`'
|
||||
return false unless script: @balanced_token ['`', '`']
|
||||
@token 'JS', script.replace(JS_CLEANER, '')
|
||||
@i += script.length
|
||||
@i: + script.length
|
||||
true
|
||||
|
||||
# Matches regular expression literals. Lexing regular expressions is difficult
|
||||
@@ -152,7 +152,7 @@ exports.Lexer: class Lexer
|
||||
return false unless @chunk.match REGEX_START
|
||||
return false if include NOT_REGEX, @tag()
|
||||
return false unless regex: @balanced_token ['/', '/']
|
||||
regex += (flags: @chunk.substr(regex.length).match(REGEX_FLAGS))
|
||||
regex: + (flags: @chunk.substr(regex.length).match(REGEX_FLAGS))
|
||||
if regex.match REGEX_INTERPOLATION
|
||||
str: regex.substring(1).split('/')[0]
|
||||
str: str.replace REGEX_ESCAPE, (escaped) -> '\\' + escaped
|
||||
@@ -161,7 +161,7 @@ exports.Lexer: class Lexer
|
||||
@tokens: @tokens.concat [[',', ','], ['STRING', "'$flags'"], [')', ')'], [')', ')']]
|
||||
else
|
||||
@token 'REGEX', regex
|
||||
@i += regex.length
|
||||
@i: + regex.length
|
||||
true
|
||||
|
||||
# Matches a token in which which the passed delimiter pairs must be correctly
|
||||
@@ -173,13 +173,13 @@ exports.Lexer: class Lexer
|
||||
# so they're treated as real tokens, like any other part of the language.
|
||||
comment_token: ->
|
||||
return false unless comment: @match COMMENT, 1
|
||||
@line += (comment.match(MULTILINER) or []).length
|
||||
@line: + (comment.match(MULTILINER) or []).length
|
||||
lines: compact comment.replace(COMMENT_CLEANER, '').split(MULTILINER)
|
||||
i: @tokens.length - 1
|
||||
if @unfinished()
|
||||
i -= 1 while @tokens[i] and not include LINE_BREAK, @tokens[i][0]
|
||||
i: - 1 while @tokens[i] and not include LINE_BREAK, @tokens[i][0]
|
||||
@tokens.splice(i + 1, 0, ['COMMENT', lines, @line], ['TERMINATOR', '\n', @line])
|
||||
@i += comment.length
|
||||
@i: + comment.length
|
||||
true
|
||||
|
||||
# Matches newlines, indents, and outdents, and determines which is which.
|
||||
@@ -194,8 +194,8 @@ exports.Lexer: class Lexer
|
||||
# can close multiple indents, so we need to know how far in we happen to be.
|
||||
line_token: ->
|
||||
return false unless indent: @match MULTI_DENT, 1
|
||||
@line += indent.match(MULTILINER).length
|
||||
@i += indent.length
|
||||
@line: + indent.match(MULTILINER).length
|
||||
@i : + indent.length
|
||||
prev: @prev(2)
|
||||
size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
|
||||
next_character: @chunk.match(MULTI_DENT)[4]
|
||||
@@ -219,7 +219,7 @@ exports.Lexer: class Lexer
|
||||
while move_out > 0 and @indents.length
|
||||
last_indent: @indents.pop()
|
||||
@token 'OUTDENT', last_indent
|
||||
move_out -= last_indent
|
||||
move_out: - last_indent
|
||||
@token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR' or no_newlines
|
||||
true
|
||||
|
||||
@@ -229,7 +229,7 @@ exports.Lexer: class Lexer
|
||||
return false unless space: @match WHITESPACE, 1
|
||||
prev: @prev()
|
||||
prev.spaced: true if prev
|
||||
@i += space.length
|
||||
@i: + space.length
|
||||
true
|
||||
|
||||
# Generate a newline token. Consecutive newlines get merged together.
|
||||
@@ -253,7 +253,7 @@ exports.Lexer: class Lexer
|
||||
value: match and match[1]
|
||||
space: match and match[2]
|
||||
@tag_parameters() if value and value.match(CODE)
|
||||
value ||= @chunk.substr(0, 1)
|
||||
value: or @chunk.substr(0, 1)
|
||||
prev_spaced: @prev() and @prev().spaced
|
||||
tag: value
|
||||
if value.match(ASSIGNMENT)
|
||||
@@ -271,7 +271,7 @@ exports.Lexer: class Lexer
|
||||
else if include(CALLABLE, @tag()) and not prev_spaced
|
||||
tag: 'CALL_START' if value is '('
|
||||
tag: 'INDEX_START' if value is '['
|
||||
@i += value.length
|
||||
@i: + value.length
|
||||
return @tag_half_assignment(tag) if space and prev_spaced and @prev()[0] is 'ASSIGN' and include HALF_ASSIGNMENTS, tag
|
||||
@token tag, value
|
||||
true
|
||||
@@ -311,7 +311,7 @@ exports.Lexer: class Lexer
|
||||
return if @tag() isnt ')'
|
||||
i: 0
|
||||
while true
|
||||
i += 1
|
||||
i: + 1
|
||||
tok: @prev(i)
|
||||
return if not tok
|
||||
switch tok[0]
|
||||
@@ -354,13 +354,13 @@ exports.Lexer: class Lexer
|
||||
[i, pi]: [1, 1]
|
||||
while i < str.length - 1
|
||||
if starts str, '\\', i
|
||||
i += 1
|
||||
i: + 1
|
||||
else if match: str.substring(i).match INTERPOLATION
|
||||
[group, interp]: match
|
||||
interp: "this.${ interp.substring(1) }" if starts interp, '@'
|
||||
tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i
|
||||
tokens.push ['IDENTIFIER', interp]
|
||||
i += group.length - 1
|
||||
i: + group.length - 1
|
||||
pi: i + 1
|
||||
else if (expr: balanced_string str.substring(i), [['${', '}']])
|
||||
tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i
|
||||
@@ -371,9 +371,9 @@ exports.Lexer: class Lexer
|
||||
tokens.push ['TOKENS', nested]
|
||||
else
|
||||
tokens.push ['STRING', "$quote$quote"]
|
||||
i += expr.length - 1
|
||||
i: + expr.length - 1
|
||||
pi: i + 1
|
||||
i += 1
|
||||
i: + 1
|
||||
tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i and pi < str.length - 1
|
||||
tokens.unshift ['STRING', "''"] unless tokens[0][0] is 'STRING'
|
||||
for token, i in tokens
|
||||
|
||||
@@ -79,7 +79,7 @@ exports.BaseNode: class BaseNode
|
||||
idt: (tabs) ->
|
||||
idt: @tab or ''
|
||||
num: (tabs or 0) + 1
|
||||
idt += TAB while num -= 1
|
||||
idt: + TAB while num: - 1
|
||||
idt
|
||||
|
||||
# Construct a node that returns the current node's result.
|
||||
@@ -112,7 +112,7 @@ exports.BaseNode: class BaseNode
|
||||
# `toString` representation of the node, for inspecting the parse tree.
|
||||
# This is what `coffee --nodes` prints out.
|
||||
toString: (idt) ->
|
||||
idt ||= ''
|
||||
idt: or ''
|
||||
'\n' + idt + @type + (child.toString(idt + TAB) for child in @children).join('')
|
||||
|
||||
# Default implementations of the common node identification methods. Nodes
|
||||
@@ -162,14 +162,14 @@ exports.Expressions: class Expressions extends BaseNode
|
||||
make_return: ->
|
||||
idx: @expressions.length - 1
|
||||
last: @expressions[idx]
|
||||
last: @expressions[idx -= 1] if last instanceof CommentNode
|
||||
last: @expressions[idx: - 1] if last instanceof CommentNode
|
||||
return this if not last or last instanceof ReturnNode
|
||||
@expressions[idx]: last.make_return() unless last.contains_pure_statement()
|
||||
this
|
||||
|
||||
# An **Expressions** is the only node that can serve as the root.
|
||||
compile: (o) ->
|
||||
o ||= {}
|
||||
o: or {}
|
||||
if o.scope then super(o) else @compile_root(o)
|
||||
|
||||
compile_node: (o) ->
|
||||
@@ -315,11 +315,11 @@ exports.ValueNode: class ValueNode extends BaseNode
|
||||
temp: o.scope.free_variable()
|
||||
complete: "($temp = $complete)$@SOAK" + (baseline: temp + prop.compile(o))
|
||||
else
|
||||
complete: complete + @SOAK + (baseline += prop.compile(o))
|
||||
complete: complete + @SOAK + (baseline: + prop.compile(o))
|
||||
else
|
||||
part: prop.compile(o)
|
||||
baseline += part
|
||||
complete += part
|
||||
baseline: + part
|
||||
complete: + part
|
||||
@last: part
|
||||
|
||||
if op and soaked then "($complete)" else complete
|
||||
@@ -772,7 +772,7 @@ exports.CodeNode: class CodeNode extends BaseNode
|
||||
splat.trailings.push(param)
|
||||
else
|
||||
params.push(param)
|
||||
i += 1
|
||||
i: + 1
|
||||
params: (param.compile(o) for param in params)
|
||||
@body.make_return()
|
||||
(o.scope.parameter(param)) for param in params
|
||||
@@ -798,7 +798,7 @@ exports.CodeNode: class CodeNode extends BaseNode
|
||||
child.traverse block for child in @real_children()
|
||||
|
||||
toString: (idt) ->
|
||||
idt ||= ''
|
||||
idt: or ''
|
||||
children: (child.toString(idt + TAB) for child in @real_children()).join('')
|
||||
"\n$idt$children"
|
||||
|
||||
@@ -824,7 +824,7 @@ exports.SplatNode: class SplatNode extends BaseNode
|
||||
i: 0
|
||||
for trailing in @trailings
|
||||
o.scope.assign(trailing.compile(o), "arguments[arguments.length - $@trailings.length + $i]")
|
||||
i += 1
|
||||
i: + 1
|
||||
"$name = Array.prototype.slice.call(arguments, $@index, arguments.length - ${@trailings.length})"
|
||||
|
||||
# A compiling a splat as a destructuring assignment means slicing arguments
|
||||
@@ -850,7 +850,7 @@ SplatNode.compile_mixed_array: (list, o) ->
|
||||
else
|
||||
code: "[$code]"
|
||||
args.push(if i is 0 then code else ".concat($code)")
|
||||
i += 1
|
||||
i: + 1
|
||||
args.join('')
|
||||
|
||||
#### WhileNode
|
||||
@@ -924,7 +924,7 @@ exports.OpNode: class OpNode extends BaseNode
|
||||
PREFIX_OPERATORS: ['typeof', 'delete']
|
||||
|
||||
constructor: (operator, first, second, flip) ->
|
||||
@type += ' ' + operator
|
||||
@type: + ' ' + operator
|
||||
@children: compact [@first: first, @second: second]
|
||||
@operator: @CONVERSIONS[operator] or operator
|
||||
@flip: !!flip
|
||||
@@ -1221,12 +1221,12 @@ exports.IfNode: class IfNode extends BaseNode
|
||||
|
||||
# If the `else_body` is an **IfNode** itself, then we've got an *if-else* chain.
|
||||
is_chain: ->
|
||||
@chain ||= @else_body and @else_body instanceof IfNode
|
||||
@chain: or @else_body and @else_body instanceof IfNode
|
||||
|
||||
# The **IfNode** only compiles into a statement if either of its bodies needs
|
||||
# to be a statement. Otherwise a ternary is safe.
|
||||
is_statement: ->
|
||||
@statement ||= !!(@comment or @tags.statement or @body.is_statement() or (@else_body and @else_body.is_statement()))
|
||||
@statement: or !!(@comment or @tags.statement or @body.is_statement() or (@else_body and @else_body.is_statement()))
|
||||
|
||||
compile_condition: (o) ->
|
||||
(cond.compile(o) for cond in flatten([@condition])).join(' || ')
|
||||
@@ -1235,8 +1235,8 @@ exports.IfNode: class IfNode extends BaseNode
|
||||
if @is_statement() then @compile_statement(o) else @compile_ternary(o)
|
||||
|
||||
make_return: ->
|
||||
@body &&= @body.make_return()
|
||||
@else_body &&= @else_body.make_return()
|
||||
@body: and @body.make_return()
|
||||
@else_body: and @else_body.make_return()
|
||||
this
|
||||
|
||||
# Compile the **IfNode** as a regular *if-else* statement. Flattened chains
|
||||
|
||||
@@ -46,7 +46,7 @@ exports.Rewriter: class Rewriter
|
||||
while true
|
||||
break unless @tokens[i]
|
||||
move: block(@tokens[i - 1], @tokens[i], @tokens[i + 1], i)
|
||||
i += move
|
||||
i: + move
|
||||
true
|
||||
|
||||
# Massage newlines and indentations so that comments don't have to be
|
||||
@@ -88,20 +88,20 @@ exports.Rewriter: class Rewriter
|
||||
switch token[0]
|
||||
when 'CALL_START' then parens.push(0)
|
||||
when 'INDEX_START' then brackets.push(0)
|
||||
when '(' then parens[parens.length - 1] += 1
|
||||
when '[' then brackets[brackets.length - 1] += 1
|
||||
when '(' then parens[parens.length - 1]: + 1
|
||||
when '[' then brackets[brackets.length - 1]: + 1
|
||||
when ')'
|
||||
if parens[parens.length - 1] is 0
|
||||
parens.pop()
|
||||
token[0]: 'CALL_END'
|
||||
else
|
||||
parens[parens.length - 1] -= 1
|
||||
parens[parens.length - 1]: - 1
|
||||
when ']'
|
||||
if brackets[brackets.length - 1] == 0
|
||||
brackets.pop()
|
||||
token[0]: 'INDEX_END'
|
||||
else
|
||||
brackets[brackets.length - 1] -= 1
|
||||
brackets[brackets.length - 1]: - 1
|
||||
return 1
|
||||
|
||||
# Methods may be optionally called without parentheses, for simple cases.
|
||||
@@ -113,15 +113,15 @@ exports.Rewriter: class Rewriter
|
||||
@scan_tokens (prev, token, post, i) =>
|
||||
tag: token[0]
|
||||
switch tag
|
||||
when 'CALL_START' then calls += 1
|
||||
when 'CALL_END' then calls -= 1
|
||||
when 'CALL_START' then calls: + 1
|
||||
when 'CALL_END' then calls: - 1
|
||||
when 'INDENT' then stack.push(0)
|
||||
when 'OUTDENT'
|
||||
last: stack.pop()
|
||||
stack[stack.length - 1] += last
|
||||
stack[stack.length - 1]: + last
|
||||
open: stack[stack.length - 1] > 0
|
||||
if tag is 'CALL_END' and calls < 0 and open
|
||||
stack[stack.length - 1] -= 1
|
||||
stack[stack.length - 1]: - 1
|
||||
@tokens.splice(i, 0, ['CALL_END', ')', token[2]])
|
||||
return 2
|
||||
if !post? or include IMPLICIT_END, tag
|
||||
@@ -137,7 +137,7 @@ exports.Rewriter: class Rewriter
|
||||
return 1 unless prev and include(IMPLICIT_FUNC, prev[0]) and include IMPLICIT_CALL, tag
|
||||
calls: 0
|
||||
@tokens.splice(i, 0, ['CALL_START', '(', token[2]])
|
||||
stack[stack.length - 1] += 1
|
||||
stack[stack.length - 1]: + 1
|
||||
return 2
|
||||
|
||||
# Because our grammar is LALR(1), it can't handle some single-line
|
||||
@@ -154,7 +154,7 @@ exports.Rewriter: class Rewriter
|
||||
idx: i + 1
|
||||
parens: 0
|
||||
while true
|
||||
idx += 1
|
||||
idx: + 1
|
||||
tok: @tokens[idx]
|
||||
pre: @tokens[idx - 1]
|
||||
if (not tok or
|
||||
@@ -164,8 +164,8 @@ exports.Rewriter: class Rewriter
|
||||
insertion: if pre[0] is "," then idx - 1 else idx
|
||||
@tokens.splice(insertion, 0, ['OUTDENT', 2, token[2]])
|
||||
break
|
||||
parens += 1 if tok[0] is '('
|
||||
parens -= 1 if tok[0] is ')'
|
||||
parens: + 1 if tok[0] is '('
|
||||
parens: - 1 if tok[0] is ')'
|
||||
return 1 unless token[0] is 'THEN'
|
||||
@tokens.splice(i, 1)
|
||||
return 0
|
||||
@@ -178,11 +178,11 @@ exports.Rewriter: class Rewriter
|
||||
@scan_tokens (prev, token, post, i) =>
|
||||
for pair in pairs
|
||||
[open, close]: pair
|
||||
levels[open] ||= 0
|
||||
levels[open]: or 0
|
||||
if token[0] is open
|
||||
open_line[open]: token[2] if levels[open] == 0
|
||||
levels[open] += 1
|
||||
levels[open] -= 1 if token[0] is close
|
||||
levels[open]: + 1
|
||||
levels[open]: - 1 if token[0] is close
|
||||
throw new Error("too many ${token[1]} on line ${token[2] + 1}") if levels[open] < 0
|
||||
return 1
|
||||
unclosed: key for key, value of levels when value > 0
|
||||
@@ -218,14 +218,14 @@ exports.Rewriter: class Rewriter
|
||||
return 1
|
||||
else if include EXPRESSION_END, tag
|
||||
if debt[inv] > 0
|
||||
debt[inv] -= 1
|
||||
debt[inv]: - 1
|
||||
@tokens.splice i, 1
|
||||
return 0
|
||||
else
|
||||
match: stack.pop()
|
||||
mtag: match[0]
|
||||
return 1 if tag is INVERSES[mtag]
|
||||
debt[mtag] += 1
|
||||
debt[mtag]: + 1
|
||||
val: if mtag is 'INDENT' then match[1] else INVERSES[mtag]
|
||||
@tokens.splice i, 0, [INVERSES[mtag], val]
|
||||
return 1
|
||||
|
||||
@@ -19,7 +19,7 @@ ok(area(
|
||||
|
||||
sum_of_args: ->
|
||||
sum: 0
|
||||
sum += val for val in arguments
|
||||
sum: + val for val in arguments
|
||||
sum
|
||||
|
||||
ok sum_of_args(1, 2, 3, 4, 5) is 15
|
||||
@@ -22,7 +22,7 @@ class SplitNode extends BaseNode
|
||||
# and creates a SplitNode.
|
||||
CoffeeScript.extend ->
|
||||
return false unless variable: @match(/^--(\w+)--/, 1)
|
||||
@i += variable.length + 4
|
||||
@i: + variable.length + 4
|
||||
@token 'EXTENSION', new SplitNode(variable)
|
||||
true
|
||||
|
||||
@@ -46,7 +46,7 @@ class WordArrayNode extends BaseNode
|
||||
|
||||
CoffeeScript.extend ->
|
||||
return false unless words: @chunk.match(/^%w\{(.*?)\}/)
|
||||
@i += words[0].length
|
||||
@i: + words[0].length
|
||||
@token 'EXTENSION', new WordArrayNode(words[1].split(/\s+/))
|
||||
true
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ ok !func(8)
|
||||
|
||||
# Should cache the switch value, if anything fancier than a literal.
|
||||
num: 5
|
||||
result: switch num += 5
|
||||
result: switch num: + 5
|
||||
when 5 then false
|
||||
when 15 then false
|
||||
when 10 then true
|
||||
|
||||
Reference in New Issue
Block a user