I want a regular expression that will extract _A_, 12345, Non_Literal_Left, and Non_Literal_Right from the following string:
... ${_A_}, ${12345}, '${Literal}' $yada yada'$[]' '${Non_Literal_Left} ${Non_Literal_Right}'
The closest I have been able to get is everything less the single quote restriction for literals:
Matcher matcher = Pattern.compile("\\$\\{(\\w+)\\}").matcher(s);
while (matcher.find()) {
result.add(matcher.group(1));
}
Which results in everything I want plus Literal, which I do not want to match.
Thanks in advance...
You could simply use a negative lookbehind:
"(?<!')\\$\\{(\\w+)\\}"
This will now only match if the $ is not preceded by '.
As Matt Ball mentioned in a comment, it might make sense to add another negative lookahead to the end, too:
"(?<!')\\$\\{(\\w+)\\}(?!')"
However, this will only matter if you have invalid/unmatched usage of ' as in ${Literal}' (in this case my first regex will still match Literal, the latter won't).
That was a joy.
Something tells me a RegEx expression would have been a little cleaner.
/**
* Utility class for parsing record field parameters in properties.
*
* #author Ryan
*
*/
public static class PropertyParser {
/**
* Stores the results of parsing a property.
*
* #author Ryan
*
*/
public static class ParsedParameters {
private final Set<String> literals;
private final Set<String> parameters;
private ParsedParameters() {
this.parameters = new HashSet<String>();
this.literals = new HashSet<String>();
}
/**
* Adds a literal property value to this object.
*
* #param string The literal property value to add to this object.
*/
private void addLiteral(String string) {
this.literals.add(string);
}
/**
* Adds a parameter name to this object.
*
* #param string The parameter name to add to this object.
*/
private void addParameter(String string) {
this.parameters.add(string);
}
/**
* Returns the set of literals stored in this object.
*
* #return The set of literals stored in this object.
*/
public Set<String> getLiterals() {
return this.literals;
}
/**
* Returns the set of parameters stored in this object.
*
* #return The set of parameters stored in this object.
*/
public Set<String> getParameters() {
return this.parameters;
}
}
private static final String BAD_FIELD_CHAR =
"Illegal character detected for field parameter: %c";
/**
* Extracts placeholder field name parameters from the input string.
* <p>
* Single quotes can be used to avoid the parser interpreting the ${...}
* as a field parameter.
* <p>
* For example, the parser would not detect any field parameters in the following string:
* <p>
* #!/bin/bash<br>
* # Echos the first argument<br>
* echo '${1}'<br>
* <p>
* The {#link #PropertySubstitutor()} is responsible for removing the single quotes
* surrounding the parameter when substituting the actual property value(s).
* <p>
* <b>Nested Parameters</b>
* <p>
* This parser itself will only parse the inner-most parameter or literal.
* <p>
* For example, ${Some${Value}} would actually be treated as a legal string, with
* 'Value' as the only field parameter extracted. During runtime substitution,
* this would result in ${Somebody} if the record value for the field "Value" was "body".
* <p>
* Theoretically, this parser could then be ran again to extract this generated parameter.
*
* #param string The property to parse for field parameters.
* #return An object containing the parsed parameters and literal values.
* #throws IllegalArgumentException If the property contains parameter syntax
* (i.e. ${text}) but contains illegal characters for the field.
* <p>
* Allowed characters for field names are alpha-numeric and underscores.
*/
public static ParsedParameters parseParametersAndLiterals(String string)
throws IllegalArgumentException {
if ((string == null) || string.isEmpty()) {
return new ParsedParameters();
}
ParsedParameters result = new ParsedParameters();
StringBuffer param = null;
Character badChar = null;
char c;
char p = '^';
boolean close = false;
boolean lQuote = false;
boolean open = false;
int l = string.length();
for (int i = 0; i < l; ++i) {
c = string.charAt(i);
if (!lQuote && (p == '\'') && (c == '$')) {
lQuote = true;
} else if ((p == '$') && (c == '{')) {
param = new StringBuffer();
open = true;
badChar = null;
} else if (open
&& (((c == '}') && (!lQuote || ((1 + i) == l))) || (lQuote && (p == '}')))) {
open = false;
close = true;
} else if (open) {
boolean validCharacter = Character.isLetterOrDigit(c) || (c == '_');
if (validCharacter || (lQuote && (c != '}'))) {
param.append(c);
}
if (!validCharacter && (c != '}')) {
badChar = c;
}
}
if (close) {
if ((badChar != null) && !(lQuote && (p == '}') && (c == '\''))) {
throw new IllegalArgumentException(String.format(BAD_FIELD_CHAR, badChar));
} else if (c != '\'') {
if (param.length() > 0) {
result.addParameter(param.toString());
}
} else {
result.addLiteral(param.toString());
}
lQuote = false;
close = false;
badChar = null;
}
p = c;
}
return result;
}
}
And tests, of course.
public class TestPropertyParser {
private Set<String> literals;
private Set<String> params;
private void assertLiteralsContains(String string) {
assertTrue(this.literals.contains(string));
}
private void assertParamsContains(String string) {
assertTrue(this.params.contains(string));
}
private void assertResultSizes(Integer paramSize, Integer literalSize) {
if (paramSize != null) {
assertNotNull(this.params);
assertEquals((int) paramSize, this.params.size());
} else {
assertNull(this.params);
}
if (literalSize != null) {
assertNotNull(this.literals);
assertEquals((int) literalSize, this.literals.size());
} else {
assertNull(this.literals);
}
}
private void parseAndSet(String stringToParse) {
ParsedParameters result = PropertyParser.parseParametersAndLiterals(stringToParse);
this.literals = result.getLiterals();
this.params = result.getParameters();
}
#Before
public void setup() {
this.params = new HashSet<String>();
this.literals = new HashSet<String>();
}
#Test(expected = IllegalArgumentException.class)
public void testParserInvalidParameterQuoteLeft() {
parseAndSet("'${Invalid Parameter}");
}
#Test(expected = IllegalArgumentException.class)
public void testParserInvalidParameterQuoteRight() {
parseAndSet("${Invalid Parameter}'");
}
#Test(expected = IllegalArgumentException.class)
public void testParserInvalidParameterSpaces() {
parseAndSet(" ${Invalid Parameter}");
}
#Test
public void testParserValidStrings() {
// Initialization condition.
assertResultSizes(0, 0);
// Null string.
parseAndSet(null);
assertResultSizes(0, 0);
// Empty string.
parseAndSet(new String());
assertResultSizes(0, 0);
// Single parameter.
parseAndSet("... ${_A_}, $yada yada'$[]' '${");
assertResultSizes(1, 0);
assertParamsContains("_A_");
// Many parameters and one literal.
parseAndSet("... ${_A_}, ${12345}, '${Literal}''${Non_Literal_Left} ${Non_Literal_Right}' ");
assertResultSizes(4, 1);
assertParamsContains("_A_");
assertParamsContains("12345");
assertParamsContains("Non_Literal_Left");
assertParamsContains("Non_Literal_Right");
assertLiteralsContains("Literal");
// Nested literal and odd bracket placements.
parseAndSet("''${Totally}''$}{$'${Single}");
assertResultSizes(1, 1);
assertParamsContains("Single");
assertLiteralsContains("Totally");
// Subset of ASCII characters.
parseAndSet("`1234567890-=qwertyuiop[]\\asdfghjkl;'zxcvbnm,./!##$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:\"ZXCVBNM<>?");
assertResultSizes(0, 0);
// Illegal characters in literal.
parseAndSet("'${This literal is completely valid}'");
assertResultSizes(0, 1);
assertLiteralsContains("This literal is completely valid");
// Test incomplete literal, no closure.
parseAndSet("'${This literal is never closed");
assertResultSizes(0, 0);
// Test incomplete parameter from left.
parseAndSet("${Never_Closed");
assertResultSizes(0, 0);
// And again... with a parameter at the end.
parseAndSet("${Never_Closed${But_This_Is}");
assertResultSizes(1, 0);
assertParamsContains("But_This_Is");
// Empty parameter.
parseAndSet("${}");
assertResultSizes(0, 0);
// Restarting a new parameter within an already open parameter.
parseAndSet("${Perfectly valid${a}");
assertResultSizes(1, 0);
assertParamsContains("a");
// Variation of the above with quotes.
parseAndSet("'${Perfectly valid'${a}");
assertResultSizes(1, 0);
assertParamsContains("a");
// Variation of the above with quotes.
parseAndSet("${Perfectly valid'${a}");
assertResultSizes(1, 0);
assertParamsContains("a");
// Variation of the above with quotes.
parseAndSet("${Perfectly valid${a}'");
assertResultSizes(1, 0);
assertParamsContains("a");
// Variation of the above with quotes.
parseAndSet("${Perfectly valid'${a}'");
assertResultSizes(0, 1);
assertLiteralsContains("a");
// Variation of the above with spaces.
parseAndSet(" ${ Perfectly valid${a} ");
assertResultSizes(1, 0);
assertParamsContains("a");
// TODO Determine what the desired behavior is for nested literals and parameters.
// Test nested parameter in literal.
parseAndSet("'${Nested ${Parameter}}'");
assertResultSizes(1, 0);
assertParamsContains("Parameter");
// Nested parameter.
parseAndSet("${Nested_${Parameter}}'");
assertResultSizes(1, 0);
assertParamsContains("Parameter");
// Literal nested in a parameter.
parseAndSet(" ${Nested'${Literal}'}");
assertResultSizes(0, 1);
assertLiteralsContains("Literal");
}
}
Related
So basically, the idea is as follows:
The program must extend JTextPane
The program must detect when user typed an abbreviation in the extended JTextPane.
If the user keeps his caret at the last position of the abbreviation, and presses CTRL + ENTER, the program must replace that abbreviation with a definition.
My question is, how would that be done, would somebody be able to make a demo to demonstrate that?
I am aware that keyListener is not the best approach here.
Update A few hours later:
I managed to get it working finally.
I will write down my working demo down below, I hope it will save you the headache that I had to go through while working on it.
GIF Demonstration:
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
/**
* JTextPane with capability to:
* -To keep the correct amount of tabs on new line when user types Enter
* -Dynamically add abbreviations
* -To replace abbreviation with its definition when user keeps caret at last letter of the abbreviation and taps CTRL + ENTER
* -To set position of the caret for each abbreviation after it is replaced by its definition
*/
public class AbbreviationJTextPane extends JTextPane {
public static void main(String[] args) {
JFrame frame = new JFrame();
// Create abbreviation JTextPane
AbbreviationJTextPane abbreviationPane = new AbbreviationJTextPane();
// Add abbreviations
abbreviationPane.addAbbreviation("sysout", "System.out.println();", 19); // 19 sets caret between parenthesis ( )
abbreviationPane.addAbbreviation("/**", "/*\n * \n * \n * /", 6); // 6 sets caret on second line after *
abbreviationPane.addAbbreviation("sob", "short of breath", 15);
// Add abbreviation JTextPane to JFrame
frame.add(abbreviationPane);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setTitle("Demo on abbreviation with CTRL + ENTER capability for JTextPane");
frame.setSize(720, 250);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private AbbreviationList abbreviations = new AbbreviationList();
public void addAbbreviation(String abbreviation, String definition, int setCaretPositionBack) {
abbreviations.add(new AbbreviationParameter(setCaretPositionBack, abbreviation, definition));
}
public AbbreviationJTextPane() {
// Add key detection functional
InputMap im = getInputMap();
ActionMap am = getActionMap();
// Add ENTER Listener - Replaces the way ENTER key works - new line will keep the same amount of tabs as on previous line
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "onEnter");
am.put("onEnter", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
onEnter();
}
});
// Add CTRL + ENTER Listener
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK), "onCTRL_ENTER");
am.put("onCTRL_ENTER", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
onCtrlEnter();
}
});
}
// ====================== LISTENERS
/**
* Overrides the way tapping Enter key functions.
* New line "\n" will keep the same amount of tabs "\t" as on previous line
*/
private void onEnter() {
newLineWithTabs();
}
/**
* Overrides the way tapping CTRL + ENTER functions
*/
private void onCtrlEnter() {
// The method below must be called like this to avoid bugs where expanding does
// not work on new line
expandAbbreviation(" ");
expandAbbreviation("\n");
expandAbbreviation("\t");
}
// ====================== FUNCTIONAL
/**
* Inserts a new line.
* New line "\n" will keep the same amount of tabs "\t" as on previous line
*/
private void newLineWithTabs() {
try {
// Get the entire text in the document
String text = getDocument().getText(0, getCaretPosition());
// Get the end index of the text
int end = text.length();
// Insert relevant amount of tabs on each new line:
String tabs = getTabsFromLine(text, end);
// Replace all new lines in definition with relevant amount of tabs
String definition = "\n".replace("\n", "\n"+tabs);
// Insert the definition into the document at the start index
getDocument().insertString(end, definition, null);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
/**
* This method is used to expand an abbreviation in the document based on the
* given word.
*
* #param lastIndexOf - a String representing the word that the abbreviation is
* being searched from
*/
private void expandAbbreviation(String lastIndexOf) {
try {
// Get the caret position in the document
int caretPosition = getCaretPosition();
Document doc = getDocument();
// Get the entire text in the document
String text = doc.getText(0, caretPosition);
// Get the start index of the word that is being searched
int start = text.lastIndexOf(lastIndexOf) + 1;
// Get the end index of the text
int end = text.length();
// Get the word that is being searched
String word = text.substring(start, end);
// Check if the abbreviations list contains the word and the caret position is
// at the end of the document
if (abbreviations.containsAbbreviation(word) && caretPosition == end) {
// Get the definition of the word from the abbreviations list
AbbreviationParameter inputParameter = abbreviations.getObjectForAbbreviation(word);
// If input parameter is null, this means that no such abbreviation exists in
// the list
if (inputParameter == null) {
return;
}
// Get definition from inputParameter
String definition = inputParameter.definition;
// Insert relevant amount of tabs on each new line:
String tabs = getTabsFromLine(text, end);
// Replace all new lines in definition with relevant amount of tabs
definition = definition.replace("\n", "\n"+tabs);
// Remove the word from the document
doc.remove(start, end - start);
// Insert the definition into the document at the start index
doc.insertString(start, definition, null);
// Set caret onto the appropriate index
setCaretPosition(start + inputParameter.caretPosition);
}
} catch (BadLocationException e) {
// No need to print anything as BadLocationException error will keep happening
// e.printStackTrace();
}
}
// ====================== UTILITY METHODS
/**
* Gets the tabs from line.
*
* #param text - the entire text in the document
* #param end - the end index of the text
* #return the tabs from line, string will be "" if there are no tabs at all
*/
private String getTabsFromLine(String text, int end) {
// Count all tabs in the line where caret is at present
int tabsCount = countCharacter(getLineAtIndex(text, end), '\t');
// Create String containing the amount of tabs necessary
StringBuilder tabs = new StringBuilder();
for(int iTab = 0; iTab < tabsCount; iTab++) {
tabs.append("\t");
}
return tabs.toString();
}
/**
* Returns the full line of a given index in a string.
*
* #param input the input string
* #param index the index in the input string
* #return the full line of the given index
*/
public static String getLineAtIndex(String input, int index) {
// Find the start index of the line
int start = input.lastIndexOf("\n", index) + 1;
// Find the end index of the line
int end = input.indexOf("\n", index);
if (end == -1) {
end = input.length();
}
// Return the substring from the start to the end of the line
return input.substring(start, end);
}
/**
* Count the number of a specified character in a string.
*
* #param s the input string
* #param c the character to count
* #return the number of occurrences of the character in the string
*/
public static int countCharacter(String s, char c) {
int count = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == c) {
count++;
}
}
return count;
}
/**
* Count the number of occurrences of a specified string in another string.
*
* #param input the input string
* #param target the string to count
* #return the number of occurrences of the target string in the input string
*/
public static int countString(String input, String target) {
int count = 0;
int index = 0;
while ((index = input.indexOf(target, index)) != -1) {
count++;
index += target.length();
}
return count;
}
}
/**
* Constructor class for abbreviations, their definition, and expected caret
* location after use
*
*/
class AbbreviationParameter {
/**
* This value is meant to indicate how many chars forward the caret should be
* placed onto after definition is placed.
*/
final int caretPosition;
final String abbreviation;
final String definition;
public AbbreviationParameter(int caretPosition, String abbreviation, String definition) {
this.caretPosition = caretPosition;
this.abbreviation = abbreviation;
this.definition = definition;
}
/**
* Gets the definition.
*
* #return the definition
*/
public String getDefinition() {
return definition;
}
}
/**
* List with all abbreviations and their values
*/
class AbbreviationList extends ArrayList<AbbreviationParameter> {
private static final long serialVersionUID = -7332763119043404932L;
/**
* Checks if list contains given abbreviation.
*
* #param abbreviation - the abbreviation
* #return true, if successful
*/
public boolean containsAbbreviation(String abbreviation) {
ArrayList<AbbreviationParameter> list = this;
for (AbbreviationParameter stringInputParameter : list) {
if (stringInputParameter.abbreviation.equals(abbreviation)) {
return true;
}
}
return false;
}
/**
* Gets the object for abbreviation.
*
* #param abbreviation - the abbreviation
* #return the {#link #AbbreviationList} object for abbreviation given if match is found, will return null if no such abbreviation is found
*/
public AbbreviationParameter getObjectForAbbreviation(String abbreviation) {
ArrayList<AbbreviationParameter> list = this;
for (AbbreviationParameter stringInputParameter : list) {
if (stringInputParameter.abbreviation.equals(abbreviation)) {
return stringInputParameter;
}
}
return null;
}
}
question
How to get each individual replacement result from a Regex replacement?
ex
String regexMatchedWord = matcher.group(); allows me to access the current matched result;
But is there something like String regexMatchedSubstitution = matcher.currentMatchedReplacementResult(); allows me to access the current replacement result?
public class Test {
public static void main(String[] args) {
String content_SearchOn = "Sample sentence: snake, snail, snow, spider";
String regexStrSubstitution = "$2$3$1";
String regexStrMatchFor = "(s)(.)(.)";
Matcher matcher = Pattern.compile(regexStrMatchFor).matcher(content_SearchOn);
ArrayList<String> arr_regexMatchedWord = new ArrayList<>();
ArrayList<String> arr_regexMatchedSubstitution = new ArrayList<>();
StringBuilder sb_content_Replaced = new StringBuilder();
while (matcher.find()) {
String regexMatchedWord = matcher.group();
arr_regexMatchedWord.add(regexMatchedWord);
matcher.appendReplacement(sb_content_Replaced, regexStrSubstitution);
String regexMatchedSubstitution = null; // << What should I put here -- to get each replacement result?
arr_regexMatchedSubstitution.add(regexMatchedSubstitution);
}
matcher.appendTail(sb_content_Replaced);
System.out.println(sb_content_Replaced); // Sample enstence: naske, nasil, nosw, pisder
System.out.println(arr_regexMatchedWord); // [sen, sna, sna, sno, spi]
System.out.println(arr_regexMatchedSubstitution); // [ens, nas, nas, nos, pis] // << expect
}
}
comments
if Java is not able to do this, is there any other language able to? (Javascript? Python?)
Update: potential solution (workaround)
(as talked in the comment) A simple possible way might be:
convert those $1 into group(1) programmatically,
but you have to watch out for the escape characters like \ that has special meaning...
Another way might be:
use Reflection to somehow get the local variable result in the source code appendExpandedReplacement(replacement, result); of java.util.regex.Matcher.appendReplacement(StringBuilder, String)
public Matcher appendReplacement(StringBuilder sb, String replacement) {
// If no match, return error
if (first < 0)
throw new IllegalStateException("No match available");
StringBuilder result = new StringBuilder();
appendExpandedReplacement(replacement, result);
// Append the intervening text
sb.append(text, lastAppendPosition, first);
// Append the match substitution
sb.append(result);
lastAppendPosition = last;
modCount++;
return this;
}
Or:
Record the end index before the append & count from that index to get the Appended Replacement after the append.
solution (workaround) Java implementation
#logic::
Record the end index before the append & count from that index to get the Appended Replacement after the append.
#code::
public class Test {
public static void main(String[] args) {
String content_SearchOn = "Sample sentence: snake, snail, snow, spider";
String regexStrSubstitution = "$2$3x$1";
String regexStrMatchFor = "(s)(.)(.).";
Matcher matcher = Pattern.compile(regexStrMatchFor).matcher(content_SearchOn);
ArrayList<String> arr_regexMatchedWord = new ArrayList<>();
ArrayList<String> arr_regexMatchedSubstitution = new ArrayList<>();
StringBuilder sb_content_SearchOn = new StringBuilder(content_SearchOn);
StringBuilder sb_content_Replaced = new StringBuilder();
String content_OriPlusCurrAppendSubsti = null;
StringBuilder sb_CurrAppendSubsti_buffer = null;
int indStart_g0_curr = -1;
int indEnd_g0_curr = -1;
int indStart_g0_prev = -1;
int indEnd_g0_prev = -1;
while (matcher.find()) {
// #>>>#
String regexMatchedWord = matcher.group();
indStart_g0_curr = matcher.start();
indEnd_g0_curr = matcher.end();
arr_regexMatchedWord.add(regexMatchedWord);
// #>>>
// #main[business logic]::
// <strike> length_sb_content_Replaced_prev = sb_content_Replaced.length();
// <strike> String regexMatchedSubstitution = sb_content_Replaced.substring(length_sb_content_Replaced_prev);
// #note: it appends both the `the intervening text` + `the match substitution` ...
//need_check,need_confrim_recall if multi call? // matcher.appendReplacement(new StringBuilder(), regexStrSubstitution); // ok its broken, so cant
//~ matcher.appendReplacement(sb_content_Replaced, regexStrSubstitution);
sb_CurrAppendSubsti_buffer = new StringBuilder();
matcher.appendReplacement(sb_CurrAppendSubsti_buffer, regexStrSubstitution + "_$0");
sb_content_Replaced.append(sb_CurrAppendSubsti_buffer);
// #main;;
// #>>>
// #main[get the individual replacement result]::
//~ String regexMatchedSubstitution = null; // << What should I put here -- to get each replacement result?
if (indEnd_g0_prev == -1) {
content_OriPlusCurrAppendSubsti = "";
} else {
content_OriPlusCurrAppendSubsti = sb_content_SearchOn.substring(0, indEnd_g0_prev);
}
content_OriPlusCurrAppendSubsti += sb_CurrAppendSubsti_buffer;
String regexMatchedSubstitution = content_OriPlusCurrAppendSubsti.substring(indStart_g0_curr);
arr_regexMatchedSubstitution.add(regexMatchedSubstitution);
// #main;;
// #>>>#
indStart_g0_prev = indStart_g0_curr;
indEnd_g0_prev = indEnd_g0_curr;
}
matcher.appendTail(sb_content_Replaced);
//
System.out.println(sb_content_Replaced); // Sample enxs_sentence: naxs_snake, naxs_snail, noxs_snow, pixs_spider
System.out.println(arr_regexMatchedWord); // [sent, snak, snai, snow, spid]
System.out.println(arr_regexMatchedSubstitution); // [enxs_sent, naxs_snak, naxs_snai, noxs_snow, pixs_spid] // << expect
}
}
solution (workaround) Javascript implementation
#logic::
simply brute force with hardcode string delimiter indicator in regex
replaceAll() -- add brackets around the matched replacement during replacement
matchAll() -- search the matched replacement that was enclosed in the brackets
#code (moved from specific example to a general class [here])::
class RegexUtil {
// https://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
// https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
/**
* #param {String} literal_string
* #returns {String}
*/
static escapeRegex(literal_string) {
return literal_string.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g, '\\$&');
}
/**
* #param {String} string
* #returns {String}
*/
static escapeRegexReplacement(string) {
return string.replace(/\$/g, '$$$$');
}
/**
* #param {String} content_SearchOn
* #param {RegExp} regexMatchFor
* #param {String} regexStrSubstitution
* #param {String} regexFlag
* #returns {String[]}
*/
static get_RegexMatchedReplacement(content_SearchOn, regexMatchFor, regexStrSubstitution) {
const arr_regexMatchedSubstitution = [];
let time_now;
let delim_regexMatchedSub_left;
let delim_regexMatchedSub_right;
/** #type {IterableIterator<RegExpMatchArray>} */ let itr;
let i = 0;
do {
i++;
if (i === 50) {
throw new Error('Many loops tried, Unable to brute force with hardcode string indicator in regex. (The chance of this happening is nearly impossible.)');
}
time_now = Date.now();
delim_regexMatchedSub_left = '#drmsL' + time_now + ';';
delim_regexMatchedSub_right = '#drmsR' + time_now + ';';
itr = content_SearchOn.matchAll(new RegExp(RegexUtil.escapeRegex(delim_regexMatchedSub_left) + '|' + RegexUtil.escapeRegex(delim_regexMatchedSub_right), 'g'));
} while (itr.next().done !== true);
const content_Replaced_WithDelimiter = content_SearchOn.replaceAll(regexMatchFor, RegexUtil.escapeRegexReplacement(delim_regexMatchedSub_left) + regexStrSubstitution + RegexUtil.escapeRegexReplacement(delim_regexMatchedSub_right));
itr = content_Replaced_WithDelimiter.matchAll(new RegExp(RegexUtil.escapeRegex(delim_regexMatchedSub_left) + '(.*?)' + RegexUtil.escapeRegex(delim_regexMatchedSub_right), 'gs')); // need flag s
for (const matcher_curr of itr) {
arr_regexMatchedSubstitution.push(matcher_curr[1]);
}
return arr_regexMatchedSubstitution;
}
}
#code (moved from specific example [here] to a general class)::
class RegexUtil {
// https://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
// https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
/**
* #param {String} literal_string
* #returns {String}
*/
static escapeRegex(literal_string) {
return literal_string.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g, '\\$&');
}
/**
* #param {String} string
* #returns {String}
*/
static escapeRegexReplacement(string) {
return string.replace(/\$/g, '$$$$');
}
}
//think aga, to use a generic way to escape special meaning delimiter in regex ...
const content_SearchOn = 'Sample sentence: snake, snail, snow, spider';
let regexStrSubstitution = '$2$3x$1';
const regexStrMatchFor = '(s)(.)(.).';
const regexFlag = 'gmd';
regexStrSubstitution += '_$&';
const arr_regexMatchedWord = [];
const arr_regexMatchedSubstitution = [];
let time_now;
let delim_regexMatchedSub_left;
let delim_regexMatchedSub_right;
/** #type {IterableIterator<RegExpMatchArray>} */ let itr;
let i = 0;
do {
i++;
if (i === 50) {
throw new Error('Many loops tried, Unable to brute force with hardcode string indicator in regex. (The chance of this happening is nearly impossible.)');
}
time_now = Date.now();
delim_regexMatchedSub_left = '#drmsL' + time_now + ';';
delim_regexMatchedSub_right = '#drmsR' + time_now + ';';
itr = content_SearchOn.matchAll(new RegExp(RegexUtil.escapeRegex(delim_regexMatchedSub_left) + '|' + RegexUtil.escapeRegex(delim_regexMatchedSub_right), 'g'));
} while (itr.next().done !== true);
const content_Replaced_WithDelimiter = content_SearchOn.replaceAll(new RegExp(regexStrMatchFor, regexFlag), RegexUtil.escapeRegexReplacement(delim_regexMatchedSub_left) + regexStrSubstitution + RegexUtil.escapeRegexReplacement(delim_regexMatchedSub_right));
itr = content_Replaced_WithDelimiter.matchAll(new RegExp(RegexUtil.escapeRegex(delim_regexMatchedSub_left) + '(.*?)' + RegexUtil.escapeRegex(delim_regexMatchedSub_right), 'gs')); // need flag s
for (const matcher_curr of itr) {
arr_regexMatchedSubstitution.push(matcher_curr[1]);
}
itr = content_SearchOn.matchAll(new RegExp(regexStrMatchFor, regexFlag));
for (const matcher_curr of itr) {
arr_regexMatchedWord.push(matcher_curr[0]);
}
const content_Replaced = content_SearchOn.replaceAll(new RegExp(regexStrMatchFor, regexFlag), regexStrSubstitution);
console.log(content_Replaced); // Sample enxs_sentence: naxs_snake, naxs_snail, noxs_snow, pixs_spider
console.log(arr_regexMatchedWord); // [sent, snak, snai, snow, spid]
console.log(arr_regexMatchedSubstitution); // [enxs_sent, naxs_snak, naxs_snai, noxs_snow, pixs_spid] // << expect
comment (minor)
The reason to brute force it with hardcode string indicator in regex is that,
Javascript is even worse at:
not providing the appendReplacement()
replacer callback function does not support those $1
Note: The above-mentioned special replacement patterns do not apply for strings returned from the replacer function.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_function_as_the_replacement
replacer callback function does not support those $1
Which makes this following idea useless (could have work) (complex & low performance) ::
for (const matcher_curr of itr_matcher) {
ind_ReplaceOnlyCurrOne++;
let ind_Match = -1;
function replace_OnlyOneWord_c_for_get_regexMatchedSubstitution(...args) {
ind_Match++;
/** #type {String} */ const g0 = args[0];
if (ind_Match === ind_ReplaceOnlyCurrOne) {
// prettier-ignore
let arg_last = args.at(-1); let ind_g0; let content_SearchOn; let groups;
// prettier-ignore
if (typeof arg_last === 'string') { content_SearchOn = arg_last; ind_g0 = args.at(-2); } else { groups = arg_last; content_SearchOn = args.at(-2); ind_g0 = args.at(-3); }
arr_regexMatchedWord.push(g0);
indStart_g0 = ind_g0;
indEnd_g0 = ind_g0 + g0.length;
return replacer_main(args);
} else {
return RegexUtil.escapeRegexReplacement(g0);
}
}
const content_ReplacedOnlyCurrOne__P1_Pm_P2 = content_SearchOn.replaceAll(new RegExp(regexStrMatchFor, regexFlag), replace_OnlyOneWord_c_for_get_regexMatchedSubstitution);
const Pm_P2 = content_ReplacedOnlyCurrOne__P1_Pm_P2.slice(indStart_g0);
const P2 = content_SearchOn.slice(indEnd_g0);
const regexMatchedSubstitution__Pm = Pm_P2.replaceAll(new RegExp(RegexUtil.escapeRegexp(P2)+'$', 'g'), '');
arr_regexMatchedSubstitution.push(regexMatchedSubstitution__Pm);
}
You can use replaceAll(Function<MatchResult, String> replacer) on a Matcher to "intercept" the replacement:
String input = "Sample sentence: snake, snail, snow, spider";
List<String> matches = new ArrayList<>();
String result = Pattern.compile("(s)(.)(.)").matcher(input)
.replaceAll(mr -> {
matches.add(mr.group());
return mr.group(2) + mr.group(3) + mr.group(1);
});
System.out.println(result);
System.out.println(matches);
Output:
Sample enstence: naske, nasil, nosw, pisder
[sen, sna, sna, sno, spi]
I wanted to mock a static method used inside a private method of a class and return a specific value.
public class Test {
private String encodeValue(String abc) {
try {
return URLEncoder.encode(...);
} catch (UnsupportedEncodingException e) {
throw InvalidValueException.create("Error converting");
}
}
URLEncoder.encode ->encode is a static method inside URLEncoder.
In Test class using powermock works:
PowerMock.mockStatic(URLEncoder.class);
expect(URLEncoder.encode()).andThrow(new UnsupportedEncodingException());
PowerMock.replay(URLEncoder.class);
String encoded = Whitebox.invokeMethod(testMock,"encodeVaue","Apple Mango");
But i wanted to replace Powermock with any other mocking ways available.
Is there a way to mock the above class.
URL Encoder class:
/**
* Translates a string into {#code application/x-www-form-urlencoded}
* format using a specific encoding scheme. This method uses the
* supplied encoding scheme to obtain the bytes for unsafe
* characters.
* <p>
* <em><strong>Note:</strong> The <a href=
* "http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars">
* World Wide Web Consortium Recommendation</a> states that
* UTF-8 should be used. Not doing so may introduce
* incompatibilities.</em>
*
* #param s {#code String} to be translated.
* #param enc The name of a supported
* <a href="../lang/package-summary.html#charenc">character
* encoding</a>.
* #return the translated {#code String}.
* #exception UnsupportedEncodingException
* If the named encoding is not supported
* #see URLDecoder#decode(java.lang.String, java.lang.String)
* #since 1.4
*/
public static String encode(String s, String enc)
throws UnsupportedEncodingException {
boolean needToChange = false;
StringBuffer out = new StringBuffer(s.length());
Charset charset;
CharArrayWriter charArrayWriter = new CharArrayWriter();
if (enc == null)
throw new NullPointerException("charsetName");
try {
charset = Charset.forName(enc);
} catch (IllegalCharsetNameException e) {
throw new UnsupportedEncodingException(enc);
} catch (UnsupportedCharsetException e) {
throw new UnsupportedEncodingException(enc);
}
for (int i = 0; i < s.length();) {
int c = (int) s.charAt(i);
//System.out.println("Examining character: " + c);
if (dontNeedEncoding.get(c)) {
if (c == ' ') {
c = '+';
needToChange = true;
}
//System.out.println("Storing: " + c);
out.append((char)c);
i++;
} else {
// convert to external encoding before hex conversion
do {
charArrayWriter.write(c);
/*
* If this character represents the start of a Unicode
* surrogate pair, then pass in two characters. It's not
* clear what should be done if a bytes reserved in the
* surrogate pairs range occurs outside of a legal
* surrogate pair. For now, just treat it as if it were
* any other character.
*/
if (c >= 0xD800 && c <= 0xDBFF) {
/*
System.out.println(Integer.toHexString(c)
+ " is high surrogate");
*/
if ( (i+1) < s.length()) {
int d = (int) s.charAt(i+1);
/*
System.out.println("\tExamining "
+ Integer.toHexString(d));
*/
if (d >= 0xDC00 && d <= 0xDFFF) {
/*
System.out.println("\t"
+ Integer.toHexString(d)
+ " is low surrogate");
*/
charArrayWriter.write(d);
i++;
}
}
}
i++;
} while (i < s.length() && !dontNeedEncoding.get((c = (int) s.charAt(i))));
charArrayWriter.flush();
String str = new String(charArrayWriter.toCharArray());
byte[] ba = str.getBytes(charset);
for (int j = 0; j < ba.length; j++) {
out.append('%');
char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16);
// converting to use uppercase letter as part of
// the hex value if ch is a letter.
if (Character.isLetter(ch)) {
ch -= caseDiff;
}
out.append(ch);
ch = Character.forDigit(ba[j] & 0xF, 16);
if (Character.isLetter(ch)) {
ch -= caseDiff;
}
out.append(ch);
}
charArrayWriter.reset();
needToChange = true;
}
}
return (needToChange? out.toString() : s);
}
Mocking privates and statics is one of the chief strengths of JMockit over other mocking frameworks.
The class you call "Test" is really the "ClassUnderTest", so apologies, but the test of "Test" is "TestTest" :)
public class TestTest {
#Tested
public Test cut;
#Test
public void testencodeValue() {
// Mock the static
new MockUp<URLEncoder>() {
#Mock
String encode(String s, String enc) {
return "JMockit FTW";
}
};
// invoke the private method
final Method method = MethodReflection.findCompatibleMethod(Test.class, "encodeValue", new Class<?>[] { String.class });
final String res = MethodReflection.invoke(cut, method);
assertEquals("JMockit FTW", res);
}
}
That said, testing privates is sort of a PITA. I am generally of the mind that if a method is worth testing, it is almost certainly worth exposing. The same criteria that make the method worth testing means that somebody-somewhere-someday will want to override your implementation and provide a slightly alternative one. Make their job easy, and make it protected. Make your (testing) job easy, and do the same thing.
So I'm trying to build myself my own programming language with swedish syntax(most for fun) and I have successfully managed to create variables and do some basic arithmetic on them. Moving on to do some conditional statements, I ran into some trouble. To start off, here is my (relevant) code:
SPL.g4 //My grammar file.
grammar SPL;
file
:code
;
code
:statement ';' code
;
statement
:print
|assign
|decl
|loop
|condition
;
//Declares variable
decl
:'variabel' ID
;
//Assign variable with an expression.
assign
:ID '=' expr
;
//A general expression.
expr
:'-' expr #unaryMinusExpr
|'!' expr #notExpr
|expr op=('/' | '*') expr #multiplicationExpr
|expr op=('+' | '-') expr #additiveExpr
|expr op=('<=' |'>=' | '<' | '>') expr #relationalExpr
|expr op=('=' | '!=') expr #equalityExpr
|expr 'OCH' expr #andExpr
|expr 'ELLER' expr #orExpr
|atomExpr #atom
;
//An atomic expression, more or less just a variable.
atomExpr
:ID
|INT
|'(' expr ')'
;
ID:('a'..'z')+ ;
STR:([a-zA-Z_] [a-zA-Z_0-9]*)+ ;
INT:([0-9]+)+ ;
WS: [ \n\t\r]+ -> skip ;
//Prints expr to the console
print
: 'skriv' expr
;
//Supposed to be some kind of loop and condition WORK IN PROGRESS!
condition
:'om' conditionCode ('annars om' conditionCode)* ('annars' otherBlock)?
;
conditionCode
: expr otherBlock
;
otherBlock
:'{'file'}'
| code
;
loop
:'när' expr ';'
;
Interpreter
import grammar.SPLBaseListener;
import grammar.SPLParser;
import org.antlr.v4.runtime.Token;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
/**
* Interpretes the code genereated by the parser.
* Overrides methods from the TestBaseListener so we can decide what happends.
* Created by student on 2017-01-27.
*
*/
public class Interpreter extends SPLBaseListener {
/**
* A class that holds a value. Used for storing key & value pairs in a hashmap.
*/
private static class Variable {
int val;
public Variable(int val) {
this.val = val;
}
}
private final HashMap<String, Variable> vars = new HashMap<>(); //Stores keys and values.
private Stack<Integer> exprVal = new Stack<>(); //Stores the current values.
private Stack<Boolean> conditionBooleans = new Stack<>(); //Stores the evaluated conditions
/**
* Returns a variable from the hashmap given the variable's name.
*
* #param tok
* #return
*/
private Variable getVar(Token tok) {
String name = tok.getText();
Variable v = vars.get(name);
if (v == null) {
error(tok.getLine(), "undefined " + name);
return new Variable(0); // avoid null pointer exception
} else {
return v;
}
}
/**
* When entering a declaration of a variable, we put it in the hashtable with it's name
* and a initial value of 0.
*
* #param ctx
*/
#Override
public void enterDecl(SPLParser.DeclContext ctx) {
String name = ctx.ID().getText();
Variable old = vars.put(name, new Variable(0));
if (old != null) {
error(ctx.ID().getSymbol().getLine(), "redefined " + name);
}
}
/**
* When we have exited the assignment, the variable already stored, get's the
* value that the stack is holdning.
*
* #param ctx
*/
#Override
public void exitAssign(SPLParser.AssignContext ctx) {
getVar(ctx.ID().getSymbol()).val = exprVal.pop();
}
/**
* If + add two numbers top of stack else substract them and add them back.
*
* #param ctx
*/
#Override public void exitAdditiveExpr(SPLParser.AdditiveExprContext ctx) {
String op = ctx.op.getText();
if(op.equals("+")){
exprVal.push(exprVal.pop() + exprVal.pop());
}else{ // '-'
int top = (exprVal.pop());
int second = (exprVal.pop());
exprVal.push(second - top);
}
}
/**
* If * multiplty two numbers top of stack else divide them and add them back.
*
* #param ctx
*/
#Override public void exitMultiplicationExpr(SPLParser.MultiplicationExprContext ctx) {
String op = ctx.op.getText();
if(op.equals("*")){
exprVal.push(exprVal.pop() * exprVal.pop());
}else{// '/'
int top = (exprVal.pop());
int second = (exprVal.pop());
exprVal.push(second / top);
}
}
/**
* Takes the two top elements from the stack and compares them.
* #param ctx
*/
#Override public void exitRelationalExpr(SPLParser.RelationalExprContext ctx) {
System.out.println("Im here checking relation! " + exprVal.size());
int top = (exprVal.pop());
int second = (exprVal.pop());
switch (ctx.op.getText()) {
case "<=":
System.out.println("<=");
if(top<=second)
conditionBooleans.push(true);
break;
case ">=":
System.out.println(">=");
if(top>=second)
conditionBooleans.push(true);
break;
case "<":
System.out.println("<");
if(top<second)
conditionBooleans.push(true);
break;
case ">":
System.out.println(">");
if(top>second)
conditionBooleans.push(true);
break;
default:
conditionBooleans.push(false);
}
}
/**
* Decides what codeblock to run. TODO DOES NOT WORK!
* #param ctx
*/
#Override public void enterCondition(SPLParser.ConditionContext ctx) {
System.out.println("\n CONDITION ENTERED------------------------------------------------------------------");
System.out.println(ctx.conditionCode().get(0).getText());
}
/**
* When a atomexpression is declared in code
* and if it's not null it will be pushed onto the stack.
*
* #param ctx
*/
#Override
public void enterAtomExpr(SPLParser.AtomExprContext ctx) {
if (ctx.ID() != null) {
exprVal.push(getVar(ctx.ID().getSymbol()).val);
} else if (ctx.INT() != null) {
exprVal.push(Integer.parseInt(ctx.INT().getText()));
}
}
/**
* Prints the value of exprValue.
*
* #param ctx
*/
#Override
public void exitPrint(SPLParser.PrintContext ctx) {
System.out.println("Utskrift:");
System.out.println(exprVal.pop());//Pop off the top value and print it.
}
/**
* Prints error message to the console.
*
* #param line
* #param msg
*/
private void error(int line, String msg) {
System.err.println(":" + line + ": " + msg);
}
}
test.t //the test file I'm using
variabel a;
variabel b;
a = 10;
b = 20;
a = a + b;
om a < b{
skriv a;
};
So when the interpreter reaches the if statement, that is first getting evaluated, and then it goes on to evaluate the expression. I am not sure of the order here and what to do. I believe I should save the value of the condition so I can retrieve it later on and decide on which codeblock to run. But I'm totaly lost on where to even begin.
The assignment consists in decompress a string. In particular, the code has to work for 3 samples as illustrated in the picture.
My code here works in the first 2 of the samples. However, I am not able to come up with the 3rd sample. Probably I did not understand probably the concept of recursion. Can you help me?
import java.util.Scanner;
public class Compression4 {
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
String input=in.next();
System.out.println(uncompress(input));
}
public static boolean flag = true;
public static String uncompress(String compressedText)
{
return uncompress(compressedText, "", "");
}
public static String getMultiple(String x, int N) {
if (N == 0) return "";
return ""+x+getMultiple(x,N-1);
}
public static String uncompress(String text, String count, String output)
{
if (text.equals(""))
{
return output;
}
if(text.charAt(0) == '(')
{
int FirstIndex = text.indexOf("(")+1;
String inner = text.substring(FirstIndex, text.lastIndexOf(")"));
//System.out.println(inner);
flag = false;
return uncompress (inner, count, output);
}
else if (Character.isLetter(text.charAt(0)))
{
//letter case - need to take the count we have accrued, parse it into an integer and add to output
if (flag==true)
{
//System.out.println(count);// * text.charAt(0);
String s = String.valueOf(text.charAt(0));
output += getMultiple(s,Integer.parseInt(count));
count ="1";
}
else
{
//System.out.println(count);// * text.charAt(0);
output += getMultiple(text,Integer.parseInt(count));
//System.out.println("output: "+output);
count="0";
}
}
else if(Character.isDigit(text.charAt(0)))
{
//digit case - need to add to the count but keep as a string because must be parsed later
if(flag)
count += (""+text.charAt(0));
else
{
count = "0";
count += (""+text.charAt(0));
}
}
//parse the *remainder* of the string, one character at a time, so pass in the substring(1)
return uncompress(text.substring(1), count, output);
}
}
Sorry for the long code but it's more easy to explain with code than with words.
Premise:
I think to the problem as an interpreter of a language to render a string
the language is simple and functional so recursive interpretation is possible
Algorithm phases:
First: tokenize the expression (to work at an higher level of abstraction)
Second: parse the expression just tokenized
Recursion: the logic is based on the syntax of the language. Key concepts of a recursion:
the base cases and the recursive cases
the state necessary to a single recursion (local variables of recursion, those passed as parameters to the recursive method)
the state for the all recursion (global variables of recursion, those read/write in some specific recursion)
I've made many comments to explain what the algorithm is doing. If it's not clear I can explain it better.
import java.util.ArrayList;
import java.util.List;
public class TestStringDecompression {
// simpleExpr examples: a | b | 123a | 123b | 123(a) | 123(ab) | 123(ba) | (ab) | (ba)
// 11ab = aaaaaaaaaaab = = expression = simpleExpr simpleExpr = 11a b
// 4(ab) = abababab = expression = simpleExpr = 4(ab)
// 2(3b3(ab)) = bbbabababbbbababab = expression = compositeExpr = 2 ( simpleExpr simpleExpr ) = 2 ( 3b 3(ab) )
public static void main(String[] args) {
System.out.println(new StringInflater().inflate("11ab"));
System.out.println(new StringInflater().inflate("4(ab)"));
System.out.println(new StringInflater().inflate("2(3b3(ab))"));
}
public static class StringInflater {
// This store the position of the last parsed token
private int posLastParsedToken = 0;
public String inflate(String expression) {
return parse(tokenize(expression), 0, false);
}
/**
* Language tokens:
* <ul>
* <li>literals:
* <ul>
* <li>intLiteral = [0-9]*</li>
* <li>charLiteral = [ab]</li>
* </ul>
* </li>
* <li>separators:
* <ul>
* <li>leftParen = '('</li>
* <li>rightParen = ')'</li>
* </ul>
* </li>
* </ul>
*/
private Object[] tokenize(String expression) {
List<Object> tokens = new ArrayList<Object>();
int i = 0;
while (i < expression.length()) {
if ('0' <= expression.charAt(i) && expression.charAt(i) <= '9') {
String number = "";
while ('0' <= expression.charAt(i) && expression.charAt(i) <= '9' && i < expression.length()) {
number += expression.charAt(i++);
}
tokens.add(Integer.valueOf(number));
} else {
tokens.add(expression.charAt(i++));
}
}
return tokens.toArray(new Object[tokens.size()]);
}
/**
* Language syntax:
* <ul>
* <li>simpleExpr = [intLiteral] charLiteral | [intLiteral] leftParen charLiteral+ rightParen</li>
* <li>compositeExpr = [intLiteral] leftParen (simpleExpr | compositeExpr)+ rightParen</li>
* <li>expression = (simpleExpr | compositeExpr)+</li>
* </ul>
*/
private String parse(Object[] tokens, int pos, boolean nested) {
posLastParsedToken = pos;
String result = "";
if (tokens[pos] instanceof Integer) {
/** it's a intLiteral */
// get quantifier value
int repetition = (int) tokens[pos];
// lookahead for (
if (tokens[pos + 1].equals("(")) {
// composite repetition, it could be:
// simpleExpr: "[intLiteral] leftParen charLiteral+ rightParen"
// compositeExpr: "[intLiteral] leftParen (simpleExpr | compositeExpr)+ rightParen"
result = parse(tokens, pos + 1, true);
} else {
// simple repetition, it could be:
// simpleExpr: [intLiteral] charLiteral
result = parse(tokens, pos + 1, false);
}
result = repeat(result, repetition);
// evaluate the rest of the expression because syntax allows it
if (posLastParsedToken + 1 == tokens.length) {
// end of the expression
return result;
} else {
// there are other simpleExpr or compositeExpr to parse
return result + parse(tokens, posLastParsedToken + 1, false);
}
} else if (tokens[pos].equals('(')) {
/** it's a leftParen */
// an open paren means what follow this token is considered nested (useful for string to treat as char sequence)
return parse(tokens, pos + 1, true);
} else if (tokens[pos].equals(')')) {
/** it's a rightParen */
// a closed paren, nothing to render
return "";
} else {
/** it's a charLiteral */
if (nested) {
// it's nested between paren, so more parsing is requested to consume next charLiteral or next simpleExpr or compositeExpr
return tokens[pos] + parse(tokens, pos + 1, nested);
} else {
// it's not nested between paren, return charLiteral as is
return "" + tokens[pos];
}
}
}
private String repeat(String s, int repetition) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < repetition; i++) {
result.append(s);
}
return result.toString();
}
}
}