Click here to Skip to main content
15,882,152 members
Articles / Containers / Virtual Machine

Parsing Expression Grammar Support for C# 3.0 Part 1 - PEG Lib and Parser Generator

Rate me:
Please Sign up or sign in to vote.
4.95/5 (49 votes)
7 Oct 2008CPOL40 min read 202.5K   2.1K   118  
Introduction to the parsing method PEG with library and parser generator
#!/usr/local/bin/python

# tabview.py -- View a tab-delimited file in a spreadsheet-like display.
# Contributed by A.M. Kuchling <amk@amk.ca>
#
# The tab-delimited file is displayed on screen.  The highlighted
# position is shown in the top-left corner of the screen; below it are
# shown the contents of that cell.
#
#  Movement keys are:
#    Cursor keys: Move the highlighted cell, scrolling if required.
#    Q or q     : Quit
#    TAB        : Page right a screen
#    Home       : Move to the start of this line
#    End        : Move to the end of this line
#    PgUp/PgDn  : Move a page up or down
#    Insert     : Memorize this position
#    Delete     : Return to memorized position (if any)
#
# TODO : 
#    A 'G' for Goto: enter a cell like AA260 and move there
#    A key to re-read the tab-delimited file
#
# Possible projects:
#    Allow editing of cells, and then saving the modified data
#    Add formula evaluation, and you've got a simple spreadsheet
# program.  (Actually, you should allow displaying both via curses and
# via a Tk widget.)  
#

import curses, re, string

def yx2str(y,x):
    "Convert a coordinate pair like 1,26 to AA2"
    if x<26: s=chr(65+x)
    else:
	x=x-26
	s=chr(65+ (x/26) ) + chr(65+ (x%26) )
    s=s+str(y+1)
    return s

coord_pat = re.compile('^(?P<x>[a-zA-Z]{1,2})(?P<y>\d+)$')

def str2yx(s):
    "Convert a string like A1 to a coordinate pair like 0,0"
    match = coord_pat.match(s)
    if not match: return None
    y,x = match.group('y', 'x')
    x = string.upper(x)
    if len(x)==1: x=ord(x)-65
    else:
	x= (ord(x[0])-65)*26 + ord(x[1])-65 + 26
    return string.atoi(y)-1, x

assert yx2str(0,0) == 'A1'
assert yx2str(1,26) == 'AA2'
assert str2yx('AA2') == (1,26)
assert str2yx('B2') == (1,1)
	
class TabFile:
    def __init__(self, scr, filename, column_width=20):
	self.scr=scr ; self.filename = filename
	self.column_width = column_width
	f=open(filename, 'r')
	self.data = []
	while (1):
	    L=f.readline()
	    if L=="": break
	    self.data.append( string.split(L, '\t') )
#	    if len(self.data)>6: break # XXX
	self.x, self.y = 0,0
	self.win_x, self.win_y = 0,0
	self.max_y, self.max_x = self.scr.getmaxyx()
	self.num_columns = int(self.max_x/self.column_width)
	self.scr.clear()	
	self.display()

    def move_to_end(self):
	"""Move the highlighted location to the end of the current line."""

	# This is a method because I didn't want to have the code to 
	# handle the End key be aware of the internals of the TabFile object.
	yp=self.y+self.win_y ; xp=self.x+self.win_x
	if len(self.data)<=yp: end=0
	else: end=len(self.data[yp])-1
	
	# If the end column is on-screen, just change the
	# .x value appropriately.
	if self.win_x <= end < self.win_x + self.num_columns:
	    self.x = end - self.win_x
	else:
	    if end<self.num_columns:
		self.win_x = 0 ; self.x = end
	    else:
		self.x = self.num_columns-1
		self.win_x = end-self.x
	        
	
    def display(self):
	"""Refresh the current display"""
	
	self.scr.addstr(0,0, 
			yx2str(self.y + self.win_y, self.x+self.win_x)+'    ',
			curses.A_REVERSE)

	for y in range(0, self.max_y-3):
	    self.scr.move(y+2,0) ; self.scr.clrtoeol()
	    for x in range(0, int(self.max_x / self.column_width) ):
		self.scr.attrset(curses.A_NORMAL)
		yp=y+self.win_y ; xp=x+self.win_x
		if len(self.data)<=yp: s=""
		elif len(self.data[yp])<=xp: s=""
		else: s=self.data[yp][xp]
		s = string.ljust(s, 15)[0:15]
		if x==self.x and y==self.y: self.scr.attrset(curses.A_STANDOUT)
		self.scr.addstr(y+2, x*self.column_width, s)

	yp=self.y+self.win_y ; xp=self.x+self.win_x
	if len(self.data)<=yp: s=""
	elif len(self.data[yp])<=xp: s=""
	else: s=self.data[yp][xp]

	self.scr.move(1,0) ; self.scr.clrtoeol()
	self.scr.addstr(s[0:self.max_x])
	self.scr.refresh()

