Programming Forums

Programming Forums (http://www.programmingforums.org/forumindex.php)
-   Existing Project Development (http://www.programmingforums.org/forum51.html)
-   -   Hybrid python shell (http://www.programmingforums.org/showthread.php?t=6031)

Arevos Sep 22nd, 2005 4:37 PM

Hybrid python shell
 
I've been interested in unix shell design for a while now, ever since I attempted to redesign Bash during uni. It didn't turn out that well at the time, but I did come to the opinion that the problem with Bash was not Bash per se, but its environment. Piping is a rather limited way of passing data between commands, and error codes are also pretty limiting.

So I began to think, what if the key functionality of a shell, such as file copying, file reading, file searching etc. were part of the shell, rather than external executables. External software could still be executed, but it wouldn't be native to the shell; you'd need a "run" or "exec" command.

I didn't think about this further for about a year or so, but recently I've been thinking about it again. It occurs to me that Python has all the functionality Bash has, and a lot more besides. But whilst Python is powerful, its syntax is less convenient than Bash; "cp a b" is a lot easier to type than "copy('a', 'b')"

And this is where Harpy comes in; it provides a concise, lispish syntax that can access callable python objects, and has the ability to import python modules.

In case anyone's still interested after all that spiel, I'll give you an example of Harpy usage, and the code for Harpy 0.1. Harpy needs PyParsing to run.

Example: Let's say you have a module "test.py":
:

import os

def echo(*l):
  for s in l:
      print s,
  print

def list_files(directory = "."):
  return os.listdir(directory)

The functions in this module can be accessed in harpy:
:

$ import test
$ test:echo hello world
hello world
$ from test
$ echo thanks for all the fish
thanks for all the fish
$ echo (list_files)
['test.pyc', 'test.py', 'harpy.py']
$ echo (list_files).__len__
<method-wrapper object at 0xb7c1404c>
$ echo ((list_files).__len__)
3
$ echo (list_files /)
['bin', 'dev', 'etc', 'lib', 'mnt', 'opt', 'srv', 'tmp', 'sys', 'var', 'usr',
'.dev', 'boot', 'home', 'proc', 'sbin', 'root', 'cdrom', 'media',
'initrd.img', 'initrd', 'vmlinuz']

Code of Harpy 0.1 (163 lines, 3.4k):
:

# Harpy 0.1 (jreeves@monkeyengines.co.uk)
import sys
import readline
import traceback

from pyparsing import *

def evaluate(expression):
  if hasattr(expression, '__eval__'):
      return expression.__eval__()
  else:
      return expression

class Interpreter:

  def __init__(self, namespace = {}):
      self.parse = self.__parser__()
     
      self.__namespace__ = namespace
      self.__namespace__['__interpreter__'] = self
      self.__namespace__.update(self.__builtins__())


  def load_module(self, name):
      module = __import__(name).__dict__
      try:
        module.update(module['__namespace__'])
      except KeyError:
        pass
      return module


  def __builtins__(self):
      def import_module(name):
        self.__namespace__[name] = self.load_module(name)

      def from_module(name, members = []):
        module = self.load_module(name)
        if members == []:
            self.__namespace__.update(module)
        else: 
            for member in members:
              self.__namespace__[member] = module[member]
             
      return { 'import' : import_module, 'from' : from_module }

 
  class Command:
      def __init__(self, namespace, element):
        self.namespace = namespace
        self.element  = element

      def __eval__(self):
        command = evaluate(self.element[0])
        if type(command) is str:
            command = self.namespace[command]
           
        for value in self.element[1:]:
            command = evaluate(getattr(command, value))
        return command

 
  class Argument:
      def __init__(self, element):
        self.element = element

      def __eval__(self):
        element = evaluate(self.element[0])
        for value in self.element[1:]:
            element = evaluate(getattr(element, value))

        return element
 
 
  class List:
      def __init__(self, head, tail):
        self.command  = head
        self.arguments = tail

      def __eval__(self):
        return evaluate(self.command)(
            *[evaluate(arg) for arg in self.arguments])
 
 
  def __parser__(self):     
      word  = CharsNotIn('"\'(): \n')
      quote = quotedString
      atom  = word | quote

      list    = Forward()
      molecule = Suppress('(') + list + Suppress(')')
      object  = atom | molecule
     
      expression = object + ZeroOrMore(Suppress(':') + atom)

      head = Empty() + expression
      tail = ZeroOrMore(expression)

      list << head + tail
     
      line = StringStart() + list + StringEnd()
      line.ignore("#" + restOfLine)

      def action(function):
        def wrapper(string, location, tokens):
            return function(tokens)
        return wrapper

      def quote_action(tokens):
        return [tokens[0][1:-1]]

      def head_action(tokens):
        return self.Command(self.__namespace__, tokens[0])

      def tail_action(tokens):
        return [[self.Argument(t) for t in tokens]]
       
      def list_action(tokens):
        return self.List(tokens[0], tokens[1])
     
      def expression_action(tokens):
        return [tokens.asList()]

      quote.setParseAction( action(quote_action) )
      head.setParseAction( action(head_action) )
      tail.setParseAction( action(tail_action) )
      list.setParseAction( action(list_action) )
      expression.setParseAction( action(expression_action) )

      def parser(string):
        return line.parseString(string)[0]

      return parser


  def prompt(self):
      if not sys.stdin.isatty():
        return ""
      else:
        return "$ "


  def interact(self):
      while True:
        try:
            line = raw_input(self.prompt())
        except EOFError:
            break
           
        self.push(line)


  def push(self, line):
      try:
        parse_tree = self.parse(line)
        evaluate(parse_tree)
      except:
        traceback.print_exc()


if __name__ == '__main__':
  shell = Interpreter()
  shell.interact()



All times are GMT -5. The time now is 9:13 PM.

Powered by vBulletin® Version 3.7.0, Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.
Copyright ©2007 DaniWeb® LLC