From a30ca2fd4d5f3c9be88af635cc2b3f08891ed9cf Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 13 Dec 2018 20:08:02 +0200 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20regex?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ownlang/modules/regex/MatcherValue.java | 171 ++++++++++++++++++ .../ownlang/modules/regex/PatternValue.java | 62 +++++++ .../annimon/ownlang/modules/regex/regex.java | 61 +++++++ src/test/resources/modules/regex/match.own | 15 ++ .../modules/regex/replaceCallback.own | 8 + 5 files changed, 317 insertions(+) create mode 100644 src/main/java/com/annimon/ownlang/modules/regex/MatcherValue.java create mode 100644 src/main/java/com/annimon/ownlang/modules/regex/PatternValue.java create mode 100644 src/main/java/com/annimon/ownlang/modules/regex/regex.java create mode 100644 src/test/resources/modules/regex/match.own create mode 100644 src/test/resources/modules/regex/replaceCallback.own diff --git a/src/main/java/com/annimon/ownlang/modules/regex/MatcherValue.java b/src/main/java/com/annimon/ownlang/modules/regex/MatcherValue.java new file mode 100644 index 0000000..c8dd925 --- /dev/null +++ b/src/main/java/com/annimon/ownlang/modules/regex/MatcherValue.java @@ -0,0 +1,171 @@ +package com.annimon.ownlang.modules.regex; + +import com.annimon.ownlang.exceptions.TypeException; +import com.annimon.ownlang.lib.*; +import java.util.regex.Matcher; + +public class MatcherValue extends MapValue { + + private final Matcher value; + + public MatcherValue(Matcher value) { + super(22); + this.value = value; + init(); + } + + private void init() { + set("start", this::start); + set("end", this::end); + set("find", this::find); + set("group", this::group); + set("pattern", this::pattern); + set("region", this::region); + set("replaceFirst", this::replaceFirst); + set("replaceAll", this::replaceAll); + set("replaceCallback", this::replaceCallback); + set("reset", this::reset); + set("usePattern", this::usePattern); + set("useAnchoringBounds", this::useAnchoringBounds); + set("hasAnchoringBounds", Converters.voidToBoolean(value::hasAnchoringBounds)); + set("useTransparentBounds", this::useTransparentBounds); + set("hasTransparentBounds", Converters.voidToBoolean(value::hasTransparentBounds)); + set("hitEnd", Converters.voidToBoolean(value::hitEnd)); + set("lookingAt", Converters.voidToBoolean(value::lookingAt)); + set("matches", Converters.voidToBoolean(value::matches)); + set("groupCount", Converters.voidToInt(value::groupCount)); + set("regionStart", Converters.voidToInt(value::regionStart)); + set("regionEnd", Converters.voidToInt(value::regionEnd)); + } + + private Value start(Value[] args) { + Arguments.checkOrOr(0, 1, args.length); + final int result; + if (args.length == 0) { + result = value.start(); + } else { + final Value arg = args[0]; + if (arg.type() == Types.NUMBER) { + result = value.start(arg.asInt()); + } else { + result = value.start(arg.asString()); + } + } + return NumberValue.of(result); + } + + private Value end(Value[] args) { + Arguments.checkOrOr(0, 1, args.length); + final int result; + if (args.length == 0) { + result = value.end(); + } else { + final Value arg = args[0]; + if (arg.type() == Types.NUMBER) { + result = value.end(arg.asInt()); + } else { + result = value.end(arg.asString()); + } + } + return NumberValue.of(result); + } + + private Value find(Value[] args) { + Arguments.checkOrOr(0, 1, args.length); + final boolean result; + if (args.length == 0) { + result = value.find(); + } else { + result = value.find(args[0].asInt()); + } + return NumberValue.fromBoolean(result); + } + + private Value group(Value[] args) { + Arguments.checkOrOr(0, 1, args.length); + final String result; + if (args.length == 0) { + result = value.group(); + } else { + final Value arg = args[0]; + if (arg.type() == Types.NUMBER) { + result = value.group(arg.asInt()); + } else { + result = value.group(arg.asString()); + } + } + return new StringValue(result); + } + + private Value pattern(Value[] args) { + return new PatternValue(value.pattern()); + } + + private Value region(Value[] args) { + Arguments.check(2, args.length); + value.region(args[0].asInt(), args[1].asInt()); + return this; + } + + private Value replaceFirst(Value[] args) { + Arguments.check(1, args.length); + return new StringValue(value.replaceFirst(args[0].asString())); + } + + private Value replaceAll(Value[] args) { + Arguments.check(1, args.length); + return new StringValue(value.replaceAll(args[0].asString())); + } + + static StringValue replaceCallback(MatcherValue matcherValue, Function callback) { + final Matcher matcher = matcherValue.value; + final StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + String replacement = callback.execute(matcherValue).asString(); + matcher.appendReplacement(sb, replacement); + } + matcher.appendTail(sb); + return new StringValue(sb.toString()); + } + + private Value replaceCallback(Value[] args) { + Arguments.check(1, args.length); + if (args[0].type() != Types.FUNCTION) { + throw new TypeException(args[0].toString() + " is not a function"); + } + return replaceCallback(this, ((FunctionValue) args[0]).getValue()); + } + + private Value reset(Value[] args) { + Arguments.checkOrOr(0, 1, args.length); + if (args.length == 0) { + value.reset(); + } else { + value.reset(args[0].asString()); + } + return this; + } + + private Value useAnchoringBounds(Value[] args) { + Arguments.check(1, args.length); + value.useAnchoringBounds(args[0].asInt() != 0); + return this; + } + + private Value useTransparentBounds(Value[] args) { + Arguments.check(1, args.length); + value.useTransparentBounds(args[0].asInt() != 0); + return this; + } + + private Value usePattern(Value[] args) { + Arguments.check(1, args.length); + final Value arg = args[0]; + if (arg.type() == Types.MAP && (arg instanceof PatternValue)) { + value.usePattern(((PatternValue) arg).getValue()); + } else { + throw new TypeException("Pattern value expected"); + } + return this; + } +} diff --git a/src/main/java/com/annimon/ownlang/modules/regex/PatternValue.java b/src/main/java/com/annimon/ownlang/modules/regex/PatternValue.java new file mode 100644 index 0000000..e39598c --- /dev/null +++ b/src/main/java/com/annimon/ownlang/modules/regex/PatternValue.java @@ -0,0 +1,62 @@ +package com.annimon.ownlang.modules.regex; + +import com.annimon.ownlang.exceptions.TypeException; +import com.annimon.ownlang.lib.Arguments; +import com.annimon.ownlang.lib.ArrayValue; +import com.annimon.ownlang.lib.Converters; +import com.annimon.ownlang.lib.FunctionValue; +import com.annimon.ownlang.lib.MapValue; +import com.annimon.ownlang.lib.NumberValue; +import com.annimon.ownlang.lib.Types; +import com.annimon.ownlang.lib.Value; +import java.util.regex.Pattern; + +public class PatternValue extends MapValue { + + private final Pattern value; + + public PatternValue(Pattern value) { + super(8); + this.value = value; + init(); + } + + private void init() { + set("flags", Converters.voidToInt(value::flags)); + set("pattern", Converters.voidToString(value::pattern)); + set("matcher", this::matcher); + set("matches", this::matches); + set("split", this::split); + set("replaceCallback", this::replaceCallback); + } + + public Pattern getValue() { + return value; + } + + private Value split(Value[] args) { + Arguments.checkOrOr(1, 2, args.length); + final int limit = (args.length == 2) ? args[1].asInt() : 0; + return ArrayValue.of(value.split(args[0].asString(), limit)); + } + + private Value matcher(Value[] args) { + Arguments.check(1, args.length); + return new MatcherValue(value.matcher(args[0].asString())); + } + + private Value matches(Value[] args) { + Arguments.check(1, args.length); + return NumberValue.fromBoolean(value.matcher(args[0].asString()).matches()); + } + + private Value replaceCallback(Value[] args) { + Arguments.check(2, args.length); + if (args[1].type() != Types.FUNCTION) { + throw new TypeException(args[1].toString() + " is not a function"); + } + return MatcherValue.replaceCallback( + new MatcherValue(value.matcher(args[0].asString())), + ((FunctionValue) args[1]).getValue()); + } +} diff --git a/src/main/java/com/annimon/ownlang/modules/regex/regex.java b/src/main/java/com/annimon/ownlang/modules/regex/regex.java new file mode 100644 index 0000000..a7c0193 --- /dev/null +++ b/src/main/java/com/annimon/ownlang/modules/regex/regex.java @@ -0,0 +1,61 @@ +package com.annimon.ownlang.modules.regex; + +import com.annimon.ownlang.lib.Arguments; +import com.annimon.ownlang.lib.ArrayValue; +import com.annimon.ownlang.lib.Functions; +import com.annimon.ownlang.lib.MapValue; +import com.annimon.ownlang.lib.NumberValue; +import com.annimon.ownlang.lib.StringValue; +import com.annimon.ownlang.lib.Value; +import com.annimon.ownlang.lib.Variables; +import com.annimon.ownlang.modules.Module; +import java.util.regex.Pattern; + +public final class regex implements Module { + + public static void initConstants() { + MapValue map = new MapValue(20); + map.set("UNIX_LINES", NumberValue.of(Pattern.UNIX_LINES)); + map.set("I", NumberValue.of(Pattern.CASE_INSENSITIVE)); + map.set("CASE_INSENSITIVE", NumberValue.of(Pattern.CASE_INSENSITIVE)); + map.set("COMMENTS", NumberValue.of(Pattern.COMMENTS)); + map.set("M", NumberValue.of(Pattern.MULTILINE)); + map.set("MULTILINE", NumberValue.of(Pattern.MULTILINE)); + map.set("LITERAL", NumberValue.of(Pattern.LITERAL)); + map.set("S", NumberValue.of(Pattern.DOTALL)); + map.set("DOTALL", NumberValue.of(Pattern.DOTALL)); + map.set("UNICODE_CASE", NumberValue.of(Pattern.UNICODE_CASE)); + map.set("CANON_EQ", NumberValue.of(Pattern.CANON_EQ)); + map.set("U", NumberValue.of(Pattern.UNICODE_CHARACTER_CLASS)); + map.set("UNICODE_CHARACTER_CLASS", NumberValue.of(Pattern.UNICODE_CHARACTER_CLASS)); + + map.set("quote", args -> { + Arguments.check(1, args.length); + return new StringValue(Pattern.quote(args[0].asString())); + }); + map.set("matches", args -> { + Arguments.check(2, args.length); + return NumberValue.fromBoolean(Pattern.matches(args[0].asString(), args[1].asString())); + }); + map.set("split", args -> { + Arguments.checkOrOr(2, 3, args.length); + final Pattern pattern = Pattern.compile(args[0].asString()); + final int limit = (args.length == 3) ? args[2].asInt() : 0; + return ArrayValue.of(pattern.split(args[1].asString(), limit)); + }); + map.set("compile", regex::compile); + Variables.define("Pattern", map); + } + + @Override + public void init() { + initConstants(); + Functions.set("regex", regex::compile); + } + + private static Value compile(Value[] args) { + Arguments.checkOrOr(1, 2, args.length); + final int flags = (args.length == 2) ? args[1].asInt() : 0; + return new PatternValue(Pattern.compile(args[0].asString(), flags)); + } +} diff --git a/src/test/resources/modules/regex/match.own b/src/test/resources/modules/regex/match.own new file mode 100644 index 0000000..24954fe --- /dev/null +++ b/src/test/resources/modules/regex/match.own @@ -0,0 +1,15 @@ +use ["regex", "types"] + +def testMatchGitUrl() { + pattern = Pattern.compile("https?://((git(hu|la)b\.com)|bitbucket\.org)/?") + assertTrue(pattern.matches("http://github.com")) + assertTrue(pattern.matches("http://github.com/")) + assertTrue(pattern.matches("https://gitlab.com/")) + assertTrue(pattern.matches("https://bitbucket.org/")) + + assertFalse(pattern.matches("http://github.org")) + assertFalse(pattern.matches("https://bithub.com/")) + assertFalse(pattern.matches("http://gitlab.org")) + assertFalse(pattern.matches("ftp://github.com/")) + assertFalse(pattern.matches("http://gitbucket.org/")) +} \ No newline at end of file diff --git a/src/test/resources/modules/regex/replaceCallback.own b/src/test/resources/modules/regex/replaceCallback.own new file mode 100644 index 0000000..cdfb525 --- /dev/null +++ b/src/test/resources/modules/regex/replaceCallback.own @@ -0,0 +1,8 @@ +use ["regex", "types"] + +def testReplaceCallback() { + in = "[1-2-3-4]" + pattern = regex("(\d)") + out = pattern.replaceCallback(in, def(m) = m.group() * int(m.group())) + assertEquals("[1-22-333-4444]", out) +} \ No newline at end of file