Skip to content
Merged
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
132 changes: 132 additions & 0 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import io
import sys
from think.interpreter import ThinkInterpreter
from think.errors import ThinkRuntimeError

@pytest.fixture
def capture_output():
Expand Down Expand Up @@ -145,6 +146,137 @@ def test_list_operations(self, interpreter, parser, capture_output):
interpreter.execute(ast)
assert interpreter.state['items'][0] == 0
assert interpreter.state['items'][2] == 2

def test_list_indexing(self, interpreter, parser, capture_output):
"""Test comprehensive list indexing operations."""
code = '''objective "Test list indexing"
task "ListIndexing":
step "Setup":
numbers = [10, 20, 30, 40, 50]

first = numbers[0]
last = numbers[4]

last_item = numbers[-1]
second_to_last = numbers[-2]

idx = 2
middle = numbers[idx]

expr_idx = numbers[1 + 1]

matrix = [[1, 2, 3], [4, 5, 6]]
nested_val = matrix[1][2]

calc_idx = 10 / 2 - 1
computed = numbers[calc_idx]

run "ListIndexing"'''

ast = parser.parse(code)
interpreter.execute(ast)

# Verify basic positive indexing
assert interpreter.state['first'] == 10, "First element incorrect"
assert interpreter.state['last'] == 50, "Last element incorrect"

# Verify negative indexing
assert interpreter.state['last_item'] == 50, "Negative indexing failed"
assert interpreter.state['second_to_last'] == 40, "Negative indexing failed"

# Verify variable as index
assert interpreter.state['middle'] == 30, "Variable index failed"

# Verify expression as index
assert interpreter.state['expr_idx'] == 30, "Expression index failed"

# Verify nested indexing
assert interpreter.state['nested_val'] == 6, "Nested indexing failed"

# Verify computed index
assert interpreter.state['computed'] == 50, "Computed index failed"

def test_list_indexing_errors(self, interpreter, parser, capture_output):
"""Test error cases for list indexing."""
# Test index out of bounds (positive)
code = '''objective "Test list index errors"
task "ListErrors":
step "OutOfBounds":
numbers = [1, 2, 3]
invalid = numbers[5]
run "ListErrors"'''

with pytest.raises(ThinkRuntimeError) as exc_info:
ast = parser.parse(code)
interpreter.execute(ast)
assert "Invalid index/key" in str(exc_info.value)

# Test index out of bounds (negative)
code = '''objective "Test negative index errors"
task "ListErrors":
step "NegativeOutOfBounds":
numbers = [1, 2, 3]
invalid = numbers[-4]
run "ListErrors"'''

with pytest.raises(ThinkRuntimeError) as exc_info:
ast = parser.parse(code)
interpreter.execute(ast)
assert "Invalid index/key" in str(exc_info.value)

# Test non-integer index
code = '''objective "Test non-integer index"
task "ListErrors":
step "NonInteger":
numbers = [1, 2, 3]
invalid = numbers[1.5]
run "ListErrors"'''

with pytest.raises(ThinkRuntimeError) as exc_info:
ast = parser.parse(code)
interpreter.execute(ast)
assert "Invalid index/key" in str(exc_info.value)

# Test invalid type for index
code = '''objective "Test invalid index type"
task "ListErrors":
step "InvalidType":
numbers = [1, 2, 3]
invalid = numbers["one"]
run "ListErrors"'''

with pytest.raises(ThinkRuntimeError) as exc_info:
ast = parser.parse(code)
interpreter.execute(ast)
assert "Invalid index/key" in str(exc_info.value)

def test_nested_list_indexing_errors(self, interpreter, parser, capture_output):
"""Test error cases for nested list indexing."""
# Test accessing index of non-list
code = '''objective "Test invalid nested indexing"
task "NestedErrors":
step "NonList":
numbers = [1, [2, 3], 4]
invalid = numbers[0][1]
run "NestedErrors"'''

