Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow various types to interpolate into string(take 2!!) #37

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 58 additions & 13 deletions src/bkl/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class Expr(object):
"""
def __init__(self, pos=None):
self.pos = pos

def is_const(self):
"""
Returns true if the expression is constant, i.e. can be evaluated
Expand Down Expand Up @@ -96,6 +96,12 @@ def as_py(self):
"""
raise NotImplementedError

def to_string(self):
"""
Return the expression as a Python string. Has same semantics :meth:`as_py()`.
"""
raise NotImplementedError

def as_symbolic(self):
"""
Returns the value as a symbolic representation, see SymbolicFormatter.
Expand Down Expand Up @@ -133,6 +139,9 @@ def __init__(self, value, pos=None):
def as_py(self):
return self.value

def to_string(self):
return str(self.value)

def __nonzero__(self):
return bool(self.value)

Expand All @@ -151,6 +160,9 @@ def __init__(self, items, pos=None):
def as_py(self):
return [ i.as_py() for i in self.items ]

def to_string(self):
return " ".join([i.to_string() for i in self.items])

def __nonzero__(self):
return bool(self.items)

Expand All @@ -175,9 +187,12 @@ def __init__(self, items, pos=None):
self.items = items

def as_py(self):
items = (i.as_py() for i in self.items)
items = (i.to_string() for i in self.items)
return "".join(i for i in items if i is not None)

def to_string(self):
return self.as_py()

def __nonzero__(self):
for i in self.items:
if i:
Expand All @@ -195,6 +210,9 @@ class NullExpr(Expr):
def as_py(self):
return None

def to_string(self):
return ""

def __nonzero__(self):
return False

Expand Down Expand Up @@ -225,6 +243,9 @@ def __init__(self, var, pos=None):
def as_py(self):
raise NonConstError(self)

def to_string(self):
self.as_py()

def __str__(self):
return "${%s}" % self.var

Expand All @@ -251,6 +272,9 @@ def __init__(self, var, context, pos=None):
def as_py(self):
return self.get_value().as_py()

def to_string(self):
return self.get_value().to_string()

def get_value(self):
"""
Returns value of the referenced variable. Throws an exception if
Expand Down Expand Up @@ -280,6 +304,14 @@ def __str__(self):
return "$(%s)" % self.var


class ReferenceAsStringExpr(ReferenceExpr):
"""
Reference to a variable inside of a string. Has the same attributes as :class:bkl.expr.ReferenceExpr.
"""
def as_py(self):
return self.get_value().to_string()


class BoolValueExpr(Expr):
"""
Constant boolean value, i.e. true or false.
Expand All @@ -295,6 +327,9 @@ def __init__(self, value, pos=None):
def as_py(self):
return self.value

def to_string(self):
return str(self)

def __nonzero__(self):
return self.value

Expand Down Expand Up @@ -336,7 +371,7 @@ def __init__(self, operator, left, right=None, pos=None):
self.operator = operator
self.left = left
self.right = right

def has_bool_operands(self):
"""
Returns true if the operator is such that it requires boolean operands
Expand Down Expand Up @@ -367,6 +402,9 @@ def as_py(self):
else:
assert False, "invalid BoolExpr operator"

def to_string(self):
return BoolValueExpr(self.as_py()).to_string()

def __nonzero__(self):
left = bool(self.left)
right = bool(self.right)
Expand Down Expand Up @@ -412,6 +450,9 @@ def __init__(self, cond, yes, no, pos=None):
def as_py(self):
return self.get_value().as_py()

def to_string(self):
return self.get_value().to_string()

def get_value(self):
"""
Returns value of the conditional expression, i.e. either
Expand Down Expand Up @@ -493,6 +534,9 @@ def as_py(self):
# with explicit anchor:
return "%s/%s" % (self.anchor, "/".join(x.as_py() for x in self.components))

def to_string(self):
return self.as_py()

def __nonzero__(self):
return bool(self.components)

Expand Down Expand Up @@ -623,16 +667,17 @@ class Visitor(object):

def __init__(self):
self._dispatch = {
NullExpr : self.null,
LiteralExpr : self.literal,
ListExpr : self.list,
ConcatExpr : self.concat,
ReferenceExpr : self.reference,
PlaceholderExpr : self.placeholder,
PathExpr : self.path,
BoolValueExpr : self.bool_value,
BoolExpr : self.bool,
IfExpr : self.if_,
NullExpr : self.null,
LiteralExpr : self.literal,
ListExpr : self.list,
ConcatExpr : self.concat,
ReferenceExpr : self.reference,
ReferenceAsStringExpr : self.reference,
PlaceholderExpr : self.placeholder,
PathExpr : self.path,
BoolValueExpr : self.bool_value,
BoolExpr : self.bool,
IfExpr : self.if_,
}

