6 Language and Parser API
#lang shrubbery | package: rhombus-prototype |
The shrubbery meta-language is similar to the s-exp meta-language. It expects a module name after #lang shrubbery to serve as the language of a Racket module form, while the body of the module after the #lang line is parsed as shrubbery notation.
Unlike s-exp, shrubbery also works without another language listed on the #lang line. In that case, running the module prints the S-expression form of the parsed shrubbery (see Parsed Representation). For example,
#lang shrubbery 1+2
prints '(top (group 1 (op +) 2)). But if "demo.rkt" contains
"demo.rkt"
#lang racket/base (require (for-syntax racket/base syntax/parse)) (provide (rename-out [module-begin #%module-begin]) + - * /) (define-syntax (module-begin stx) (syntax-parse stx #:datum-literals (top group op) [(_ (top (group n1:number (op o) n2:number))) #'(#%module-begin (o 'n1 'n2))]))
then
#lang shrubbery "demo.rkt" 1+2
prints the result 3.
A same-line module language for shrubbery is determined by using parse-all in 'line mode. As long as the resulting shrubbery is not empty, it is parsed in the same way that rhombus parses module names for import.
6.1 Parsing API
(require shrubbery/parse) | package: rhombus-prototype |
procedure
(parse-all in #:source source [#:mode mode])
→ (or/c eof-object? syntax?) in : input-port? source : any/c mode : (or/c 'top 'interactive 'line 'text) = 'top
The result syntax object has no scopes, but it has source-location information and raw-text properties. Source-location information is never associated with the “parentheses” of the syntax object. Instead, source-location information and other properties for a shrubbery (), [], {}, or '' is associated with the parens, brackets, braces, or quotes identifier. Similarly, source-location information and properties for a : block or | alternatives are recorded on a block or alts identifier. A group identifier in the representation has only raw-text properties for text before and after the group elements. For an operator, source-location information and properties are associated to the operator identifier, and not the wrapper op identifier. Each structuring identifier like group, block, parens, or op has the 'identifier-as-keyword syntax property as #t.
The default 'top mode read in until an end-of-file, and it expects a sequence of groups that are indented consistently throughout (i.e., all starting at the same column). The 'text mode is similar, but it starts in “text” mode, as if the entire input is inside curly braces of an @ form (see At-Notation Using @).
The 'interactive and 'line modes are similar. They are suitable for a read-eval-print loop or reading the continuation of a #lang shrubbery line, respectively. In both modes, reading stops when a newline is encountered, unless an opener remains to be closed or a : was encountered. If reading continues due to a :, then it stops when a blank line is found (where a line containing a comment does not count as blank). In 'line mode, the result may be empty, while 'interactive mode continues past a newline if the result would be empty.
6.2 Raw-Text Properties
(require shrubbery/property) | package: rhombus-prototype |
A syntax object produced by parse-all includes properties that allow the original shrubbery text to be reconstructed from the syntax object. Furthermore, this raw-text information is distributed among syntax objects in a way that helps it stay preserved to a useful degree on subterms as they are rearranged by enforestation and macro expansion. The shrubbery/property module exports identifiers bound to the property-key symbols, which can be helpful to avoid a typo in a quoted symbol.
The property values are trees of strings: a string, an empty list, or a pair containing two trees. Raw text can be reconstructed through a preorder traversal of the tree.
procedure
(syntax-raw-property stx) → any/c
stx : syntax? (syntax-raw-property stx val) → syntax? stx : syntax? val : any/c
For example, the input 0x11 will be parsed as the number 17 with a 'raw property value "0x11".
For an identifier such as parens that represents groups within an opener–closer pair, 'raw text will be attached to the identifier for the opener text, while 'raw-tail will be attached to the same identifier for the closer text.
For op and group wrapper identifiers, an empty 'raw property is associated with the identifier. An explicit empty property cooperates with inference in shrubbery-syntax->string for whether raw-text properties should be used.
procedure
(syntax-raw-prefix-property stx) → any/c
stx : syntax? (syntax-raw-prefix-property stx val) → syntax? stx : syntax? val : any/c
procedure
(syntax-raw-suffix-property stx) → any/c
stx : syntax? (syntax-raw-suffix-property stx val) → syntax? stx : syntax? val : any/c
For example, the input 1 + 2 // done will be parsed into the S-expression representation '(top (group 1 (op +) 2)). The syntax object for group will have a 'raw-prefix value equivalent to " " and a 'raw-suffix value equivalent to " // done", but possibily within a tree structure instead of a single string. The syntax object for 1 and will have a 'raw-suffix value equivalent to " ", while the syntax object for + and will have a 'raw-suffix value equivalent to " " (i.e., two spaces).
procedure
(syntax-raw-tail-property stx) → any/c
stx : syntax? (syntax-raw-tail-property stx val) → syntax? stx : syntax? val : any/c
procedure
(syntax-raw-tail-suffix-property stx) → any/c
stx : syntax? (syntax-raw-tail-suffix-property stx val) → syntax? stx : syntax? val : any/c
For example, the input (1 + 2) * 4 //done will be parsed into the S-expression representation '(top (group (parens (group 1 (op +) 2)) (op *) 4)). The syntax object for the outer group will have a 'raw-suffix value equivalent to " // done". The syntax object for parens will have a 'raw value equivalent to "(", a 'raw-tail value equivalent to ")", and a 'raw-tail-suffix value equivalent to " ". The inner group syntax object will have no properties or ones with values that are equivalent to empty strings.
procedure
(syntax-opaque-raw-property stx) → any/c
stx : syntax? (syntax-opaque-raw-property stx val) → syntax? stx : syntax? val : any/c
The 'opaque-raw property is useful in macro expansion to
record a macro’s input to its output—
6.3 Writing Shrubbery Notation
(require shrubbery/write) | package: rhombus-prototype |
procedure
(write-shrubbery v [ port #:pretty? pretty? #:multi-line? multi-line? #:armor? armor?]) → void? v : any/c port : output-port? = (current-output-port) pretty? : any/c = #f multi-line? : any/c = #f armor? : any/c = #f
The default mode with pretty? as #false prints in a simple and relatively fast way (compared to pretty-shrubbery). Even with pretty? as a true value, the output is a single line unless multi-line? is a true value, while multi-line? produces newlines eagerly. Use pretty-shrubbery to gain more control over line choices when printing.
If pretty? is #false or armor? is a true value, then the printed form is line- and column-insensitive.
Note that write-shrubbery expects an S-expression, not a syntax object, so it cannot use raw-text properties. See also shrubbery-syntax->string.
procedure
(pretty-shrubbery v [#:armor? armor?]) → any/c
v : any/c armor? : any/c = #f
The description is an S-expression DAG (directed acyclic graph) that represents pretty-printing instructions and alternatives:
string or bytes: print literally.
'nl: print a newline followed by spaces corresponding to the current indentation.
`(seq ,doc ...): print each doc in sequence, each with the same indentation.
`(nest ,n ,doc): print doc with the current indentation increased by n.
`(align ,doc): print doc with the current indentation set to the current output column.
`(or ,doc ,doc): print either doc; always taking the first doc in an 'or will produce single-line output, while always taking the second doc will print a maximal number of lines.
The description can be a DAG because 'or alternatives might have components in common. In the worst case, a tree view of the instructions can be exponentially larger than the DAG representation.
6.4 Reconstructing Shrubbery Notation
(require shrubbery/print) | package: rhombus-prototype |
procedure
(shrubbery-syntax->string s [ #:use-raw? use-raw? #:max-length max-length #:keep-suffix? keep-suffix? #:infer-starting-indentation? infer-starting-indentation? #:register-stx-range register-stx-range #:render-stx-hook render-stx-hook]) → string? s : syntax? use-raw? : any/c = #f max-length : (or/c #f exact-positive-integer?) = #f keep-suffix? : any/c = #f infer-starting-indentation? : any/c = #t
register-stx-range :
(syntax? exact-nonnegative-integer? exact-nonnegative-integer? . -> . any) = void
render-stx-hook : (syntax? output-port? . -> . any/c) = (lambda (stx output) #f)
If max-length is a number, the returned string will contain no more than max-length characters. Internally, conversion to a string can take shortcuts once the first max-length characters have been determined.
When keep-suffix? is true and raw-text mode is used to generate the result string, then 'raw-prefix, 'raw-suffix, and 'raw-tail-suffix text on the immediate syntax object are included in the result. Otherwise, prefixes and suffixes are rendered only when they appear between 'raw text.
If infer-starting-indentation? is true, then a consistent amount of leading whitespace is removed from each line of the result string.
The register-stx-range and render-stx-hook arguments provide a hook to record or replace rendering of a syntax object within s. The register-stx-range procedure is called with each syntax object in s after printing, and the second and third arguments report the starting and ending locations in the string for the syntax object’s printed form. The render-stx-hook procedure is called before printing each syntax object, and if it returns a true value, then printing assumes that the syntax object has alerady been rendered to the given output port (which is ultimately delivered to a string), and it is not printed in the default way.