Skip to content

Commit

Permalink
edit
Browse files Browse the repository at this point in the history
  • Loading branch information
usfalami committed Aug 27, 2024
1 parent 8edf9fc commit c5521a9
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 81 deletions.
118 changes: 44 additions & 74 deletions src/main/java/org/usf/jquery/web/RequestParser.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package org.usf.jquery.web;

import static java.util.Collections.emptyList;
import static java.util.Objects.isNull;
import static java.util.Objects.requireNonNull;
import static org.usf.jquery.core.SqlStringBuilder.quote;
import static org.usf.jquery.core.Validation.VAR_PATTERN;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

/**
*
Expand All @@ -22,95 +21,72 @@ public final class RequestParser {
private char c;

private RequestParser(String s) {
this.s = s;
this.s = requireNonNull(s, "value is null");
this.size = s.length();
this.c = size == 0 ? 0 : s.charAt(idx);
}

public static RequestEntryChain parseEntry(String s) {
return new RequestParser(s).parseEntry();
return new RequestParser(s).parseEntries(false, c-> false).get(0);
}

public static List<RequestEntryChain> parseEntries(String s) {
return new RequestParser(s).parseEntries(false);
return s.isEmpty() ? emptyList() : new RequestParser(s).parseEntries(true, c-> false);
}

private List<RequestEntryChain> parseEntries(boolean inner) {
private List<RequestEntryChain> parseEntries(boolean multiple, CharPredicate until) {
var entries = new ArrayList<RequestEntryChain>();
entries.add(parseEntry(true));
while(c == ',') {
nextChar(true);
entries.add(parseEntry(true));
entries.add(parseEntry());
if(multiple) {
while(c == ',') {
nextChar(true);
entries.add(parseEntry());
}
}
if(idx == size || (inner && c == ')')) {
return entries.size() == 1 && isNull(entries.get(0).getValue()) //check this
? emptyList()
: entries;
if(idx == size || until.test(c)) {
return entries;
}
throw unexpectedCharException();
throw new EntrySyntaxException("unexpected character '" + c + "' at index=" + idx); //end
}

private RequestEntryChain parseEntry() {
var e = parseEntry(true);
if(idx == size) {
return e;
}
throw unexpectedCharException();
}

private RequestEntryChain parseEntry(boolean txt) {
RequestEntryChain entry = null;
if(c == '"') {
if(txt) {
nextChar(true);
var from = idx;
nextWhile(RequestParser::legalTxtChar); //accept any
requireChar('"'); //nextChar
entry = new RequestEntryChain(s.substring(from, idx), true); //no next, no args, no tag
nextChar(false);
}
}
else {
entry = new RequestEntryChain(nextVal());
if(legalLetter(c)) {
var entry = new RequestEntryChain(requireLegalIdentifier(nextWhile(RequestParser::legalVarChar)));
if(c == '(') { //operator
nextChar(true);
entry.setArgs(parseEntries(true)); // no args | null
requireChar(')'); //nextChar
entry.setArgs(parseEntries(true, c-> c==')')); //
requireChar(')');
nextChar(false);
}
if(c == '.') {
nextChar(true);
entry.setNext(parseEntry(false));
if(legalLetter(c)) { //require identifier after '.'
entry.setNext(parseEntry());
}
}
if(c == ':') {
nextChar(true);
entry.setTag(requireLegalVariable(nextVar().get()));
entry.setTag(requireLegalIdentifier(nextWhile(RequestParser::legalVarChar)));
}
return entry;
}
return entry;
}

private String nextVal() {
var from = idx;
var v = nextVar();
if((idx == size || c == '.' || c == ':') && from<idx && legalLetter(s.charAt(from))) { //^[a-zA-Z]
return v.get();
} //!variable
nextWhile(RequestParser::legalValChar); //continue as value
return from == idx ? null : s.substring(from, idx); // empty => null
if(c == '"') {
nextChar(true);
var txt = nextWhile(RequestParser::legalTxtChar);
requireChar('"');
nextChar(false);
return new RequestEntryChain(txt, true); //no next, no args, no tag
}
return new RequestEntryChain(legalNumber(c) || c == '-' ? nextWhile(RequestParser::legalValChar) : null); // decimal negative? & instant format
}

private Supplier<String> nextVar() {
private String nextWhile(CharPredicate cp) {
var from = idx;
nextWhile(RequestParser::legalVarChar);
return ()-> s.substring(from, idx);
}

private void nextWhile(CharPredicate cp) {
while(idx<size && cp.test(c=s.charAt(idx))) ++idx;
if(idx == size) {
c = 0;
}
return s.substring(from, idx);
}

private void nextChar(boolean require) {
Expand All @@ -120,11 +96,11 @@ private void nextChar(boolean require) {
else if(idx == size) {
c = 0;
if(require) {
throw somethingExpectedException();
throw new EntrySyntaxException("something expected after '" + s.charAt(size-1) + "'");
}
}
else {
throw new IllegalStateException("idx>size");
throw new ArrayIndexOutOfBoundsException("idx>size");
}
}

Expand All @@ -134,24 +110,14 @@ private void requireChar(char rc) {
}
}

private String requireLegalVariable(String s) {
private static String requireLegalIdentifier(String s) {
if(s.matches(VAR_PATTERN)) {
return s;
}
throw s.isEmpty() && idx < size
? unexpectedCharException()
: new EntrySyntaxException("illegal identifier : " + quote(s));
}

private EntrySyntaxException unexpectedCharException() {
return new EntrySyntaxException("unexpected character '" + c + "' at index=" + idx); //end
}

private EntrySyntaxException somethingExpectedException() {
return new EntrySyntaxException("something expected after '" + s.charAt(size-1) + "'");
throw new EntrySyntaxException("illegal identifier : " + quote(s));
}

private static boolean legalTxtChar(char c) { //avoid SQL injection & HTTP reserved symbol
private static boolean legalTxtChar(char c) { //avoid SQL / HTTP reserved symbol
return c != '"' && c != '\'' && c != '&' && c != '?' && c != '=';
}

Expand All @@ -160,13 +126,17 @@ private static boolean legalValChar(char c) {
}

private static boolean legalVarChar(char c) {
return legalLetter(c) || (c >= '0' && c <= '9') || c == '_';
return legalLetter(c) || legalNumber(c) || c == '_';
}

private static boolean legalLetter(char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}

private static boolean legalNumber(char c) {
return c >= '0' && c <= '9';
}

@FunctionalInterface
interface CharPredicate {
boolean test(char c);
Expand Down
14 changes: 7 additions & 7 deletions src/test/java/org/usf/jquery/web/RequestParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ class RequestParserTest {
"concat(v.c,\"abc\",\"123\")",
"mod(abs(trunc(exp())))",
"co1.mod(6.acc).trunc(3).plus(100)", //6.acc as value
"co1.concat(co1.trunc(2).string(10), 1234)", //6.acc as value
"co1.concat(co1.trunc(2).string(10),1234)", //6.acc as value
"co1.concat(,123)", //null value
"co1.concat(123,)", //null value
"co1.concat(, ,123,)", //null value
"co1.concat(,,123,)", //null value
"aa.fn(3.3,b,c,d)",
"aa.fn(,3.3,b,c,d,)",
"aa.fn(2020-01-01,b,c,d)",
Expand All @@ -46,9 +46,9 @@ void testParse(String s) {

@ParameterizedTest
@ValueSource(strings = {
"",
"12345",
"1name",
// "",
// "12345",
// "1name",
"_column",
"(column",
")column",
Expand All @@ -62,11 +62,11 @@ void testParse(String s) {
"column,",
"column:",
"column.",
"\"column\"",
// "\"column\"",
"function(arg",
"function(arg))",
"123(a,b,c)",
"123.abc",
// "123.abc",
"fn(3@c)",
"fn(\"",
"fn(\"arg",
Expand Down

0 comments on commit c5521a9

Please sign in to comment.