Docsity
Docsity

Prepare for your exams
Prepare for your exams

Study with the several resources on Docsity


Earn points to download
Earn points to download

Earn points by helping other students or get them with a premium plan


Guidelines and tips
Guidelines and tips

Attribute Grammars: Evaluation and Generation, Study notes of Computer Science

An overview of attribute grammars, focusing on their evaluation and generation. It covers the concept of functional graphs, data flow analysis, and the relationship between attributes and tree nodes. The document also includes an example of attribute grammars for the small language tiny, demonstrating how to specify its semantics.

Typology: Study notes

Pre 2010

Uploaded on 09/17/2009

koofers-user-q7w
koofers-user-q7w 🇺🇸

10 documents

1 / 17

Toggle sidebar

Related documents


Partial preview of the text

Download Attribute Grammars: Evaluation and Generation and more Study notes Computer Science in PDF only on Docsity! COP 5555 - PRINCIPLES OF PROGRAMMING LANGUAGES NOTES ON ATTRIBUTE GRAMMARS. I. Introduction. These notes are intended to supplement class lectures on attribute grammars. We will begin with functional graphs, and describe methods of evaluating them. Then we will introduce attribute grammars, show an example of their use, and proceed to give an attribute grammar for Tiny. II. Functional graphs. Definition: A functional graph is a directed graph in which i) Nodes represent functions, a. Incoming edges are parameters. b. Outgoing edges represent functional values. ii) Edges represent transmission of data among functions. Example: 2 3 + 5 Here we have three functions: i- Constant function 2. ii- Constant function 3. iii- Binary function + (addition). After some delay, the value of the addition function is five. Question: What effect does a cycle in a functional graph have? Answer: A cycle can make sense only if the graph can achieve a steady state, in which no more changes in the values occur. Example: 1 + ? No steady state is achieved, because the value of ? keeps incrementing. PLP Notes Attribute Grammars -2- Example: true and ? A steady state is achieved, because regardless of the initial value of the feedback loop, it does not change after the first AND operation, and remains with that value. However, if the "AND" were changed to a "NAND", there would be no steady state. In general, it is impossible to detect whether a steady state will ever occur (the problem is undecid- able, i.e. as difficult as deciding whether a Turing machine will halt). If we assume that each node in the graph is a single processor, then the graph can be thought of as a network of parallel processors. We will also assume that all functional graphs are acyclic. Evaluation of Functional Graphs: Functional graphs are evaluated by first inserting registers, and then propagating values along the edges. Register insertion: Given edges E1. . . En from a node A to nodes B1. . . Bn, we insert a register so that there is one edge from A to the register, and n edges from the register to B1. . . Bn respectively. Example: A B2 B1 Bn GETS CONVERTED TO A B2 B1 Bn ... ... All registers must be initialized to some "undefined" value, which must be outside of the domains of all functions. For an outgoing edge from a "top-most" node, a register is simply added to the graph, so that the edge goes from the node to the register. Note that this is the same as the first case, with n=0. Functional value propagation: There are two ways to propagate values along the functional graph. The first, called "data flow analy- sis", makes several passes on the graph, evaluating functions whose inputs (registers) are defined. The other, called "lazy evaluation", uses a stack to perform a depth-first search, backwards (i.e. the opposite direction of the edges), for the "bottom-most" nodes of the graph. The functions are then evaluated in the order specified by the contents of the stack. PLP Notes Attribute Grammars -5- S: Σ → PowerSet (SATT) I: Σ → PowerSet (IATT) Σ is the vocabulary in the grammar. In our case, Σ = { 0, 1, cat, . }. Note that meta-symbols ’<’ and ’>’ are left out of Σ. Functions S and I map each symbol to a subset of the synthesized and inherited attributes, respectively. In our case, S(0) = { value, length } S(1) = { value, length } S(cat) = { value, length } S(.) = { value } I(0) = { exp } I(1) = { exp } I(cat) = { exp } I(.) = { } Recall that attributes are "attached" to tree nodes, and that they become registers in the functional graph. Example: Suppose the input binary number is ’10.1’. Here’s the AST, with the corresponding attributes attached: . cat 1 1 0 v e v l e v l e v l e v l The inherited attributes are depicted on the left of each node, and the synthesized attributes are depicted on the right. This is simply a convention. However, it aids in understanding the flow of information: top-down on the left, and bottom-up on the right. For a giv en tree node, a certain attribute may occur both at that node and at some of its siblings. In the above example, attribute "value" occurs "locally" at the root node and at both its first and second kid. To prevent confusion, we will use a tree addressing scheme, as follows: Definition: Giv en a tree node T, with kids T1,. . . Tn, i- a(ε ) refers to attribute "a" at node T, and ii- a(i) refers to attribute "a" at the i’th kid of T (i.e. node Ti). In the above example, given that we are referring to the root node, v(ε ) refers to the "value" attribute at the root node, while v(1) and v(2) refer to the "value" attribute at the first and second kids of the root, respec- tively. PLP Notes Attribute Grammars -6- All that is left is to specify the functions that connect the registers in the functional graph. This is done with the axioms. Axiom Specification: Definition: For each production of the form A → < r K1. . . Kn > in the AST grammar, a set of axioms is specified as follows: Let r1, . . . , rn be the roots of subtrees K1, . . . , Kn. (1) For each attribute ’a’ in S(r), (2) for each sibling rk of r, and for each attribute ’a’ in I(rk), (3) there is exactly one axiom of the form (4) a = f ( w1,w2,. . . , wm ), where (5) i- f is a pre-defined semantic function (6) ii- for all 1 ≤ i ≤ m, either (7) wi = z(ε ), with z in I(r), or (8) wi = z(j), with z in S(r j), for some j. Let’s make some sense out of this. There are two separate issues here. The first issue is the number of axioms required. There must be exactly one axiom for each synthesized attribute at the root (line 1). There must also be exactly one axiom for each inherited attribute at each of the root’s kids (line 2). Another way of phrasing this is to say that we (i.e. the attribute grammar writers) must specify what goes up from the current node (line 1), and what goes down to each kid of the current node (line 2). The second issue is the form of each of the required axioms. Every axiom is of the form shown (line 4), i.e. a single predefined semantic function (line 5), all of whose arguments (line 6), must come from either inherited attributes at the root (line 7), or synthesized attributes at one of the kids (line 8). It would be most illustrative for the reader to draw some pictures, to understand the restrictions that these rules impose. For example, one may not inherit an attribute from two lev els above in the tree, nor synthesize from two lev els below. Further, one may not inherit from inherited attributes at the same level in the tree, nor may one synthesize from synthesized attributes at the same level in the tree. The Attribute Grammar for Binary Numbers: Let’s begin with the first production in the AST grammar: S → <.NN>. The general form of the tree is shown below. Each subtree may have either ’cat’, ’0’, or ’1’ as its root. In all three cases, the attributes for the root of the subtree are the same. According to the axiom specification rules stated above, we need three axioms for this production: one for value(ε ), one for exp(1), and one for exp(2). . T1 T2 v e v l e v l PLP Notes Attribute Grammars -7- The axioms are: value(ε ) = value(1) + value(2) exp(1) = 0 exp(2) = - length(2) The segment of functional graph is shown below. The lines connecting trees nodes have been omitted. . T1 T2 v e v l e v l + neg0 Note that the length attribute from the first kid is ignored, while the length from the second kid is negated and sent down the exponent of the second kid. The length attribute is used only for the second kid of the root (if any), so that negative exponents may be computed. At the bottom of T2 (the right-hand side tree), length will originate with value 1. It will be incremented by one on the way up, negated at the top, and sent down via the inherited exponent attribute (and incremented along the way), to be used as the correct (neg- ative) power of 2, for the binary digits. The two synthesized "value" attributes (the integral and the frac- tional part of the binary number) are added together to form the decimal result. Here’s the entire set of axioms: S → < . N N > value(ε ) = value(1) + value(2) exp(1) = 0 exp(2) = - length(2) # explained above S → < . N > value(ε ) = value(1) # no fraction; copy value up. exp(1) = 0 N → < cat N D > value(ε ) = value(1) + value (2) # add two values. length(ε ) = length(1) + 1 # increment length up left. exp(1) = exp (ε ) + 1 # increment exp down left exp(2) = exp (ε ) # copy exp down right N → D # No axioms ! D → 0 value(ε ) = 0 # zero no matter what. length(ε ) = 1 # initial length. D → 1 value(ε ) = 2 ** exp(ε ) # compute value. length(ε ) = 1 # initial length. PLP Notes Attribute Grammars -10- Scanner Specification Scanner Generator Scan Table Scanner Source Input Screener Specification Screener Generator Screen Table Screener Parser Specification Parser Generator Parse Table Parser AST prefix→postfix Tree Transducer AST (postfix) LR Tree Parser & Graph Generator GRAPH Graph Evaluator Target Output Tables (& Axioms) LR(0) Tree Parser Generator AG (postfix) prefix→postfix Grammar Transducer Attribute Grammar Tokens Tokens The larger dashed box is the translator genration system; the smaller is the translator being generated. The phases of the translation (scanning, screening, parsing, and back-end processing) are shown. In the case of binary numbers, the user of the system provides the three front-end specifications and the attribute grammar. The system generates a parser from the front-end specifications, and a graph generator from the attribute grammar. The user then provides a sample source input, which is transduced to the AST, trans- formed into postfix notation, and recognized by the LR(0) tree parser, which in turn builds the functional graph, and passes it to the graph evaluator to produce the translated output. In our case the source language is that of binary numbers, and the target language is that of decimal numbers. The correctness of the trans- lation, of course, depends on ALL four specifications. Now, finally, the real world. In the case of binary numbers, all attributes were integers. However, attributes can be whatever we want them to be, as long as we have well-defined semantic functions to manipulate them. In the next section, we use attribute grammars to specify (completely) the semantics of a (very) small language, called Tiny for lack of a better name. In this attribute grammar, the most important PLP Notes Attribute Grammars -11- attribute will be a "file" attribute -- the code file in which instructions of the target machine will be placed. This code file attribute will propagate around the entire abstract syntax tree, collecting instructions as the evaluation of the graph (and the translation) proceeds. The result of the evaluation of the functional graph will be this code file, i.e. the sequence of machine instructions to which the source program has been translated. To execute the translated program, of course, the code file must be loaded into the target machine and executed. An Attribute Grammar for Tiny Tiny is a very small Pascal-like language, with the following characteristics: 1) Every program is given a name. 2) There are user-defined variables, of type integer. 3) Legal expressions are composed of variables, integers, the intrinsic function "read", boolean operator "not", equality operator "=", binary operators "+" and "-" and unary "-". "not" and unary "-’ are the most binding operators, "+" and "-" are next, and "=" is the least binding. "+" and "-" are left asso- ciative; parentheses override both precedence and associativity. 4) There are assignment statements, and variables must be assigned a value before they are used in expressions. There are no declarations. 5) There is an if-then-else construct, a while-loop, statement sequencing, and an intrinsic procedure "output". Here’s Tiny’s syntax: Tiny → ’program’ Name ’:’ Statement ’end’ Name ’.’ => ’program’; Statement → ’assign’ Name ’:=’ Expression => ’assign’ → ’output’ Expression => ’output’ → ’if ’ Expression ’then’ Statement ’else’ Statement ’fi’ => ’if’ → ’while’ Expression ’do’ Statement ’od’ => ’while’ → Statement list ’;’ => ’;’ ; Expression → Term ’=’ Term => ’=’ → Term; Term → Term ’+’ Factor => ’+’ → Term ’-’ Factor => ’-’ → Factor; Factor → ’-’ Factor => ’-’ → ’not’ Factor => ’not’ → Name → ’read’ => ’read’ → ’<integer>’ → ’(’ Expression ’)’; Name → ’<identifier>’; Here’s a sample Tiny program, that copies 10 integers from the input to the output: program copy: assign i := 1; while not (i=11) do output read; assign i := i + 1 od end copy. PLP Notes Attribute Grammars -12- Here’s the AST grammar for Tiny: P → < ’program’ ’<identifier>’ E ’<identifier>’ > E → < ’assign’ ’<identifier>’ E > → < ’output’ E > → < ’if ’ E E E > → < ’while’ E E > → < ’;’ E* > → < ’not’ E > → < ’=’ E E > → < ’+’ E E > → < ’-’ E E > → < ’-’ E > → ’<identifier>’ → ’<integer>’ → ’read’ Notice that the grammar has been simplified. There is no distinction between expressions and statements. The AST grammar generates a superset of the AST’s generated by the phrase-structure grammar above. This is in fact a healthy practice. In general it is a good idea to write a compiler back-end (or in our case an attribute grammar that specifies the compiler back-end) that is capable of handling more trees than the parser (as it stands now) will ever giv e it. If the concrete syntax of the language ever changes, the back-end will be general enough (we hope) to handle the changes without a major upheaval. The compiler for Tiny generates code for a very simple (again) target machine, whose instruction set is described by the algorithm below: Algorithm Tiny Target Machine: I := 1 Next_Instruction: case Code[I] of save n: stack[n] := stack[top--] load n: stack[++top] := stack[n] negate: stack[top] := -stack[top] not: stack[top] := not stack[top] add: t := stack[top--]; stack[top] := stack[top] + t subtract: t := stack[top--]; stack[top] := stack[top] - t equal: t := stack[top--]; stack[top] := stack[top] = t read: stack[++top] := getinteger(input) print: putinteger(stack[top--]) lit n: stack[++top] := n goto n: I := n; goto Next_Instruction iffalse n: if stack[top--] = 0 then I:=n; goto Next_Instruction fi iftrue n: if stack[top--] = 1 then I:=n; goto Next_Instruction fi stop: halt end; ++I; goto Next_Instruction; For the sample Tiny program shown above, the target code is as follows: PLP Notes Attribute Grammars -15- E → read code↑(ε ) = gen ( code↓(ε ), "read" ) next↑(ε ) = next↓(ε ) + 1 top↑(ε ) = top↓(ε ) + 1 type↑(ε ) = "integer" E → < - E > code↑(ε ) = gen ( code↑(1), "negate" ) next↑(ε ) = next↑(1) + 1 type↑(ε ) = "integer" error↑(ε ) = if type↑(1) = "integer" then error ↑(1) else gen ( error↑(1), "Illegal type for minus" ) E → < + E E > code↑(ε ) = gen ( code↑(2), "add" ) next↑(ε ) = next↑(2) + 1 top↑(ε ) = top↑(2) - 1 type↑(ε ) = "integer" error↑(ε ) = if type↑(1) = type↑(2) = "integer" then error↑(2) else gen ( error↑(2), "Illegal type for plus" ) E → < - E E > code↑(ε ) = gen ( code↑(2), "subtract" ) next↑(ε ) = next↑(2) + 1 top↑(ε ) = top↑(2) - 1 type↑(ε ) = "integer" error↑(ε ) = if type↑(1) = type↑(2) = "integer" then error↑(2) else gen ( error↑(2), "Illegal type for minus" ) E → < not E > code↑(ε ) = gen ( code↑(1), "not" ) next↑(ε ) = next↑(1) + 1 type↑(ε ) = "boolean" error↑(ε ) = if type↑(1) = "boolean" then error↑(1) else gen ( error↑(1), "Illegal type for not" ) E → < = E E > code↑(ε ) = gen ( code↑(2), "equal" ) next↑(ε ) = next↑(2) + 1 type↑(ε ) = "boolean" top↑(ε ) = top↑(2) - 1 error↑(ε ) = if type↑(1) = type↑(2) then error↑(2) else gen ( error↑(2), "Type clash in equal comparison" ) PLP Notes Attribute Grammars -16- E → < assign ’<identifier>:x’ E > code↑(ε ) = if lookup("x") = 0 then enter("x",top↑(2)); code↑(2) else gen ( code↑(2), "save", lookup("x") ) next↑(ε ) = if lookup("x") = 0 then next↑(2) else next↑(2) + 1 top↑(ε ) = if lookup ("x") = 0 then top↑(2) else top↑(2) - 1 error↑(ε ) = if type↑(2) = "integer" then error↑(2) else gen ( error↑(2), "Assignment type clash" ) type↑(ε ) = "statement" E → < output E > code↑(ε ) = gen ( code↑(1), "print" ) next↑(ε ) = next↑(1) + 1 top↑(ε ) = top↑(1) - 1 type↑(ε ) = "statement" error↑(ε ) = if type↑(1) = "integer" then error↑(1) else gen ( error↑(1), "Illegal type for output" ) E → < ; E* > Use Defaults ! E → < if E E E > code↓(2) = gen ( code↑(1), "iffalse", next↑(2) + 1 ) next↓(2) = next↑(1) + 1 top↓(2) = top↑(1) - 1 code↓(3) = gen ( code↑(2), "goto", next↑(3) ) next↓(3) = next↑(2) + 1 error↓(2) = if type↑(1) = "boolean" then error↑(1) else gen ( error↑(1), "Illegal expression for if" ) error↓(3) = if type↑(2) = "statement" then error↑(2) else gen ( error↑(2), "Statement required for if" ) error↑(ε ) = if type↑(3) = "statement" then error↑(3) else gen ( error↑(3), "Statement required for if" ) E → < while E E > code↓(2) = gen ( code↑(1), "iffalse", next↑(2) + 1 ) next↓(2) = next↑(1) + 1 top↓(2) = top↑(1) - 1 PLP Notes Attribute Grammars -17- code↑(ε ) = gen ( code↑(2), "goto", next↓(ε ) ) next↑(ε ) = next↑(2) + 1 type↑(ε ) = "statement" error↓(2) = if type↑(1) = "boolean" then error↑(1) else gen ( error↑(1), "Illegal expression in while" ) error↑(ε ) = if type↑(2) = "statement" then error↑(2) else gen ( error↑(2), "Statement required in while" ) Tiny → < program ’<identifier>:x’ E ’<identifier>:y’ > code↓(2) = Open error↓(2) = Open next↓(2) = 1 top↓(2) = 0 code↑(ε ) = close ( gen ( code↑(2), "stop" ) ) error↑(ε ) = close ( if x = y then error↑(2) else gen ( error↑(2), "program names don’t match" ) ) It would be useful to draw pictures of the functional graph segments, and maybe even the entire AST and functional graph for the sample Tiny program shown above. PLP Notes Attribute Grammars
Docsity logo



Copyright © 2024 Ladybird Srl - Via Leonardo da Vinci 16, 10126, Torino, Italy - VAT 10816460017 - All rights reserved