diff --git a/signature/TYPE.str b/signature/TYPE.str new file mode 120000 index 0000000..332d8ef --- /dev/null +++ b/signature/TYPE.str @@ -0,0 +1 @@ +TYPE.stx \ No newline at end of file diff --git a/signature/TYPE.stx b/signature/TYPE.stx new file mode 100644 index 0000000..e299f8b --- /dev/null +++ b/signature/TYPE.stx @@ -0,0 +1,7 @@ +module signature/TYPE +signature + sorts TYPE // semantic type + constructors + INT : TYPE + STRING : TYPE + STREAM : TYPE diff --git a/statics/util.stx b/statics/util.stx new file mode 100644 index 0000000..7ce94e7 --- /dev/null +++ b/statics/util.stx @@ -0,0 +1,7 @@ +module statics/util +imports signature/TYPE + +rules + lastTYPE : list(TYPE) -> TYPE + lastTYPE([T]) = T. + lastTYPE([U | TS]) = lastTYPE(TS). diff --git a/tests/emit_thrice.fos b/tests/emit_thrice.fos index 6042aad..232e2be 100644 --- a/tests/emit_thrice.fos +++ b/tests/emit_thrice.fos @@ -1,4 +1,4 @@ - stream << 72 + 87 + stream << 'Some numbers: ' stream << 88 + 96 99 + 12 >> diff --git a/trans/analysis.str b/trans/analysis.str index 858d1ec..e0c22ef 100644 --- a/trans/analysis.str +++ b/trans/analysis.str @@ -1,14 +1,4 @@ module analysis - -signature - sorts - TYPE - - constructors - INT : TYPE - STRING : TYPE - STREAM : TYPE - imports statixruntime diff --git a/trans/haskell.str b/trans/haskell.str index a22fe36..560b29c 100644 --- a/trans/haskell.str +++ b/trans/haskell.str @@ -1,5 +1,5 @@ module haskell -imports libstrategolib signatures/- util analysis +imports libstrategolib signatures/- signature/TYPE util analysis rules /* Approach: A) We will define a local transformation taking a term with value strings @@ -52,7 +52,13 @@ rules hs: (_, Terminate(x)) -> $[[x];;] hs: (_, Sequence(l)) -> l - /* One drawback of using paramorphism is at the very leaves we have + /* One drawback of using paramorphism is we have to handle lists + explicitly: + */ + hs: (_, []) -> [] + hs: (_, [x | xs]) -> [x | xs] + + /* Another drawback of using paramorphism is at the very leaves we have to undouble the tuple: */ hs: (x, x) -> x where x diff --git a/trans/statics.stx b/trans/statics.stx index 0099855..b6a7d83 100644 --- a/trans/statics.stx +++ b/trans/statics.stx @@ -1,6 +1,8 @@ module statics imports signatures/fostr-sig +imports signature/TYPE +imports statics/util /** md Title: Adding Program Analysis with Statix @@ -36,7 +38,10 @@ Then I reached the point at which the grammar was basically just ``` (The first four clauses are in comments because they approximate fostr's grammar; it actually uses a few more sorts for sequences of -expressions, to achieve fostr's exact layout rules.) +expressions, to achieve fostr's exact layout rules. Also note that the parsing +of literal strings later evolved to include the surrounding single quotes, +because the rule above implicitly allows layout between the quotes and the +string contents, creating ambiguity.) This was the first point at which there were two different types that might need to be written to standard output (Int and String), and although of course @@ -47,7 +52,7 @@ that point since I knew it would be hopeless without statically typing fostr programs). So it was time to bite the bullet and add type checking via Statix to fostr. -The first step is to replace the simple assertion that any TopLevel +The first step was to replace the simple assertion that any TopLevel is OK with a constraint that its Seq must type properly, and an assignment of that type to the top level node: ```statix @@ -57,17 +62,9 @@ programOk(tl@TopLevel(seq)) :- {T} ``` Of course, for this to even parse, we must have a definition of `type_Seq`: ```statix +{! ../signature/TYPE.stx extract: {start: module, stop: rules} !} **/ -/** md */ -signature - sorts TYPE // semantic type - constructors - INT : TYPE - STRING : TYPE - STREAM : TYPE -/* **/ - // see docs/implementation.md for detail on how to switch to multi-file analysis rules // single-file entry point @@ -108,6 +105,34 @@ where of course type_Ex needs its own declaration analogous to the above. type_Line(l) == T, @ls.type := T. + /** md + +The other (and in fact more typical) rule for `type_Seq`, when it actually +consists of a sequence of expressions, is a bit more involved. Fortunately +Statix provides a primitive for mapping over a list, so we can proceed as +follows: +```statix + types_Exs maps type_Ex(list(*)) = list(*) + type_Seq(s@Sequence(l)) = T :- {lt} + types_Exs(l) == lt, + lastTYPE(lt) == T, + @s.type := T. +``` +Here `lastTYPE` is a function that extracts the last TYPE from a list. +Unless/until Statix develops some sort of standard library, it must be +hand-defined, as done in "statics/util.stx" like so: +```statix +{! ../statics/util.stx extract: {start: lastTYPE} !} +``` + **/ + + types_Lines maps type_Line(list(*)) = list(*) + + type_LineSeq(ls@Sequence(l)) = T :- {lt} + types_Lines(l) == lt, + lastTYPE(lt) == T, + @ls.type := T. + type_OptTermEx : OptTermEx -> TYPE type_Line(l@OptTermEx2Line(ote)) = T :- @@ -196,8 +221,33 @@ This pattern lets us specify error messages. ### Using type annotations in transformation -_Probably want to include stuff from analysis.str/ haskell.str here_ +At this point, Statix properly types all of the valid programs of the very +rudimentary language defined by the grammar above. But the proximate purpose +for implementing this typing was to aid Haskell code generation. So how +do we actually use the assigned types in a Stratego transformation? +Statix provides a Stratego api that includes, among other items, strategies +`stx-get-ast-analysis` and `stx-get-ast-type(|analysis)` that provide access +to the assigned types. However, it's easiest to use the information via +a wrapper like this, essentially lifted from the "chicago" language project: +```stratego +{! analysis.str extract: + start: Extract.the.type +terminate: Prints.the.analyzed.type +!} +``` + +Now `get_type` run on a node of the analyzed AST produces the assigned `TYPE` +(as an ATerm in the constructors of sort TYPE in Statix). + +Thus, you can select on the assigned type, as in the strategy to select +the correct Haskell operator to use to send an item to standard output: +```stratego +{! haskell.str extract: + start: '(.*hs_getOp.=.*)' + stop: \s +!} +``` **/ rules // multi-file entry point