[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