370 lines
11 KiB
Python
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()
|