Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions chapter_5/exercise_5_10/expr.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ void push(float element);

int main(int argc, char *argv[]) {
char Error = 0;
int op_count = 0;

// Using int instead of size_t for loop variable to match signed argc
// This prevents sign comparison warnings
Expand All @@ -29,19 +30,23 @@ int main(int argc, char *argv[]) {
char op = *argv[i];
switch (op) {
case '+':
op_count++;
push(number1 + number2);
break;

case '-':
op_count++;
push(number1 - number2);
break;

case '*': // This char might require to be escaped when passed
// as an argument.
op_count++;
push(number1 * number2);
break;

case '/':
op_count++;
if (number2 == 0) {
Error = 4;
} else {
Expand Down Expand Up @@ -87,15 +92,20 @@ int main(int argc, char *argv[]) {

return EXIT_FAILURE;
}

printf("result: %.3f", pop());

if (op_count == 0 && argc > 2) {
printf("Error: Operator is missing!\n");
return EXIT_FAILURE;
} else {
printf("result: %.3f", pop());
}

return EXIT_SUCCESS;
}

float pop(void) {
if (stack_pointer > 0) {
return stack[stack_pointer--];
return stack[--stack_pointer];
}

printf("Error: the stack is empty.\n");
Expand All @@ -104,7 +114,7 @@ float pop(void) {

void push(float element) {
if (stack_pointer < STACK_SIZE) {
stack[++stack_pointer] = element;
stack[stack_pointer++] = element;
} else {
printf("Error: the stack is full.\n");
}
Expand Down
87 changes: 87 additions & 0 deletions chapter_5/exercise_5_10/expr_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/bin/bash

# check if file is present and executable
if [ ! -f expr ]; then
echo "File 'expr' found!"
exit 1
fi

if [ ! -x expr ]; then
echo "File 'expr' not executable!"
exit 1
fi

echo "=== Testing expr calculator ==="

# Test 1: Simple addition
T=1
result=$(./expr 2 3 +)
expected="result: 5.000"
if [[ "$result" == "$expected" ]]; then
echo "Test $T PASS: 2 + 3 = 5"
else
echo "Test $T FAIL: Expected '$expected', got '$result'"
fi

# Test 2: Commutativity of addition
T=2
result=$(./expr 3 2 +)
expected="result: 5.000"
if [[ "$result" == "$expected" ]]; then
echo "Test $T PASS: 3 + 2 = 5"
else
echo "Test $T FAIL: Expected '$expected', got '$result'"
fi

# Test 3: Subtraction
T=3
result=$(./expr 10 3 -)
expected="result: 7.000"
if [[ "$result" == "$expected" ]]; then
echo "Test $T PASS: 10 - 3 = 7"
else
echo "Test $T FAIL: Expected '$expected', got '$result'"
fi

# Test 4: Complex expression
T=4
# ------------------------------------
# Expected:
# 5 --> [5]
# 1 --> [5, 1]
# 2 --> [5, 1, 2]
# + --> (1 + 2 = 3) --> [5, 3]
# 4 --> [5, 3, 4]
# * --> (3 * 4 = 12) --> [5, 12]
# + --> (5 + 12 = 17) --> [17]
# 3 --> [17, 3]
# - --> (17 - 3 = 14) --> [14]
# ------------------------------------
result=$(./expr 5 1 2 + 4 \* + 3 -)
expected="result: 14.000"
if [[ "$result" == "$expected" ]]; then
echo "Test $T PASS: Complex RPN expression"
else
echo "Test $T FAIL: Expected '$expected', got '$result'"
fi

# Test 5: Division by zero
T=5
result=$(./expr 5 0 / 2>&1)
if [[ "$result" == *"division by zero"* ]]; then
echo "Test $T PASS: Division by zero detected"
else
echo "Test $T FAIL: Should detect division by zero"
fi

# Test 6: Missing operator
T=6
result=$(./expr 5 1)
expected="Error: Operator is missing!"
if [[ "$result" == "$expected" ]]; then
echo "Test $T PASS: Missing operator"
else
echo "Test $T FAIL: Expected '$expected', got '$result'"
fi
Comment on lines +79 to +85
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Test 6 expected output is inconsistent with error message formatting.

The test expects "Error: Operator is missing!" without a trailing newline. While this matches the current behavior of expr.c (line 91), it's inconsistent with:

  • Other error messages in expr.c which include \n
  • The general convention that command-line tools print newlines after output

This is a minor cosmetic issue, but fixing it in both expr.c and this test would improve consistency.

Related: The suggested fix in the review comment for expr.c lines 91-93 addresses this by adding proper validation and a newline to the result output.

🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 79-79: 'expr' expects 3+ arguments, but sees 2. Make sure each operator/operand is a separate argument, and escape <>&|.

(SC2307)

🤖 Prompt for AI Agents
In chapter_5/exercise_5_10/expr_test.sh around lines 79 to 85, the test expects
the error string "Error: Operator is missing!" without a trailing newline which
is inconsistent with other error messages and the intended fix in expr.c; update
the test to expect a newline-terminated string (e.g., "Error: Operator is
missing!\n") or use a comparison that ignores trailing newlines (for example,
compare trimmed output) so the test matches the corrected expr.c behavior that
prints the error message followed by a newline.


echo "=== Tests Complete ==="