Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent Behaviour of For Loop #33

Closed
geekstakulus opened this issue Dec 31, 2023 · 9 comments
Closed

Inconsistent Behaviour of For Loop #33

geekstakulus opened this issue Dec 31, 2023 · 9 comments

Comments

@geekstakulus
Copy link

Introduction

In Oberon-07 and its predecessors, the for loop is designed to iterate a fixed number of times. As such, the control variable within the loop should remain constant, and the expression following the "TO" keyword should also be fixed.

Current Behavior

Currently, the Oberonc compiler flags modifications to the control variable within the loop body as errors. However, it doesn't enforce the immutability of the expression following the "TO" keyword. Consider the following two code snippets:

MODULE ForLoop1;
	IMPORT Out;
	VAR
		n : INTEGER;
BEGIN
	FOR n := 1 TO 5 DO
		DEC(n);
		Out.String("i = ");
		Out.Int(n, 0);
		Out.Ln
	END
END ForLoop1.

This code fails to compile due to the attempt to decrement n, which triggers an error. This behavior is expected and thus considered correct.

However, consider the next code snippet:

MODULE ForLimit;
 IMPORT Out;
 VAR
   i, limit : INTEGER;
BEGIN
 limit := 4;

 FOR i := 1 TO limit + 1 DO
   DEC(limit);
   Out.Int(limit, 0);
   Out.String(", ");
   Out.Int(i, 0);
   Out.Ln
 END;
 Out.String(":");
 Out.Int(limit, 0);
 Out.Ln
END ForLimit.

This code compiles successfully, but produces unexpected output. The expression limit + 1 should only be evaluated once, but in this case, it appears to be reevaluated with each iteration of the loop, resulting in the following incorrect output:

3, 1
2, 2
1, 3
:1

Expected Behavior

The expected behavior would be for the expression limit + 1 to be evaluated only once, producing the following output:

3, 1
2, 2
1, 3
0, 4
-1, 5
:-1

Reproduction Steps

To reproduce this behavior:

  1. Compile the code using the Oberonc compiler.
  2. Observe that the compiler throws an error on the first example.
  3. Observe that the compiler compiles the code without error on the second example, but produces incorrect output.

Additional Information

Interestingly, compiling the same code with obc yields the correct output for the second example. However, it allows the modification of the control variable within the body of the for loop, which is contrary to the expected behavior. oberonc behaves similarly to Java and C.

@KevinRH
Copy link

KevinRH commented Dec 31, 2023 via email

@lboasso
Copy link
Owner

lboasso commented Dec 31, 2023 via email

@KevinRH
Copy link

KevinRH commented Dec 31, 2023 via email

@geekstakulus
Copy link
Author

Hello @KevinRH and @lboasso,

Firstly, happy new year! I appreciate all the comments and discussions on the topic. I've been reviewing the Oberon 07 Language Report, which, as is characteristic of Niklaus Wirth's writings, leaves room for interpretation. Section 9.8 states that "A for statement specifies the repeated execution of a statement sequence for a given number of times," which I took to mean the same behavior as in Modula-2 and Pascal: "... repeated execution of a statement sequence for a given number of numbers", in my opinion means that it shouldn't change mid course.

I admire Wirth's brilliance, but I also acknowledge his tendency towards laziness and inconsistency. In good engineering practice, specifications are written and adhered to strictly. However, when the implementation deviates from the stated specification due to laziness, it becomes challenging to understand how the system functions.

I stumbled upon the discrepancy regarding the for loop during my study of the Modula-2 compiler. I tested the for loop in the Oberon language using the oberonc compiler, only to find that it behaved like C and Java. And now you tell me that Wirth's RISC compiler behaved in the same way, which show that he hadn't adhered to his own guidelines once again. It appears he implemented the for loop differently in Oberon due to sheer laziness.

Unless there exists another document detailing that the for loop in the RISC compiler should function differently from his other languages, I would expect it to behave as described in the language specifications across all his languages. Interestingly, Wirth had originally removed the for loop from the original Oberon due to oversight.

While I greatly admire Wirth's contributions, I find it difficult to trust his writings as they often don't align with his implementations. This seems to contradict his reputation as a brilliant engineer.

Regarding the experimental features, rather than suggesting a patch, would creating a separate branch be more appropriate? Is this what you intended?

@lboasso
Copy link
Owner

lboasso commented Jan 2, 2024 via email

@geekstakulus
Copy link
Author

geekstakulus commented Jan 2, 2024

Please, improve the following:

Hi, @lboasso !

