Allow for omitting return keyword even when parsing as Expression fails, so long as the line of input does not look like a single non-expression statement legal in isolation in a function body.

This commit is contained in:
blujai831 2023-10-12 09:48:41 -07:00
parent c3b0f0033f
commit 7064808235
Signed by: blujai831
GPG Key ID: DDC31A0363AA5E66
4 changed files with 79 additions and 18 deletions

View File

@ -38,10 +38,6 @@ there are some less-than-intuitive caveats:
any variable assignments prior in the input line,
even to preexisting variables, will be undone.
This will preserve `x`: `x = 6`. This will not: `x = 6; return x`.
3. For the sake of shorthand, an assignment statement alone on its input line
and assigning to a nonexistent variable will act like a declaration instead.
That is to say, if `x` does not exist, `x = 6` all by itself
will act like `var x = 6`, but `x = 6; print(x)` will be an error.
You may not declare classes.

View File

@ -37,11 +37,6 @@ there are some less-than-intuitive caveats:
including a `return` statement, and that statement is reached,
any variable assignments prior in the input line,
even to preexisting variables, will be undone.
This will preserve `x`: `x = 6`. This will not: `x = 6; return x`.
3. For the sake of shorthand, an assignment statement alone on its input line
and assigning to a nonexistent variable will act like a declaration instead.
That is to say, if `x` does not exist, `x = 6` all by itself
will act like `var x = 6`, but `x = 6; print(x)` will be an error.
You may not declare classes.

View File

@ -8,6 +8,12 @@ var _session: Object
var _expression: Expression
var _assignment_regex: RegEx
var _function_regex: RegEx
var _if_regex: RegEx
var _for_regex: RegEx
var _while_regex: RegEx
var _match_regex: RegEx
var _return_regex: RegEx
var _pass_regex: RegEx
## Traverses the editor's interface tree in search of the EditorLog.
@ -34,6 +40,38 @@ func _find_parent_for_field() -> VBoxContainer:
return null
## Checks if a code snippet is multiple statements
## (has semicolons outside strings).
func _is_multiple_statements(code: String) -> bool:
var quotetype: int = 0x0
var escape := false
for i in code.length():
match code.unicode_at(i):
0x22: # "
if quotetype == 0x22:
if not escape:
quotetype = 0x0
else:
quotetype = 0x22
escape = false
0x27: # '
if quotetype == 0x27:
if not escape:
quotetype = 0x0
else:
quotetype = 0x27
escape = false
0x3b: # ;
if quotetype == 0x0:
return true
0x5c: # \
if quotetype != 0x0:
escape = not escape
_:
escape = false
return false
## Creates and executes a GDScript function from a given code string.
func _exec(code: String) -> Variant:
var result: Variant
@ -42,13 +80,12 @@ func _exec(code: String) -> Variant:
var oneliner = code.replace("\n", ' ').replace("\r", ' ')
var rxmatch: RegExMatch
# First check if it's a variable assignment.
if oneliner.find(';') < 0:
rxmatch = _assignment_regex.search(oneliner)
if rxmatch:
_session._vars[rxmatch.get_string(1)] = _exec(
rxmatch.get_string(2)
)
return null
rxmatch = _assignment_regex.search(oneliner)
if rxmatch:
_session._vars[rxmatch.get_string(1)] = _exec(
rxmatch.get_string(2)
)
return null
# Otherwise:
# Collect varnames and varvals.
for varname in _session._vars:
@ -87,7 +124,23 @@ func _exec(code: String) -> Variant:
""".dedent()
for varname in varnames:
src += "\tvar %s = _vars['%s']\n" % [varname, varname]
src += code.indent("\t") + "\n"
# Check if we should prepend `return`.
var should_prepend_return := true
# If the code is any of these kinds of statement, we should not:
if _if_regex.search(oneliner) or \
_for_regex.search(oneliner) or \
_while_regex.search(oneliner) or \
_match_regex.search(oneliner) or \
_return_regex.search(oneliner) or \
_pass_regex.search(oneliner):
should_prepend_return = false
# If the code is multiple statements, we should not:
if _is_multiple_statements(code):
should_prepend_return = false
if should_prepend_return:
src += ("return " + code).indent("\t") + "\n"
else:
src += code.indent("\t") + "\n"
for varname in varnames:
src += "\t_vars['%s'] = %s\n" % [varname, varname]
_session_script.source_code = src
@ -123,12 +176,25 @@ func _enter_tree() -> void:
_session_script.reload()
_session = _session_script.new()
_expression = Expression.new()
# Create regex.
_assignment_regex = RegEx.new()
_assignment_regex.compile("^\\s*(?:var)?\\s*([a-zA-Z0-9_]+)\\s*=(.+)$")
_assignment_regex.compile("^\\s*var\\s*([a-zA-Z0-9_]+)\\s*=(.+)$")
_function_regex = RegEx.new()
_function_regex.compile(
"^\\s*func\\s*([a-zA-Z0-9_]+)\\s*\\((.+?)\\)\\s*:\\s*(.+)$"
)
_if_regex = RegEx.new()
_if_regex.compile("^\\s*if[\\s\\(].*$")
_for_regex = RegEx.new()
_for_regex.compile("^\\s*for[\\s\\(].*$")
_while_regex = RegEx.new()
_while_regex.compile("^\\s*while[\\s\\(].*$")
_match_regex = RegEx.new()
_match_regex.compile("^\\s*match[\\s\\(].*$")
_return_regex = RegEx.new()
_return_regex.compile("^\\s*return(?:[\\s\\(].*)?$")
_pass_regex = RegEx.new()
_pass_regex.compile("^\\s*pass(?:[\\s\\(].*)?$")
# Add input field to tree.
var parent := _find_parent_for_field()
if parent:

View File

@ -14,3 +14,7 @@ config/name="GDScript Input Field Plugin"
config/description="Provides an input field in the editor output panel for executing arbitrary GDScript in a REPL-like fashion."
config/features=PackedStringArray("4.1", "Forward Plus")
config/icon="res://icon.svg"
[editor_plugins]
enabled=PackedStringArray("res://addons/gdscript_input_field/plugin.cfg")