Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change comment parsing #6583

Open
wants to merge 3 commits into
base: dev/feature
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
142 changes: 125 additions & 17 deletions src/main/java/ch/njol/skript/config/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ch.njol.skript.SkriptConfig;
import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.Skript;
Expand Down Expand Up @@ -114,37 +115,111 @@ public void move(final SectionNode newParent) {
p.remove(this);
newParent.add(this);
}

@SuppressWarnings("null")
private final static Pattern linePattern = Pattern.compile("^((?:[^#]|##)*)(\\s*#(?!#).*)$");


/**
* Splits a line into value and comment.
* <p>
* Whitespace is preserved (whitespace in front of the comment is added to the value), and any ## in the value are replaced by a single #. The comment is returned with a
* Whitespace is preserved (whitespace in front of the comment is added to the value), and any ## not in quoted strings in the value are replaced by a single #. The comment is returned with a
* leading #, except if there is no comment in which case it will be the empty string.
*
* @param line
* @return A pair (value, comment).
*/
public static NonNullPair<String, String> splitLine(final String line) {
public static NonNullPair<String, String> splitLine(String line) {
if (line.trim().startsWith("#"))
return new NonNullPair<>("", line.substring(line.indexOf('#')));
final Matcher m = linePattern.matcher(line);
boolean matches = false;
try {
matches = line.contains("#") && m.matches();
} catch (StackOverflowError e) { // Probably a very long line
handleNodeStackOverflow(e, line);

// idea: find first # that is not within a string or variable name. Use state machine to determine whether a # is a comment or not.
int length = line.length();
StringBuilder finalLine = new StringBuilder(line);
int numRemoved = 0;
SplitLineState state = SplitLineState.CODE;
SplitLineState previousState = SplitLineState.CODE;
// find next " or %
for (int i = 0; i < length; i++) {
char c = line.charAt(i);
// check for things that can be escaped by doubling
if (c == '%' || c == '"' || c == '#') {
// skip if doubled (only skip ## outside of strings)
if ((c != '#' || state != SplitLineState.STRING) && i + 1 < length && line.charAt(i + 1) == c) {
if (c == '#') { // remove duplicate #
finalLine.deleteCharAt(i - numRemoved);
numRemoved++;
}
i++;
continue;
}
SplitLineState tmp = state;
state = SplitLineState.update(c, state, previousState);
if (state == SplitLineState.HALT)
return new NonNullPair<>(finalLine.substring(0, i - numRemoved), line.substring(i));
previousState = tmp;
}
}
return new NonNullPair<>(finalLine.toString(), "");
}

/**
* state machine:<br>
* ": CODE -> STRING, STRING -> CODE, VARIABLE -> VARIABLE<br>
* %: CODE -> PREVIOUS_STATE, STRING -> CODE, VARIABLE -> CODE<br>
* {: CODE -> VARIABLE, STRING -> STRING, VARIABLE -> VARIABLE<br>
* }: CODE -> CODE, STRING -> STRING, VARIABLE -> CODE<br>
* #: CODE -> HALT, STRING -> STRING, VARIABLE -> HALT<br>
* invalid characters simply return given state.<br>
*/
private enum SplitLineState {
HALT,
CODE,
STRING,
VARIABLE;

/**
* Updates the state given a character input.
* @param c character input. '"', '%', '{', '}', and '#' are valid.
* @param state the current state of the machine
* @param previousState the previous state of the machine
* @return the new state of the machine
*/
private static SplitLineState update(char c, SplitLineState state, SplitLineState previousState) {
if (state == HALT)
return HALT;

switch (c) {
case '%':
if (state == CODE)
return previousState;
return CODE;
case '"':
switch (state) {
case CODE:
return STRING;
case STRING:
return CODE;
default:
return state;
}
case '{':
if (state == STRING)
return STRING;
return VARIABLE;
case '}':
if (state == STRING)
return STRING;
return CODE;
case '#':
if (state == STRING)
return STRING;
return HALT;
}
return state;
}
if (matches)
return new NonNullPair<>("" + m.group(1).replace("##", "#"), "" + m.group(2));
return new NonNullPair<>("" + line.replace("##", "#"), "");
}


static void handleNodeStackOverflow(StackOverflowError e, String line) {
Node n = SkriptLogger.getNode();
SkriptLogger.setNode(null); // Avoid duplicating the which node error occurred in paranthesis on every error message
SkriptLogger.setNode(null); // Avoid duplicating the which node error occurred in parentheses on every error message

Skript.error("There was a StackOverFlowError occurred when loading a node. This maybe from your scripts, aliases or Skript configuration.");
Skript.error("Please make your script lines shorter! Do NOT report this to SkriptLang unless it occurs with a short script line or built-in aliases!");
Expand Down Expand Up @@ -193,12 +268,45 @@ protected String getIndentation() {
abstract String save_i();

public final String save() {
return getIndentation() + save_i().replace("#", "##") + comment;
return getIndentation() + escapeUnquotedHashtags(save_i()) + comment;
}

public void save(final PrintWriter w) {
w.println(save());
}

private static String escapeUnquotedHashtags(String input) {
int length = input.length();
StringBuilder output = new StringBuilder(input);
int numAdded = 0;
SplitLineState state = SplitLineState.CODE;
SplitLineState previousState = SplitLineState.CODE;
// find next " or %
for (int i = 0; i < length; i++) {
char c = input.charAt(i);
// check for things that can be escaped by doubling
if (c == '%' || c == '"' || c == '#') {
// escaped #s outside of strings
if (c == '#' && state != SplitLineState.STRING) {
output.insert(i + numAdded, "#");
numAdded++;
continue;
}
// skip if doubled (not #s)
if (i + 1 < length && input.charAt(i + 1) == c) {
i++;
continue;
}
SplitLineState tmp = state;
state = SplitLineState.update(c, state, previousState);
if (state == SplitLineState.HALT)
return output.toString();
previousState = tmp;
}
}
return output.toString();
}


@Nullable
public SectionNode getParent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"set slot 4 of {_inventory} to a diamond named \"example\"",
"open {_inventory} to player",
"",
"open chest inventory named \"<##00ff00>hex coloured title!\" with 6 rows to player",
"open chest inventory named \"<#00ff00>hex coloured title!\" with 6 rows to player",
})
@RequiredPlugins("Paper 1.16+ (chat format)")
@Since("2.2-dev34, 2.8.0 (chat format)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"",
"loop {top-balances::*}:",
"\tif loop-iteration <= 10:",
"\t\tbroadcast \"##%loop-iteration% %loop-index% has $%loop-value%\"",
"\t\tbroadcast \"#%loop-iteration% %loop-index% has $%loop-value%\"",
})
@Since("2.8.0")
public class ExprLoopIteration extends SimpleExpression<Long> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"",
"loop {top-balances::*}:",
"\tloop-iteration <= 10",
"\tsend \"##%loop-iteration% %loop-index% has $%loop-value%\"",
"\tsend \"#%loop-iteration% %loop-index% has $%loop-value%\"",
})
@Since("1.0, 2.8.0 (loop-counter)")
public class ExprLoopValue extends SimpleExpression<Object> {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/ch/njol/skript/util/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ public String run(final Matcher m) {
return "" + m;
}

private static final Pattern HEX_PATTERN = Pattern.compile("(?i)#?[0-9a-f]{6}");
private static final Pattern HEX_PATTERN = Pattern.compile("(?i)#{0,2}[0-9a-f]{6}");

/**
* Tries to get a {@link ChatColor} from the given string.
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/config.sk
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
# This file, all scripts and other files ending in .sk are NOT .yml/YAML files, but very similar!
# Please remember the following when editing files:
# - To indent sections you can use spaces like in YAML, but tabs are also allowed. Just remember to stick to the one or the other for a section/trigger.
# - '#' starts a comment like in YAML. If you don't want it to start a comment simply double it: '##' (You also have to double these in "quoted text")
# - '#' starts a comment like in YAML. If you don't want it to start a comment simply double it: '##' (You do NOT have to double these in "quoted text")
# - If you use special characters (§, äöü, éèàôç, ñ, etc.) you have to encode the file in UTF-8.
#

Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/scripts/-examples/text formatting.sk
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
# You can also use <> for colours and formats, like `<red>` for red and `<bold>` for bold
#
# In Minecraft 1.16, support was added for 6-digit hexadecimal colors to specify custom colors other than the 16 default color codes.
# The tag for these colors looks like this: <##hex code> e.g. `<##123456>`
# The tag for these colors looks like this: <#hex code> e.g. `<#123456>`
#

command /color:
permission: skript.example.color
trigger:
send "&6This message is golden."
send "<light red><bold>This message is light red and bold."
send "<##FF0000>This message is red."
send "<#FF0000>This message is red."

#
# Other formatting options are also available.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public void splitLineTest() {
{"#########", "", "#########"},
{"a##b#c##d#e", "a#b", "#c##d#e"},
{" a ## b # c ## d # e ", " a # b ", "# c ## d # e "},
{"a b \"#a ##\" # b \"", "a b \"#a ##\" ", "# b \""},
};
for (String[] d : data) {
NonNullPair<String, String> p = Node.splitLine(d[0]);
Expand Down
45 changes: 45 additions & 0 deletions src/test/skript/tests/misc/comments.sk
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
test "comments":
parse:
set {_a} to {_b} # test
assert last parse logs is not set with "skript should be able to handle inline comments but did not"


parse:
assert "a" is "a" with "wrong number of hashtags"
assert "#a" is join "#", and "a" with "wrong number of hashtags"
assert "##a" is join "#", "#", and "a" with "wrong number of hashtags"
assert "###a" is join "#", "#", "#", and "a" with "wrong number of hashtags"
assert last parse logs is not set with "skript should be able to handle strings any number of hashtags but did not"


parse:
assert "a%"#"%" is join "a", and "#" with "wrong number of hashtags"
assert "#a%"#}"%" is join "#", "a", and "#}" with "wrong number of hashtags"
assert "##a%"#"%" is join "#", "#", "a", and "#" with "wrong number of hashtags"
assert "#{##a%"#"%" is join "#{", "#", "#", "a", and "#" with "wrong number of hashtags"
assert last parse logs is not set with "skript should be able to handle complex strings any number of hashtags but did not"


parse:
set {_a} to "<#abcdef>test"
set {_b} to "<##abcdef>test"
assert uncoloured {_a} is "test" with "failed to parse single hashtag colour code"
assert uncoloured {_b} is "test" with "failed to parse double hashtag colour code"
assert last parse logs is not set with "skript should be able to handle hex colour codes but did not"

parse:
set {_a} to "###SH#JABJ#BJ#JB#K#BH#G#J##J#HJ%%KJB#JKK%%""%%""%%""%%#""##%%""#""%%##""#%""%##%"#"""%#"#!!""#""#L@$L@:@#L@K:L%@^$:"#^#:^J$%:K^J%&LK:#::&&^^^%%test
assert last parse logs is not set with "skript should be able to handle very messy string but did not"


parse:
set {_a##} to "test"
set {##_b} to "test"
set {##_b::%"{}"%} to "test"
set {##_b::%"{}#"%} to "test"
assert last parse logs is not set with "skript should be able to handle hashtags in variable names but did not"


parse:
set {##_b::%"{}"#%} to "test"
assert last parse logs is set with "skript should not be able to handle hashtags in an expression in a variable name but did"
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
test "floating point errors in rounding functions":
#assert ceil(100*0.07) is 7 with "ceil function doesn't adjust for floating point errors ##1"
#assert ceil(100*0.033 - 0.3) is 3 with "ceil function doesn't adjust for floating point errors ##2"
#assert ceil(100*0.07) is 7 with "ceil function doesn't adjust for floating point errors"
#assert ceil(100*0.033 - 0.3) is 3 with "ceil function doesn't adjust for floating point errors"

#assert rounded up 100*0.07 is 7 with "ceil expression doesn't adjust for floating point errors ##1"
#assert rounded up 100*0.033 - 0.3 is 3 with "ceil expression doesn't adjust for floating point errors ##2"
#assert rounded up 100*0.07 is 7 with "ceil expression doesn't adjust for floating point errors"
#assert rounded up 100*0.033 - 0.3 is 3 with "ceil expression doesn't adjust for floating point errors"

set {_sum} to 0
loop 100 times:
add 0.1 to {_sum}
assert floor({_sum}) is 10 with "floor function doesn't adjust for floating point errors ##1"
assert rounded down {_sum} is 10 with "floor expression doesn't adjust for floating point errors ##1"
assert floor({_sum}) is 10 with "floor function doesn't adjust for floating point errors"
assert rounded down {_sum} is 10 with "floor expression doesn't adjust for floating point errors"
10 changes: 5 additions & 5 deletions src/test/skript/tests/regressions/4664-formatted time.sk
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ test "formatted time":
set {_now} to now

set {_date1} to {_now} formatted
assert {_date1} = {_now} formatted as {_default} with "default date format failed ##1"
assert {_date1} = {_now} formatted as {_default} with "default date format failed"

set {_date2} to {_now} formatted human-readable
assert {_date2} = {_now} formatted as {_default} with "default date format failed ##2"
assert {_date2} = {_now} formatted as {_default} with "default date format failed"

set {_date3} to {_now} formatted as "HH:mm"
assert length of {_date3} = 5 with "custom date format failed ##1"
assert length of {_date3} = 5 with "custom date format failed"

set {_cFormat} to "hh:mm"
set {_date4} to {_now} formatted as {_cFormat}
assert length of {_date4} = 5 with "custom date format failed ##2"
assert length of {_date4} = 5 with "custom date format failed"

set {_cFormat2} to "i"
set {_date5} to {_now} formatted as {_cFormat2}
assert {_date5} is not set with "custom date format failed ##3"
assert {_date5} is not set with "custom date format failed"
4 changes: 2 additions & 2 deletions src/test/skript/tests/regressions/4769-fire-visualeffect.sk
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ test "fire visual effect comparison":
set block at spawn of world "world" to {_block}
play 5 fire at spawn of world "world"
assert "fire" parsed as visual effect is fire with "failed to compare visual effects"
assert "fire" parsed as visual effect is "fire" parsed as itemtype to fail with "failed to compare visual effects ##2"
assert "fire" parsed as visual effect is "fire" parsed as itemtype to fail with "failed to compare visual effects"
spawn a chicken at spawn of world "world":
assert event-entity is a chicken with "failed to compare a chicken"
assert event-entity is a "chicken" parsed as itemtype to fail with "failed to compare a chicken ##2"
assert event-entity is a "chicken" parsed as itemtype to fail with "failed to compare a chicken"
clear event-entity
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
test "math order":
assert 1 + 1 = 2 with "basic math ##1 failed"
assert 5 - 3 = 2 with "basic math ##2 failed"
assert 1 + 1 = 2 with "basic math failed"
assert 5 - 3 = 2 with "basic math failed"

assert 5 - 3 - 1 = 1 with "basic chained math ##1 failed"
assert 5-3-2 = 0 with "basic chained math ##2 failed"
assert 10 - 1 - 5 = 4 with "basic chained math ##3 failed"
assert 5 - 3 - 1 = 1 with "basic chained math failed"
assert 5-3-2 = 0 with "basic chained math failed"
assert 10 - 1 - 5 = 4 with "basic chained math failed"

assert (9 - 1) - 3 = 5 with "basic chained math with parentheses ##1 failed"
assert 9 - (1 - 3) = 11 with "basic chained math with parentheses ##2 failed"
assert (9 - 1) - 3 = 5 with "basic chained math with parentheses failed"
assert 9 - (1 - 3) = 11 with "basic chained math with parentheses failed"
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ test "double quote parsing":

assert "Testing """ is set with "simple string with escaped quote failed"

assert "Testing %length of "abc"%" is set with "string with expression with string ##1 failed"
assert "%myFunction_five_nine_zero("Hello")% world" is "Hello world" with "string with expression with string ##2 failed"
assert "Testing %length of "abc"%" is set with "string with expression with string failed"
assert "%myFunction_five_nine_zero("Hello")% world" is "Hello world" with "string with expression with string failed"


assert {_abc} is not set with "simple variable failed"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ test "variable parsing":
assert {_test::%{_x}%} is not set with "simple list index using local variable failed"
assert {_test::{_x}} is not set to fail with "list index with local variable without percentage signs failed"

assert {_test::%uuid of {_x}%} is not set with "list index with expression and local variable ##1 failed"
assert {_test::%{_x}'s uuid%} is not set with "list index with expression and local variable ##2 failed"
assert {_test::%uuid of {_x}%} is not set with "list index with expression and local variable failed"
assert {_test::%{_x}'s uuid%} is not set with "list index with expression and local variable failed"

assert {_test::%{_a} split at {_b}%} is not set with "list index with expression with local variables on both ends failed"

Expand Down