I have analyzed the specifications of the Oberon 7 programming language before providing my insight. The specs doesn't mention any restrictions on modifying the control variable or stating that the end expression should be fixed. This lack of explicitness leads to ambiguity, which in turn causes the oberonc compiler to flag an error when you attempt to modify the control variable, when the specs says something else.

Let's look at an example by converting the first example to an equivalent while loop as described in the specs:

MODULE WhileLoop1;
	IMPORT Out;
	VAR
		n : INTEGER;
BEGIN
	n := 1;
	WHILE n <= 5 DO
		DEC(n);
		Out.String("i = ");
		Out.Int(n, 0);
		Out.Ln;
		INC(n)
	END
END WhileLoop1.

This code will create an infinite loop because the decrement operation DEC(n) followed by the increment operation INC(n) makes the loop counter n always equal to 1. If you compile and execute the original for loop example with obc, it will present a behaviour equivalent to this while loop, which is correct based on the specs. However, oberonc reports this as an error, and based on the specs, it shouldn't flag an error, because the specs doesn't say anything about not being allowed to change the control variable inside the for loop body or not. The only reason you've done that is because:

  1. It causes an infinite loop, and based on that you opted for a defensive programming approach by forbidding it
  2. Wirth himself implemented it this way in his RISC based compiler

The exact same spec was found in Pascal and Modula-2, yet he opted to make the end expression fixed, but allowed the control variable to be modified inside of the loop body.

Therefore, based on the specs, and here I am referring to semantically equivalence, the correct behaviour should be:

  1. The control variable can be modified and that will produce an infinite loop
  2. The end expression can be modified and that will produce the same behaviour as in C and Java

From an engineering standpoint, I agree with your approach to the first example. However, I reject the ambiguity in the current specs. Consider a scenario where you write a specification for a critical system like an airplane or an automobile. If the implementation deviates from the specifications due to laziness or oversight, it could lead to catastrophic consequences. I believe that Wirth, who is indeed a genius, should have involved real engineers in writing and implementing the specifications, because, despite being a good engineer, his laziness renders a lot of ambiguous documents one cannot rely on.

Therefore, based on the defensive programming approach, let's just close the issue. I agree with the defensive programming approach, and that is how I would implement. However, in the second case, I would implement it as in the obc compiler, or Pascal, Modula-2, Rust and Kotlin.

I will close the issue.

Thanks for your insights!

@lboasso
Copy link
Owner

lboasso commented Jan 2, 2024 via email

@geekstakulus
Copy link
Author

While I personally prefer not to use uppercase keywords, I'm able to adapt to this convention. I utilize a custom plugin for NeoVim that transforms all text to uppercase, providing an appealing syntax highlighting feature.

In addition to this, I've conceptualized a programming language inspired by Oberon, but I've yet to dedicate significant time to its development. Instead, I've been engrossed in the task of developing a Modula-2 compiler, based on Wirth's original single-pass compiler. This is where we truly witness the brilliance of Wirth, despite his apparent lack of discipline. Many elements that may initially seem like chaotic or inefficient code were actually ingenious solutions to the limitations of hardware at the time. His compilers are true works of art, demonstrating simplicity and efficiency. The seemingly cryptic variable names were also strategic ways of navigating the hardware constraints. Upon examining the compiler, one can appreciate why he chose to store both identifiers and strings in a single array. This approach, which might seem counter-intuitive, becomes clear upon studying the architecture of the Lilith computer. Despite his occasional laziness and inconsistent documentation, even his books filled with errors, it's crucial to acknowledge that Wirth's genius is undeniable. My admiration for Wirth is quite strong.

My admiration for Dikstra, who was a meticulous engineer, is far stronger, because he paid considerable attention to the details. Both Wirth and Dikstra serve as excellent examples of extraordinary programmers, although Dikstra was far more professional and less inconsistent with his specs vs implementations.

Anyway... thanks once again for this little chat.

@geekstakulus
Copy link
Author

Hi again!

I was reading the specs for Modula-2, and surprisingly enough, it is very clear. Here it is:

The expressions before and after the symbol TO define the range through which the so-called control variable (i) progresses. An optional parameter determines the incrementing (decrementing) value. If it is omitted, 1 is assumed as default value. It is recommended that the for statement be used in simple cases only; in particular, no components of the expressions determining the range must be affected by the repeated statements, and, above all, the control variable itself must not be changed by the repeated statements. The value of the control variable must be considered as undefined after the for statement is terminated.

The specification underscores that "no components of the expressions determining the range must be affected by the repeated statements, and, above all, the control variable itself must not be changed by the repeated statements." However, in his compiler, the control variable isn't restricted from being modified. The specification also ensures that the second expression of the range isn't affected by the repeated statements. This he decided to implement in the compiler.

Anyway... the best thing is to read the specs and the compiler code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants