Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Rearranged source tree to work as a package suitable to publishing to…
… PyPI
  • Loading branch information
coopernurse committed Mar 16, 2012
1 parent 4bbb0cd commit 18ffac7
Show file tree
Hide file tree
Showing 11 changed files with 671 additions and 1 deletion.
22 changes: 22 additions & 0 deletions LICENSE
@@ -0,0 +1,22 @@
(The MIT License)

Copyright (c) 2012 James Cooper <james@bitmechanic.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 changes: 19 additions & 0 deletions barrister/__init__.py
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
"""
barrister
~~~~~~~~~
A RPC toolkit for building lightweight reliable services. Ideal for
both static and dynamic languages.
:copyright: (c) 2012 by James Cooper.
:license: MIT, see LICENSE for more details.
"""
__version__ = '0.1.0'

from .runtime import contract_from_file, idgen_uuid, idgen_seq
from .runtime import RpcException, Server, HttpTransport, InProcTransport
from .runtime import Client, Batch, BatchResult
from .runtime import Contract, Interface, Enum, Struct, Function
from .parser import parse, IdlParseException, IdlScanner
from .docco import docco_html
File renamed without changes.
356 changes: 356 additions & 0 deletions barrister/parser.py
@@ -0,0 +1,356 @@
import cStringIO
from plex import Scanner, Lexicon, Str, State, IGNORE, Begin, Any, AnyChar, Range, Rep

native_types = [ "int", "float", "string", "bool" ]
letter = Range("AZaz")
digit = Range("09")
under = Str("_")
ident = letter + Rep(letter | digit | under)
arr_ident = Str("[]") + ident
space = Any(" \t\n\r")
comment = Str("// ") | Str("//")

def parse(idl_text, name=None, validate=True):
if isinstance(idl_text, (str, unicode)):
idl_text = cStringIO.StringIO(idl_text)

scanner = IdlScanner(idl_text, name)
scanner.parse()
if validate:
scanner.validate()
if len(scanner.errors) == 0:
return scanner.parsed
else:
raise IdlParseException(scanner.errors)

class IdlParseException(Exception):

def __init__(self, errors):
Exception.__init__(self)
self.errors = errors

def __str__(self):
s = ""
for e in self.errors:
if s != "":
s += ", "
s += "line: %d message: %s" % (e["line"], e["message"])
return s

class IdlScanner(Scanner):

def eof(self):
if self.cur:
self.add_error("Unexpected end of file")

def add_error(self, message, line=-1):
if line < 0:
(name, line, col) = self.position()
self.errors.append({"line": line, "message": message})

def begin_struct(self, text):
self.check_dupe_name(text)
self.cur = { "name" : text, "type" : "struct", "extends" : "",
"comment" : self.get_comment(), "fields" : [] }
self.begin('start-block')

def begin_enum(self, text):
self.check_dupe_name(text)
self.cur = { "name" : text, "type" : "enum",
"comment" : self.get_comment(), "values" : [] }
self.begin('start-block')

def begin_interface(self, text):
self.check_dupe_name(text)
self.cur = { "name" : text, "type" : "interface",
"comment" : self.get_comment(), "functions" : [] }
self.begin('start-block')

def check_dupe_name(self, name):
if self.types.has_key(name):
self.add_error("type %s already defined" % name)

def check_not_empty(self, cur, list_name, printable_name):
if len(cur[list_name]) == 0:
self.add_error("%s must have at least one %s" % (cur["name"], printable_name))
return False
return True

def start_block(self, text):
t = self.cur["type"]
if t == "struct":
self.begin("fields")
elif t == "enum":
self.begin("values")
elif t == "interface":
self.begin("functions")
else:
raise Exception("Invalid type: %s" % t)

def end_block(self, text):
ok = False
t = self.cur["type"]
if t == "struct":
ok = self.check_not_empty(self.cur, "fields", "field")
elif t == "enum":
ok = self.check_not_empty(self.cur, "values", "value")
elif t == "interface":
ok = self.check_not_empty(self.cur, "functions", "function")

if ok:
self.parsed.append(self.cur)
self.types[self.cur["name"]] = self.cur

self.cur = None
self.begin('')

def begin_field(self, text):
self.field = { "name" : text }
self.begin("field")

def end_field(self, text):
self.field["type"] = text
self.field["comment"] = self.get_comment()
self.cur["fields"].append(self.field)
self.field = None
self.begin("fields")

def begin_function(self, text):
self.function = { "name" : text, "comment" : self.get_comment(), "params" : [ ] }
self.begin("function-start")

def begin_param(self, text):
self.param = { "name" : text }
self.begin("param")

def end_param(self, text):
self.param["type"] = text
self.function["params"].append(self.param)
self.param = None
self.begin("end-param")

def end_return(self, text):
self.function["returns"] = text
self.cur["functions"].append(self.function)
self.function = None
self.begin("end-function")

def end_value(self, text):
if not text in self.cur["values"]:
val = { "value" : text, "comment" : self.get_comment() }
self.last_comment = ""
self.cur["values"].append(val)

def get_comment(self):
comment = ""
if self.comment and len(self.comment) > 0:
comment = "".join(self.comment)
self.comment = None
return comment

def start_comment(self, text):
if self.comment:
self.comment.append("\n")
else:
self.comment = []
self.prev_state = self.state_name
self.begin("comment")

def append_comment(self, text):
self.comment.append(text)

def end_comment(self, text):
self.begin(self.prev_state)
self.prev_state = None

def end_extends(self, text):
if self.cur and self.cur["type"] == "struct":
self.cur["extends"] = text
else:
self.add_error("extends is only supported for struct types")

def add_comment_block(self, text):
comment = self.get_comment()
if comment:
self.parsed.append({"type" : "comment", "value" : comment})

lex = Lexicon([
(Str("\n"), add_comment_block),
(space, IGNORE),
(Str('struct '), Begin('struct-start')),
(Str('enum '), Begin('enum-start')),
(Str('interface '), Begin('interface-start')),
(comment, start_comment),
State('struct-start', [
(ident, begin_struct),
(space, IGNORE),
(AnyChar, "Missing identifier") ]),
State('enum-start', [
(ident, begin_enum),
(space, IGNORE),
(AnyChar, "Missing identifier") ]),
State('interface-start', [
(ident, begin_interface),
(space, IGNORE),
(AnyChar, "Missing identifier") ]),
State('start-block', [
(space, IGNORE),
(Str("extends"), Begin('extends')),
(Str('{'), start_block) ]),
State('extends', [
(space, IGNORE),
(ident, end_extends),
(Str('{'), start_block) ]),
State('fields', [
(ident, begin_field),
(space, IGNORE),
(comment, start_comment),
(Str('{'), 'invalid'),
(Str('}'), end_block) ]),
State('field', [
(ident, end_field),
(arr_ident, end_field),
(Str("\n"), 'invalid'),
(space, IGNORE),
(Str('{'), 'invalid'),
(Str('}'), 'invalid') ]),
State('functions', [
(ident, begin_function),
(space, IGNORE),
(comment, start_comment),
(Str('{'), 'invalid'),
(Str('}'), end_block) ]),
State('function-start', [
(Str("("), Begin('params')),
(Str("\n"), 'invalid'),
(space, IGNORE) ]),
State('params', [
(ident, begin_param),
(space, IGNORE),
(Str(")"), Begin('function-return')) ]),
State('end-param', [
(space, IGNORE),
(Str(","), Begin('params')),
(Str(")"), Begin('function-return')) ]),
State('param', [
(ident, end_param),
(arr_ident, end_param),
(space, IGNORE) ]),
State('function-return', [
(space, IGNORE),
(ident, end_return),
(arr_ident, end_return) ]),
State('end-function', [
(Str("\n"), Begin('functions')),
(space, IGNORE) ]),
State('values', [
(ident, end_value),
(space, IGNORE),
(comment, start_comment),
(Str('{'), 'invalid'),
(Str('}'), end_block) ]),
State('comment', [
(Str("\n"), end_comment),
(AnyChar, append_comment) ])
])

def __init__(self, f, name):
Scanner.__init__(self, self.lex, f, name)
self.parsed = [ ]
self.errors = [ ]
self.types = { }
self.comment = None
self.cur = None

def parse(self):
while True:
(t, name) = self.read()
if t is None:
break
else:
self.add_error(t)
break

def validate_type(self, cur_type, types, level):
level += 1

cur_type = self.strip_array_chars(cur_type)

if cur_type in native_types or cur_type in types:
pass
elif not self.types.has_key(cur_type):
self.add_error("undefined type: %s" % cur_type, line=0)
else:
cur = self.types[cur_type]
types.append(cur_type)
if cur["type"] == "struct":
if cur["extends"] != "":
self.validate_type(cur["extends"], types, level)
for f in cur["fields"]:
self.validate_type(f["type"], types, level)
elif cur["type"] == "interface":
# interface types must be top-level, so if len(types) > 1, we
# know this interface was used as a type in a function or struct
if level > 1:
msg = "interface %s cannot be a field type" % cur["name"]
self.add_error(msg, line=0)
else:
for f in cur["functions"]:
types = [ ]
for p in f["params"]:
self.validate_type(p["type"], types, 1)
self.validate_type(f["returns"], types, 1)

def add_parent_fields(self, s, names, types):
if s["extends"] in native_types:
self.add_error("%s cannot extend %s" % (s["name"], s["extends"]), line=0)
elif self.types.has_key(s["extends"]):
if s["name"] not in types:
types.append(s["name"])
parent = self.types[s["extends"]]
if parent["type"] == "struct":
for f in parent["fields"]:
if f["name"] not in names:
names.append(f["name"])
self.add_parent_fields(parent, names, types)
else:
self.add_error("%s cannot extend %s %s" % (s["name"], parent["type"], parent["name"]), line=0)

def validate_struct_extends(self, s):
names = []
self.add_parent_fields(s, names, [])
for f in s["fields"]:
if f["name"] in names:
self.add_error("%s cannot redefine parent field %s" % (s["name"], f["name"]), line=0)

def contains_cycle(self, name, types):
name = self.strip_array_chars(name)
if self.types.has_key(name):
t = self.types[name]
if t["type"] == "struct":
if name in types:
self.add_error("cycle detected in: %s %s" % (t["type"], name), line=0)
return True
else:
types.append(name)
if self.contains_cycle(t["extends"], types):
return True
for f in t["fields"]:
# use a copy of the type list to keep function checks separate
if self.contains_cycle(f["type"], types[:]):
return True
return False

def strip_array_chars(self, name):
if name.find("[]") == 0:
return name[2:]
return name

def validate(self):
for t in self.parsed:
if t["type"] == "comment":
pass
elif not self.contains_cycle(t["name"], []):
self.validate_type(t["name"], [], 0)
if t["type"] == "struct":
self.validate_struct_extends(t)
File renamed without changes.

0 comments on commit 18ffac7

Please sign in to comment.