[education/rkward] rkward: Implement further context move operators
Thomas Friedrichsmeier
null at kde.org
Mon May 26 15:06:38 BST 2025
Git commit 4807106a57481927d43942fb7268a0697eb3ceea by Thomas Friedrichsmeier.
Committed on 26/05/2025 at 14:06.
Pushed by tfry into branch 'master'.
Implement further context move operators
M +2 -2 rkward/autotests/data/script1.R
M +50 -0 rkward/autotests/rkparsedscript_test.cpp
M +80 -1 rkward/misc/rkparsedscript.cpp
M +9 -0 rkward/misc/rkparsedscript.h
https://invent.kde.org/education/rkward/-/commit/4807106a57481927d43942fb7268a0697eb3ceea
diff --git a/rkward/autotests/data/script1.R b/rkward/autotests/data/script1.R
index 9dd7f0a87..e405c1de5 100644
--- a/rkward/autotests/data/script1.R
+++ b/rkward/autotests/data/script1.R
@@ -22,8 +22,8 @@ FunctionList <- list(
{
nest1
{
- nest2
- nest3
+ nest2(nest2_inner + 1)
+ nest3()
nest4
}
nest5
diff --git a/rkward/autotests/rkparsedscript_test.cpp b/rkward/autotests/rkparsedscript_test.cpp
index 28e785e35..4a04a82f6 100644
--- a/rkward/autotests/rkparsedscript_test.cpp
+++ b/rkward/autotests/rkparsedscript_test.cpp
@@ -183,6 +183,56 @@ class RKParsedScriptTest : public QObject {
ctx = moveAndCheck(ps.prevStatement(ctx), u"{ aaa"_s);
ctx = moveAndCheck(ps.prevStatement(ctx), u"makeFunction"_s);
}
+
+ void nextPrevOuter() {
+ loadScript(u"script1.R"_s);
+ testLog("Block1");
+ auto ctx = ps.contextAtPos(script.indexOf(u"nest3"));
+ ctx = moveAndCheck(ps.nextOuter(ctx), u"nest5"_s);
+ ctx = moveAndCheck(ps.nextOuter(ctx), u"ddd"_s);
+ QVERIFY(!ps.nextOuter(ctx).valid());
+
+ testLog("Block2");
+ ctx = ps.contextAtPos(script.indexOf(u"nest3"));
+ ctx = moveAndCheck(ps.prevOuter(ctx), u"{"_s);
+ ctx = moveAndCheck(ps.prevOuter(ctx), u"{"_s);
+ ctx = moveAndCheck(ps.prevOuter(ctx), u"makeFunction"_s);
+ ctx = moveAndCheck(ps.prevOuter(ctx), u"FunctionList"_s);
+ QVERIFY(!ps.prevOuter(ctx).valid());
+ }
+
+ void nextPrevToplevel() {
+ loadScript(u"script1.R"_s);
+ testLog("Block1");
+ auto ctx = ps.contextAtPos(script.indexOf(u"Symbol09"));
+ ctx = moveAndCheck(ps.nextToplevel(ctx), u"Symbol19"_s);
+ ctx = moveAndCheck(ps.nextToplevel(ctx), u"Symbol.x"_s);
+ ctx = moveAndCheck(ps.nextToplevel(ctx), u"FunctionList"_s);
+ QVERIFY(!ps.nextToplevel(ctx).valid());
+
+ testLog("Block2");
+ ctx = ps.contextAtPos(script.indexOf(u"Symbol09"));
+ ctx = moveAndCheck(ps.prevToplevel(ctx), u"Symbol01"_s);
+ ctx = moveAndCheck(ps.prevToplevel(ctx), u"Symbol00"_s);
+ ctx = moveAndCheck(ps.nextToplevel(ctx), u"Symbol01"_s); // make sure, we can still advance from here
+ ctx = moveAndCheck(ps.prevToplevel(ctx), u"Symbol00"_s);
+ QVERIFY(!ps.prevToplevel(ctx).valid());
+ }
+
+ void nextPrevInner() {
+ loadScript(u"script1.R"_s);
+ testLog("Block1");
+ auto ctx = ps.contextAtPos(script.indexOf(u"nest5"));
+ ctx = moveAndCheck(ps.nextStatementOrInner(ctx), u"ddd"_s);
+ ctx = moveAndCheck(ps.nextStatementOrInner(ctx), u"eee"_s);
+ ctx = moveAndCheck(ps.nextStatementOrInner(ctx), u"ggg"_s);
+ ctx = moveAndCheck(ps.nextStatementOrInner(ctx), u"jjj"_s);
+
+ testLog("Block2");
+ ctx = ps.contextAtPos(script.indexOf(u"jjj"));
+ ctx = moveAndCheck(ps.prevStatementOrInner(ctx), u"ggg"_s);
+ ctx = moveAndCheck(ps.prevStatementOrInner(ctx), u"eee"_s); // TODO: or should it be "fff"?
+ }
};
QTEST_MAIN(RKParsedScriptTest)
diff --git a/rkward/misc/rkparsedscript.cpp b/rkward/misc/rkparsedscript.cpp
index f7aed2c44..a3c2c12ec 100644
--- a/rkward/misc/rkparsedscript.cpp
+++ b/rkward/misc/rkparsedscript.cpp
@@ -200,7 +200,7 @@ RKParsedScript::ContextIndex RKParsedScript::firstContextInStatement(const Conte
if (!pi.valid()) break; // hit top end
const auto &pc = getContext(pi);
if (pc.type == Delimiter) break;
- // Consider reversing from "c" in "a + (b + c)" bs. "a + (b) + c": We want to stop at region
+ // Consider reversing from "c" in "a + (b + c)" vs. "a + (b) + c": We want to stop at region
// openings, too, *if* that region is a parent of this one
if (pc.maybeNesting() && pc.end > getContext(from).start) break;
}
@@ -227,6 +227,8 @@ RKParsedScript::ContextIndex RKParsedScript::nextStatement(const ContextIndex fr
// forward past end of current statement
auto ni = nextContext(lastContextInStatement(from));
+ // consider advancing from "b" in "a = (b + c) + d; e" -> should be e, not "+ d"
+ while (getContext(ni).type != Delimiter) ni = nextContext(lastContextInStatement(ni));
// skip over any following non-interesting contexts
while (true) {
auto type = getContext(ni).type;
@@ -251,6 +253,83 @@ RKParsedScript::ContextIndex RKParsedScript::prevStatement(const ContextIndex fr
return (firstContextInStatement(pi));
}
+RKParsedScript::ContextIndex RKParsedScript::nextOuter(const ContextIndex from) const {
+ RK_TRACE(MISC);
+ // this may not be a terribly efficient implementation, but a short one:
+ auto out = prevOuter(from);
+ return nextStatement(out);
+}
+
+RKParsedScript::ContextIndex RKParsedScript::prevOuter(const ContextIndex from) const {
+ RK_TRACE(MISC);
+ return firstContextInStatement(parentRegion(from));
+}
+
+RKParsedScript::ContextIndex RKParsedScript::nextToplevel(const ContextIndex from) const {
+ RK_TRACE(MISC);
+ auto ctx = from;
+ auto parent = parentRegion(ctx);
+ while (parent.valid() && getContext(parent).type != Top) {
+ ctx = prevOuter(ctx);
+ parent = parentRegion(ctx);
+ }
+ return nextStatement(ctx);
+}
+
+RKParsedScript::ContextIndex RKParsedScript::prevToplevel(const ContextIndex from) const {
+ RK_TRACE(MISC);
+ auto ctx = prevStatement(from);
+ auto parent = parentRegion(ctx);
+ while (parent.valid() && getContext(parent).type != Top) {
+ ctx = prevOuter(ctx);
+ parent = parentRegion(ctx);
+ }
+ return firstContextInStatement(ctx);
+}
+
+RKParsedScript::ContextIndex RKParsedScript::nextStatementOrInner(const ContextIndex from) const {
+ RK_TRACE(MISC);
+
+ auto nsi = nextStatement(from);
+ auto ci = nextContext(from);
+ while (ci.valid() && ci.index < nsi.index) {
+ auto ctx = getContext(ci);
+ // NOTE: We want to move into inner contexts, *unless* they are empty
+ if (ctx.maybeNesting()) {
+ auto candidate_inner = nextContext(ci);
+ if (parentRegion(candidate_inner) == ci) return candidate_inner;
+ }
+ ci = nextContext(ci);
+ }
+ return ci;
+}
+
+RKParsedScript::ContextIndex RKParsedScript::prevStatementOrInner(const ContextIndex from) const {
+ RK_TRACE(MISC);
+
+ auto psi = prevStatement(from);
+ auto ci = prevContext(from);
+ while (ci.valid() && ci.index > psi.index) {
+ auto ctx = getContext(ci);
+ if (ctx.maybeNesting()) {
+ auto candidate = nextContext(ci);
+ if (parentRegion(candidate) == ci && candidate != from) {
+ // found an inner region, but what's the last (inner!) statement in it?
+ // TODO: is there an easier solution?
+ auto next = candidate;
+ auto limit = nextOuter(candidate);
+ while (next.index < limit.index && next.index < ci.index) {
+ candidate = next;
+ next = nextStatementOrInner(candidate);
+ }
+ return candidate;
+ }
+ }
+ ci = prevContext(ci);
+ }
+ return ci;
+}
+
// NOTE: used in debugging, only
QString RKParsedScript::serialize() const {
QString ret;
diff --git a/rkward/misc/rkparsedscript.h b/rkward/misc/rkparsedscript.h
index faa71bfd1..8a5f61db7 100644
--- a/rkward/misc/rkparsedscript.h
+++ b/rkward/misc/rkparsedscript.h
@@ -91,6 +91,15 @@ class RKParsedScript {
ContextIndex firstContextInStatement(const ContextIndex from) const;
ContextIndex lastContextInStatement(const ContextIndex from) const;
+ ContextIndex nextOuter(const ContextIndex from) const;
+ ContextIndex prevOuter(const ContextIndex from) const;
+
+ ContextIndex nextToplevel(const ContextIndex from) const;
+ ContextIndex prevToplevel(const ContextIndex from) const;
+
+ ContextIndex nextStatementOrInner(const ContextIndex from) const;
+ ContextIndex prevStatementOrInner(const ContextIndex from) const;
+
/** retrieve the context at the given index. Safe to call, even with an invalid index
* (in which case the outermost context will be returned). */
const Context &getContext(const ContextIndex index) const {
More information about the rkward-tracker
mailing list