with pytest.raises(ThinkRuntimeError) as exc_info:
ast = parser.parse(code)
interpreter.execute(ast)
assert "Cannot index into type" in str(exc_info.value)

# Test out of bounds on nested list
code = '''objective "Test nested out of bounds"
task "NestedErrors":
step "OutOfBounds":
matrix = [[1, 2], [3, 4]]
invalid = matrix[1][5]
run "NestedErrors"'''

with pytest.raises(ThinkRuntimeError) as exc_info:
ast = parser.parse(code)
interpreter.execute(ast)
assert "Invalid index" in str(exc_info.value)


class TestFunctions:
Expand Down
82 changes: 68 additions & 14 deletions think/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ def execute_statement(self, statement):
elif stmt_type == 'decide':
return self.execute_decide(statement)


def evaluate_expression(self, expr):
"""Evaluate an expression and return its value"""
# Handle direct values
Expand Down Expand Up @@ -352,36 +353,87 @@ def evaluate_expression(self, expr):
container = self.evaluate_expression(expr['container'])
key = self.evaluate_expression(expr['key'])

# Convert string literal to string if it's being used as a key
if isinstance(key, dict) and key.get('type') == 'string_literal':
key = key['value']

if isinstance(container, (dict, list)):
# Handle list indexing
if isinstance(container, list):
try:
if isinstance(container, list):
# First validate that key can be used as an index
if not isinstance(key, (int, float)) or isinstance(key, bool):
raise ThinkRuntimeError(
message=f"Invalid index/key: List indices must be integers, got {type(key).__name__}",
task=self.current_task,
step=self.current_step,
variables={
"attempted_key": key,
"key_type": type(key).__name__
}
)

# Convert float to int if it's a whole number
if isinstance(key, float):
if not key.is_integer():
raise ThinkRuntimeError(
message=f"Invalid index/key: List indices must be whole numbers, got {key}",
task=self.current_task,
step=self.current_step,
variables={
"attempted_key": key
}
)
key = int(key)

# Check bounds before accessing
if key >= len(container) or key < -len(container):
raise ThinkRuntimeError(
message=f"Invalid index/key: {key} is out of range for list of length {len(container)}",
task=self.current_task,
step=self.current_step,
variables={
"attempted_key": key,
"list_length": len(container),
"valid_range": f"-{len(container)} to {len(container)-1}"
}
)

return container[key]
except (KeyError, IndexError, ValueError) as e:

except (TypeError, ValueError) as e:
raise ThinkRuntimeError(
message=f"Invalid index/key: {key} for container {container}",
message=f"Invalid index/key: {key}",
task=self.current_task,
step=self.current_step,
variables={
"container_type": type(container).__name__,
"container_value": container,
"attempted_key": key,
"valid_keys": list(container.keys()) if isinstance(container, dict) else f"0-{len(container)-1}"
"error": str(e)
}
)

# Handle dictionary indexing
elif isinstance(container, dict):
try:
if isinstance(key, dict) and key.get('type') == 'string_literal':
key = key['value']
return container[key]
except KeyError:
raise ThinkRuntimeError(
message=f"Invalid index/key: {key} not found in dictionary",
task=self.current_task,
step=self.current_step,
variables={
"attempted_key": key,
"available_keys": list(container.keys())
}
)

# Handle invalid container types
else:
raise ThinkRuntimeError(
message=f"Cannot index into type: {type(container)}",
message=f"Cannot index into type: {type(container).__name__}",
task=self.current_task,
step=self.current_step,
variables={
"attempted_type": type(container).__name__,
"indexable_types": ["list", "dict", "string"],
"value_attempted": str(container)
"value": str(container),
"indexable_types": ["list", "dict"]
}
)

Expand All @@ -407,6 +459,8 @@ def evaluate_expression(self, expr):

return expr



def evaluate_operation(self, operation):
"""Evaluate a mathematical or logical operation"""
op = operation['operator']
Expand Down
Loading