def main(stdscr):
    import string, curses, sys

    if len(sys.argv)==1:
	print 'Usage: tabview.py <filename>'
	return
    filename=sys.argv[1]

    # Clear the screen and display the menu of keys
    stdscr.clear()
    file = TabFile(stdscr, filename)
    
    # Main loop:
    while (1):
	stdscr.move(file.y+2, file.x*file.column_width)     # Move the cursor
	c=stdscr.getch()		# Get a keystroke
	if 0<c<256:
	    c=chr(c)
	    # Q or q exits
	    if c in 'Qq': break  
	    # Tab pages one screen to the right
	    elif c=='\t':
		file.win_x = file.win_x + file.num_columns
		file.display()
	    else: pass                  # Ignore incorrect keys

	# Cursor keys
	elif c==curses.key_up:
	    if file.y == 0:
		if file.win_y>0: file.win_y = file.win_y - 1
	    else: file.y=file.y-1
	    file.display()
	elif c==curses.key_down:
	    if file.y < file.max_y-3 -1: file.y=file.y+1
	    else: file.win_y = file.win_y+1
	    file.display()
	elif c==curses.key_left:
	    if file.x == 0:
		if file.win_x>0: file.win_x = file.win_x - 1
	    else: file.x=file.x-1
	    file.display()
	elif c==curses.key_right:
	    if file.x < int(file.max_x/file.column_width)-1: file.x=file.x+1
	    else: file.win_x = file.win_x+1
	    file.display()

	# Home key moves to the start of this line
	elif c==curses.key_home:
	    file.win_x = file.x = 0
	    file.display()
	# End key moves to the end of this line
	elif c==curses.key_end:
	    file.move_to_end()
	    file.display()

	# PageUp moves up a page
	elif c==curses.key_ppage:
	    file.win_y = file.win_y - (file.max_y - 2)
	    if file.win_y<0: file.win_y = 0
	    file.display()
	# PageDn moves down a page
	elif c==curses.key_npage:
	    file.win_y = file.win_y + (file.max_y - 2)
	    if file.win_y<0: file.win_y = 0
	    file.display()
	
	# Insert memorizes the current position
	elif c==curses.key_ic:
	    file.save_y, file.save_x = file.y + file.win_y, file.x + file.win_x
	# Delete restores a saved position
	elif c==curses.key_dc:
	    if hasattr(file, 'save_y'):
		file.x = file.y = 0
		file.win_y, file.win_x = file.save_y, file.save_x
		file.display()
        else: 
	    stdscr.addstr(0,50, curses.keyname(c)+ ' pressed')
	    stdscr.refresh()
	    pass			# Ignore incorrect keys

if __name__=='__main__':
    import curses, traceback
    try:
	# Initialize curses
	stdscr=curses.initscr()
	# Turn off echoing of keys, and enter cbreak mode,
	# where no buffering is performed on keyboard input
	curses.noecho() ; curses.cbreak()

	# In keypad mode, escape sequences for special keys
	# (like the cursor keys) will be interpreted and
	# a special value like curses.key_left will be returned
	stdscr.keypad(1)
	main(stdscr)			# Enter the main loop
	# Set everything back to normal
	stdscr.keypad(0)
	curses.echo() ; curses.nocbreak()
	curses.endwin()			# Terminate curses
    except:
        # In the event of an error, restore the terminal
	# to a sane state.
	stdscr.keypad(0)
	curses.echo() ; curses.nocbreak()
	curses.endwin()
	traceback.print_exc()		# Print the exception



By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions