Skip to content

Commit

Permalink
Gut the original ed and begin to create sed.
Browse files Browse the repository at this point in the history
  • Loading branch information
Zed A. Shaw committed Dec 15, 2017
1 parent ef56bfc commit 95dcd55
Show file tree
Hide file tree
Showing 7 changed files with 347 additions and 0 deletions.
84 changes: 84 additions & 0 deletions ex49_sed/ed/ed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import sys
import ed
from ed.scanner import Scanner
from ed.parser import EdParser

def process(line, buffer):
scan = Scanner([line])
ed_parser = EdParser(scan, buffer)
parse_tree = ed_parser.parse()

class Buffer(object):
def __init__(self, file_name=None):
self.file_name = file_name

if file_name:
self.edit(file_name)
else:
self.lines = []

def append(self, text, address=None):
if address == None:
# append at the end
self.lines.append(text)
else:
# position it after the given line
self.lines.insert(address, text)

def change(self, start=None, end=None):
pass

def delete(self, start=None, end=None):
pass

def edit(self, file_name):
self.lines = []
self.read(file_name)
self.file_name = file_name

def file(self, file_name):
self.file_name = file_name

def insert(self, address=None):
pass

def mark(self, address=None):
pass

def move(self, start=None, end=None, to=None):
pass

def nprint(self, start, end):
for i, line in enumerate(self.lines):
print(f"{i}\t{line}")

def print(self, start, end):
start = start or 0
end = end or len(self.lines)
for line in self.lines[start:end]:
print(line)

def quit(self):
sys.exit(0)

def read(self, file_name, address=None):
self.lines += open(file_name).readlines()

def subst(self, pattern, replace, start=None, end=None):
pass

def write(self, file_name=None):
file_name = file_name or self.file_name
assert file_name, "Need a file name!"
open(file_name, 'w').write("\n".join(self.lines))

def line_count(self):
return len(self.lines)

def join(self, start, end):
front = self.lines[:start]
middle = self.lines[start:end]
joined = " ".join(middle)
after = self.lines[end:]
self.lines = front + [joined] + after

117 changes: 117 additions & 0 deletions ex49_sed/ed/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from pprint import pprint


class Parser(object):

def __init__(self, scanner):
self.scanner = scanner

def match(self, token_id):
return self.scanner.match(token_id)

def peek(self):
return self.scanner.peek()

def skip(self, *what):
return self.scanner.skip(*what)

def root(self):
pass

def parse(self):
results = []
while not self.scanner.done():
results.append(self.root())
return results

def syntax_error(self, message):
return f"{message}: {self.scanner.tokens[:10]}"


class EdParser(Parser):

def __init__(self, scanner, buffer):
super().__init__(scanner)
self.buffer = buffer
self.range = None
self.cur_line = 0

def root(self):
start = self.peek()

if start == 'PRINT':
self.print()
elif start == 'NPRINT':
self.nprint()
elif start == 'APPEND':
self.append()
elif start == 'FILE':
self.file()
elif start == 'WRITE':
self.write()
elif start == 'QUIT':
self.buffer.quit()
elif start == 'INTEGER':
# this is an address
self.address()
elif start == 'JOIN':
self.join()
elif start == 'COMMA':
self.address_range()
else:
assert False, f"Not supported {self.match(start)}"

def address(self):
# need to handle the range here
addr = self.match('INTEGER')
addr_n = int(addr[1])
self.cur_line = addr_n
if self.peek() == 'COMMA':
self.address_range()

def address_range(self):
# right now only whole buffer
self.match('COMMA')
if self.peek() == 'INTEGER':
addr = self.match('INTEGER')
addr_n = int(addr[1])
self.range = (self.cur_line, addr_n)
else:
self.range = (0, self.buffer.line_count())


def calc_range(self):
if self.range:
return self.range
else:
return self.cur_line, self.cur_line + 1

def print(self):
self.match('PRINT')
self.buffer.print(*self.calc_range())

