Files
secondo-py/pysecondo/repl.py
2026-01-24 11:30:02 +08:00

370 lines
11 KiB
Python

"""
Interactive REPL for PySECONDO
Provides an interactive shell for executing SECONDO queries.
"""
import sys
from typing import List
from pysecondo.algebras.base import AlgebraManager
from pysecondo.algebras.standard import StandardAlgebra
from pysecondo.algebras.relation import RelationAlgebra
from pysecondo.storage.memory import MemoryStorage
from pysecondo.parser.parser import Parser, CreateCommand, UpdateCommand, QueryCommand
from pysecondo.parser.evaluator import Evaluator
from pysecondo.core.types import parse_type
from pysecondo.core.nested_list import NestedList
class REPL:
"""
Read-Eval-Print Loop for PySECONDO
Provides an interactive interface for:
- Creating databases
- Querying data
- Exploring results
"""
def __init__(self):
"""Initialize the REPL with all components"""
self.storage = MemoryStorage()
self.algebra_manager = AlgebraManager()
# Register algebras
self.algebra_manager.register_algebra(
"StandardAlgebra", StandardAlgebra())
self.algebra_manager.register_algebra(
"RelationAlgebra",
RelationAlgebra(self.storage)
)
self.parser = Parser()
self.evaluator = Evaluator(self.algebra_manager, self.storage)
self.running = False
self.verbose = True
def print_banner(self):
"""Print welcome banner"""
print("=" * 60)
print(" PySECONDO - Interactive Shell")
print(" A minimal implementation of SECONDO in Python")
print("=" * 60)
print()
print("Commands:")
print(" create name : type - Create a relation")
print(" update name := value - Insert/update data")
print(" query expression - Execute a query")
print(" list - List all objects")
print(" type name - Show object type")
print(" help - Show this help")
print(" quit - Exit the shell")
print()
print("Examples:")
print(" create cities : (rel (tuple ((Name string)(Population int))))")
print(' update cities := (("Beijing" 21540000)("Shanghai" 24280000))')
print(" query cities feed count")
print(" query 5 + 3")
print()
def print_result(self, value: NestedList, max_depth: int = 3):
"""
Print a nested list result nicely
Args:
value: Nested list to print
max_depth: Maximum depth to display
"""
if value.is_atom():
print(f" {value.value}")
else:
self._print_list(
value, indent=2, max_depth=max_depth, current_depth=0)
def _print_list(self, value: NestedList, indent: int, max_depth: int, current_depth: int):
"""Recursively print a nested list"""
if current_depth >= max_depth:
print(" " * indent + "...")
return
print(" " * indent + "(")
for item in value.value:
if item.is_atom():
if isinstance(item.value, str):
print(" " * (indent + 2) + f'"{item.value}"')
else:
print(" " * (indent + 2) + str(item.value))
else:
self._print_list(item, indent + 2, max_depth,
current_depth + 1)
print(" " * indent + ")")
def execute(self, query: str) -> bool:
"""
Execute a query
Args:
query: Query string
Returns:
True if execution succeeded, False otherwise
"""
try:
# Parse the query
command = self.parser.parse(query)
if command is None:
print(f"Error: Could not parse query: {query}")
return False
# Handle different command types
if isinstance(command, CreateCommand):
return self.execute_create(command)
elif isinstance(command, UpdateCommand):
return self.execute_update(command)
elif isinstance(command, QueryCommand):
return self.execute_query(command)
else:
print(f"Error: Unknown command type")
return False
except Exception as e:
print(f"Error: {e}")
import traceback
if self.verbose:
traceback.print_exc()
return False
def execute_create(self, command: CreateCommand) -> bool:
"""Execute CREATE command"""
try:
# Check if object already exists
if self.storage.object_exists(command.name):
print(f"Error: Object '{command.name}' already exists")
return False
# Parse type
obj_type = parse_type(command.type_str)
# Create empty value
from pysecondo.core.types import RelationType, BaseType
if isinstance(obj_type, RelationType):
value = NestedList.list([])
elif obj_type == BaseType.INT:
value = NestedList.atom(0)
elif obj_type == BaseType.REAL:
value = NestedList.atom(0.0)
elif obj_type == BaseType.STRING:
value = NestedList.atom("")
elif obj_type == BaseType.BOOL:
value = NestedList.atom(False)
else:
value = NestedList.list([])
self.storage.create_object(command.name, value, obj_type)
print(f"Created: {command.name}")
return True
except Exception as e:
print(f"Error creating object: {e}")
return False
def execute_update(self, command: UpdateCommand) -> bool:
"""Execute UPDATE command"""
try:
# Parse the value (nested list)
# For simplicity, we use Python's eval with restrictions
# In production, use a proper parser
# Convert SECONDO syntax to Python
value_str = command.value
# Simple parsing: handle ("str" num) patterns
# This is a very basic parser
value = self._parse_nested_list(value_str)
if not self.storage.object_exists(command.name):
print(f"Error: Object '{command.name}' does not exist")
return False
obj_type = self.storage.get_type(command.name)
self.storage.update_object(command.name, value, obj_type)
print(f"Updated: {command.name}")
return True
except Exception as e:
print(f"Error updating object: {e}")
import traceback
traceback.print_exc()
return False
def _parse_nested_list(self, s: str) -> NestedList:
"""
Parse a nested list string
Very simple parser for ("value1" value2 ("nested" ...))
"""
s = s.strip()
if not s.startswith('(') or not s.endswith(')'):
# Atomic value
if s.startswith('"') and s.endswith('"'):
return NestedList.atom(s[1:-1])
try:
if '.' in s:
return NestedList.atom(float(s))
else:
return NestedList.atom(int(s))
except ValueError:
return NestedList.atom(s)
# Parse list
inner = s[1:-1].strip()
if not inner:
return NestedList.list([])
items = []
current = []
depth = 0
in_string = False
i = 0
while i < len(inner):
char = inner[i]
if char == '"' and (i == 0 or inner[i-1] != '\\'):
in_string = not in_string
current.append(char)
elif in_string:
current.append(char)
elif char == '(':
depth += 1
current.append(char)
elif char == ')':
depth -= 1
current.append(char)
elif char in ' \t\n' and depth == 0:
if current:
items.append(''.join(current))
current = []
else:
current.append(char)
i += 1
if current:
items.append(''.join(current))
# Parse each item recursively
parsed_items = []
for item in items:
parsed_items.append(self._parse_nested_list(item.strip()))
return NestedList.list(parsed_items)
def execute_query(self, command: QueryCommand) -> bool:
"""Execute QUERY command"""
try:
tokens = self.parser.parse_expression(command.expression)
value, value_type = self.evaluator.evaluate(tokens)
print("Result:")
self.print_result(value)
print()
return True
except Exception as e:
print(f"Error executing query: {e}")
import traceback
if self.verbose:
traceback.print_exc()
return False
def cmd_list(self, args: List[str]) -> bool:
"""List all objects"""
objects = self.storage.list_objects()
if not objects:
print("No objects defined.")
return True
print("Defined objects:")
for name in objects:
obj_type = self.storage.get_type(name)
print(f" {name}: {obj_type}")
print()
return True
def cmd_type(self, args: List[str]) -> bool:
"""Show type of an object"""
if len(args) < 1:
print("Usage: type <name>")
return False
name = args[0]
if not self.storage.object_exists(name):
print(f"Error: Unknown object '{name}'")
return False
obj_type = self.storage.get_type(name)
print(f"{name}: {obj_type}")
print()
return True
def cmd_help(self, args: List[str]) -> bool:
"""Show help"""
self.print_banner()
return True
def run(self):
"""Run the REPL loop"""
self.running = True
self.print_banner()
while self.running:
try:
# Read input
line = input("secondo> ").strip()
if not line:
continue
# Handle built-in commands
parts = line.split(None, 1)
cmd = parts[0].lower()
args = parts[1:] if len(parts) > 1 else []
if cmd in ('quit', 'exit', 'q'):
print("Goodbye!")
break
elif cmd == 'list':
self.cmd_list(args)
elif cmd == 'type':
self.cmd_type(args)
elif cmd == 'help':
self.cmd_help(args)
else:
# Execute query
self.execute(line)
except KeyboardInterrupt:
print("\nUse 'quit' to exit.")
except EOFError:
print("\nGoodbye!")
break
def main():
"""Main entry point"""
repl = REPL()
repl.run()
if __name__ == "__main__":
main()