def visit(self, e):
Expand Down
2 changes: 2 additions & 0 deletions src/bkl/interpreter/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,8 @@ def _build_expression(self, ast):
e = BoolValueExpr(ast.value)
elif t is VarReferenceNode:
e = ReferenceExpr(ast.var, self.context)
elif t is VarRefAsStrNode:
e = ReferenceAsStringExpr(ast.var, self.context)
elif t is ListNode:
items = [self._build_expression(e) for e in ast.values]
e = ListExpr(items)
Expand Down
6 changes: 3 additions & 3 deletions src/bkl/interpreter/simplify.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def bool(self, e):
if left is e.left and right is e.right:
return e
else:
if (isinstance(left, NullExpr) and
if (isinstance(left, NullExpr) and
(right is None or isinstance(right, NullExpr))):
return NullExpr(pos=e.pos)
else:
Expand Down Expand Up @@ -174,9 +174,9 @@ def bool(self, e):
if left is not None and right is not None:
assert (left or right) == False
return BoolValueExpr(False, pos=e.pos)
elif op == BoolExpr.EQUAL:
elif op == BoolExpr.EQUAL:
return BoolValueExpr(e.left.as_py() == e.right.as_py())
elif op == BoolExpr.NOT_EQUAL:
elif op == BoolExpr.NOT_EQUAL:
return BoolValueExpr(e.left.as_py() != e.right.as_py())
except NonConstError:
pass
Expand Down
3 changes: 2 additions & 1 deletion src/bkl/parser/Bakefile.g
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ tokens {
BOOLVAL;
PATH_ANCHOR;
VAR_REFERENCE;
VAR_REFERENCE_ASSTRING;
LIST_OR_CONCAT;
LIST;
CONCAT;
Expand Down Expand Up @@ -225,7 +226,7 @@ expr_atom
// ("foo $(bar)") -- the former is a single value, the latter is a list. This
// grammar, however, does *NOT* differentiate between these two cases, that is
// done in the bkl.parser.ast._TreeAdaptor.rulePostProcessing() in Python code.
//
//
// FIXME: It would be better to do it here, with backtrack=true and validating
// predicates to build it directly, but bugs in ANTLR 3.4's Python
// binding prevent it from working at the moment.
Expand Down
2 changes: 1 addition & 1 deletion src/bkl/parser/BakefileQuotedString.g
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ quoted_string_component
;

var_reference
: REF_OPEN identifier REF_CLOSE -> ^(VAR_REFERENCE identifier)
: REF_OPEN identifier REF_CLOSE -> ^(VAR_REFERENCE_ASSTRING identifier)
;

literal_text: t=ANY_TEXT -> LITERAL[$t, self.unescape($t, $t.text)];
Expand Down
63 changes: 33 additions & 30 deletions src/bkl/parser/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ class VarReferenceNode(Node):
var = property(lambda self: self.children[0].text,
doc="Referenced variable")

class VarRefAsStrNode(VarReferenceNode):
"""Variable interpolating into a string."""

class AssignmentNode(Node):
"""Assignment of value to a variable."""
Expand Down Expand Up @@ -306,35 +308,36 @@ def __init__(self, filename):

# mapping of token types to AST node classes
TOKENS_MAP = {
BakefileParser.NIL : NilNode,
BakefileParser.PROGRAM : RootNode,
BakefileParser.LITERAL : LiteralNode,
BakefileParser.BOOLVAL : BoolvalNode,
BakefileParser.PATH_ANCHOR : PathAnchorNode,
BakefileParser.ID : IdNode,
BakefileParser.LIST : ListNode,
BakefileParser.CONCAT : ConcatNode,
BakefileParser.LIST_OR_CONCAT : Node, # post-processed below
BakefileParser.VAR_REFERENCE : VarReferenceNode,
BakefileParser.ASSIGN : AssignmentNode,
BakefileParser.APPEND : AppendNode,
BakefileParser.LVALUE : LvalueNode,
BakefileParser.FILES_LIST : FilesListNode,
BakefileParser.TARGET : TargetNode,
BakefileParser.IF : IfNode,
BakefileParser.OR : OrNode,
BakefileParser.AND : AndNode,
BakefileParser.NOT : NotNode,
BakefileParser.EQUAL : EqualNode,
BakefileParser.NOT_EQUAL : NotEqualNode,
BakefileParser.SUBMODULE : SubmoduleNode,
BakefileParser.IMPORT : ImportNode,
BakefileParser.PLUGIN : PluginNode,
BakefileParser.SRCDIR : SrcdirNode,
BakefileParser.BASE_LIST : BaseListNode,
BakefileParser.CONFIGURATION : ConfigurationNode,
BakefileParser.SETTING : SettingNode,
BakefileParser.TEMPLATE : TemplateNode,
BakefileParser.NIL : NilNode,
BakefileParser.PROGRAM : RootNode,
BakefileParser.LITERAL : LiteralNode,
BakefileParser.BOOLVAL : BoolvalNode,
BakefileParser.PATH_ANCHOR : PathAnchorNode,
BakefileParser.ID : IdNode,
BakefileParser.LIST : ListNode,
BakefileParser.CONCAT : ConcatNode,
BakefileParser.LIST_OR_CONCAT : Node, # post-processed below
BakefileParser.VAR_REFERENCE : VarReferenceNode,
BakefileParser.VAR_REFERENCE_ASSTRING : VarRefAsStrNode,
BakefileParser.ASSIGN : AssignmentNode,
BakefileParser.APPEND : AppendNode,
BakefileParser.LVALUE : LvalueNode,
BakefileParser.FILES_LIST : FilesListNode,
BakefileParser.TARGET : TargetNode,
BakefileParser.IF : IfNode,
BakefileParser.OR : OrNode,
BakefileParser.AND : AndNode,
BakefileParser.NOT : NotNode,
BakefileParser.EQUAL : EqualNode,
BakefileParser.NOT_EQUAL : NotEqualNode,
BakefileParser.SUBMODULE : SubmoduleNode,
BakefileParser.IMPORT : ImportNode,
BakefileParser.PLUGIN : PluginNode,
BakefileParser.SRCDIR : SrcdirNode,
BakefileParser.BASE_LIST : BaseListNode,
BakefileParser.CONFIGURATION : ConfigurationNode,
BakefileParser.SETTING : SettingNode,
BakefileParser.TEMPLATE : TemplateNode,
}

def createWithPayload(self, payload):
Expand All @@ -360,7 +363,7 @@ def filter_list_or_concat(self, node):

FIXME: It would be better to do it here, with backtrack=true and
validating predicates to build it directly, but bugs in
ANTLR 3.4's Python binding prevent it from working at the moment.
ANTLR 3.4's Python binding prevent it from working at the moment.
"""
concats = []
children = node.children
Expand Down
5 changes: 2 additions & 3 deletions src/bkl/vartypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,8 @@ class StringType(Type):

def _validate_impl(self, e):
if isinstance(e, expr.ConcatExpr):
# concatenation of strings is a string
for x in e.items:
self.validate(x)
# all types can be concatenated into string
return True
elif (not isinstance(e, expr.LiteralExpr) and
# paths etc. can be used as strings too
not isinstance(e, expr.BoolExpr) and
Expand Down
2 changes: 1 addition & 1 deletion tests/test_parsing/templates/template.ast
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ RootNode
IdNode defines
ConcatNode
LiteralNode "COMPONENT=""
VarReferenceNode
VarRefAsStrNode
IdNode id
LiteralNode """
TemplateNode
Expand Down
23 changes: 22 additions & 1 deletion tests/test_parsing/vars/quoted.ast
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,27 @@ RootNode
IdNode double
ConcatNode
LiteralNode "evaluated "
VarReferenceNode
VarRefAsStrNode
IdNode bar
LiteralNode " here (as in shell)..."
AssignmentNode
LvalueNode
IdNode a
ListNode
LiteralNode "1"
LiteralNode "2"
LiteralNode "3"
AssignmentNode
LvalueNode
IdNode b
BoolvalNode True
AssignmentNode
LvalueNode
IdNode r
ConcatNode
LiteralNode "!!"
VarRefAsStrNode
IdNode a
LiteralNode " "
VarRefAsStrNode
IdNode b
4 changes: 4 additions & 0 deletions tests/test_parsing/vars/quoted.bkl
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ zar = "this is called \"escaping\"";
single = 'no eval done $(bar) here';
double = "evaluated $(bar) here (as in shell)...";

// Also test interpolation of various types
a = 1 2 3;
b = true;
r = "!!$(a) $(b)";
3 changes: 3 additions & 0 deletions tests/test_parsing/vars/quoted.model
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ module {
zar = this is called "escaping"
single = no eval done $(bar) here
double = evaluated 1 2 3 here (as in shell)...
a = [1, 2, 3]
b = true
r = !!$(a) true
}
targets {
}
Expand Down