def nprint(self):
self.match('NPRINT')
self.buffer.nprint(*self.calc_range())

def append(self):
self.match('APPEND')
line = input()
while line != '.':
self.buffer.append(line, address=self.cur_line)
self.cur_line += 1
line = input()

def write(self):
self.match('WRITE')
self.buffer.write()

def file(self):
self.match('FILE')
file_name = self.match('FILE_NAME')
self.buffer.file(file_name[1])

def join(self):
self.match('JOIN')
self.buffer.join(*self.calc_range())

88 changes: 88 additions & 0 deletions ex49_sed/ed/scanner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import re

def L(regex, token):
return (re.compile(regex), token)

class Scanner(object):
regex_list = [
L(r"^p", "PRINT"),
L(r'^a', 'APPEND'),
L(r'^n', 'NPRINT'),
L(r'^f', 'FILE'),
L(r'^w', 'WRITE'),
L(r'^j', 'JOIN'),
L(r'^q', 'QUIT'),
L(r"^[0-9]+", "INTEGER"),
L(r"^\+", "PLUS"),
L(r"^\-", "MINUS"),
L(r"^\s+", "SPACE"),
L(r"^,", "COMMA"),
L(r'^.*', "FILE_NAME"), # terrible regex
]

def __init__(self, code):
self.code = code
self.tokens = self.scan(code)

def ignore_ws(self):
while self.tokens and self.tokens[0][0] == 'SPACE':
self.tokens.pop(0)

def match(self, token_id):
if token_id != 'SPACE':
self.ignore_ws()

if self.tokens[0][0] == token_id:
return self.tokens.pop(0)
else:
return ['ERROR', 'error']

def peek(self):
self.ignore_ws()
if self.tokens:
return self.tokens[0][0]
else:
return ['END', 'end']

def skip(self, *what):
for x in what:
if x != 'SPACE': self.ignore_ws()

tok = self.tokens[0]
if tok[0] != x:
return False
else:
self.tokens.pop(0)

return True


def match_regex(self, i, line):
start = line[i:]
for regex, token in self.regex_list:
tok = regex.match(start)
if tok:
begin, end = tok.span()
return token, start[:end], end
return None, start, None


def scan(self, code):
self.script = []

for line in code:
i = 0
line = line.rstrip()
while i < len(line):
token, string, end = self.match_regex(i, line)
assert token, "Failed to match line %s" % string
if token:
i += end
self.script.append((token, string, i, end))

return self.script

def done(self):
return len(self.tokens) == 0


13 changes: 13 additions & 0 deletions ex49_sed/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import readline
import sys
from ed import ed

buffer = ed.Buffer()

while True:
try:
line = input("> ")
ed.process(line, buffer)
except EOFError:
print()
sys.exit(0)
Empty file added ex49_sed/tests/out.txt
Empty file.
6 changes: 6 additions & 0 deletions ex49_sed/tests/test_basics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from ed import ed
import os

def test_process():
pass

39 changes: 39 additions & 0 deletions ex49_sed/tests/test_buffer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from ed.ed import Buffer
import os

def test_append():
b = Buffer()
b.append("This is a test.")
assert b.lines[0] == "This is a test."
b.append("a", address=0)
assert b.lines[0] == "a"
b.append("1", 1)
assert b.lines[1] == "1"

def test_edit():
b = Buffer()
base_file = "tests/test_buffer.py"
b.edit(base_file)
assert b.lines == open(base_file).readlines()
assert b.file_name == base_file

def test_file():
b = Buffer()
base_file = "tests/test_buffer.py"
b.file(base_file)
assert b.file_name == base_file

def test_nprint():
b = Buffer()
b.nprint(0,0)

def test_print():
b = Buffer()
b.print(0,0)

def test_write():
b = Buffer()
b.file("tests/out.txt")
b.write()
assert os.path.exists("tests/out.txt")

0 comments on commit 95dcd55

Please sign in to comment.