Contents:

1. Introduction

It is common in REBOL to create dialects that generate some output (for e.g. HTML, or PDF, and so on). This program defines a function that generalizes this concept into a "emit dialect" that can be easily extended with "macros".

2. Overview

The emit function appends values to output according to the contents of the values block. The values contained in this block are simply appended to the output except for the following cases:

keywords
words that are keywords for the emit dialect trigger the associated action (see Dialect keywords);
macros
words that refer to a macro defined with the macro function trigger the evaluation of the macro (see below);
word!'s
for all other words, their value is appended to the output;
path!'s
they are evaluated and the result is appended to the output;
paren!'s
they are evaluated and the result is appended to the output;
lit-word!'s
the respective word is appended to the output;
lit-path!'s
the respective path is appended to the output;

Overview

emit: func [
 "Append values to a series according to a dialect"
 output [series! port!]
 values [block!] "EMIT dialect block (see docs)"

 /local emit's locals
] [
 Append values to output according to the contents of values
]

The macro function defines a macro that can be used in the emit dialect. Macros can work in two ways: "normal" or /custom. Normal macros work in a similar way to REBOL functions: you specify arguments in the spec block, they are evaluated when the macro is evaluated and assigned to the words you have specified in the context of the macro. Then the values in the body block are emit'ted just like if they were there in place of where the macro has been called.

In the case of /custom macros, you still specify the context for the macro using the spec block, but the body is a parse rule that is applied directly after the call to the macro, to collect "arguments" and emit values in the parens inside the rule.

Overview +≡

macro: func [
 "Define a macro for the EMIT dialect"
 spec [block!] {Spec for the macro arguments, options and local words}
 body [block!] "Body of the macro (EMIT dialect)"
 /custom "The body is a PARSE rule"

 /local macro's locals
] [
 Define a macro for the EMIT dialect
]

The syntax for the spec block is very similar to that of spec blocks for REBOL functions, with the following differences:

no refinements
macros do not currently support refinements; they are generally replaced by /options, which work better in the case of having many refinemnts, and by the usage of /custom macros most other cases;
/options
it is possible to specify a set of optional arguments, by using the /options refinement in the spec; these arguments can be specified by calling the macro with the /options refinement which takes a block as argument; this block specifies the values for the options as in [option: value];
default values
default values for optional arguments can be defined specifying the argument with a set-word! and following it with the default value;

See Support functions for the functions used by emit and macro.

Overview +≡

Support functions

3. Examples

(Examples to be provided.)

4. Dialect keywords

Dialect keywords

emit: keyword [
 {Emit the argument, evaluating the dialect if it's a block}
 value "If block, contents is interpreted as EMIT dialect"
 /only "When output is block, emit as a sub-block"
] [
 either block? :value [
  either all [only any-block? output] [
   append/only output make block! 8
   emit last output value
  ] [
   emit output value
  ]
 ] [
  do either only ['append/only] ['append] output :value
 ]
]
if: keyword [
 {If condition is not false or none, emit the given values}
 condition
 values "Value to emit or emit dialect block"
] [
 if :condition [
  either block? :values [
   emit output values
  ] [
   append/only output :values
  ]
 ]
]
either: keyword [
 "Depending on condition, emit on-true or on-false"
 condition
 on-true "Value to emit or emit dialect block"
 on-false "Value to emit or emit dialect block"
 /local values
] [
 values: either :condition [:on-true] [:on-false]
 either block? :values [
  emit output values
 ] [
  append/only output :values
 ]
]
apply: keyword [
 {Call a macro with the arguments in the supplied block}
 name
 arguments
] [
 name: get name
 if macro? :name [
  call-macro output arguments name
 ]
]
encode-text: keyword [
 "Encode and emit text"
 text
 encoding
] [
 either any-string? output [
  encode-text/to text encoding output
 ] [
  append output encode-text text encoding
 ]
]
decode-text: keyword [
 "Decode and emit text"
 text
 encoding
] [
 either any-string? output [
  decode-text/to text encoding output
 ] [
  append output decode-text text encoding
 ]
]
; needs optimization
foreach: keyword [
 "Emits values for each value in a series"
 word "Word or block of words to set each time (local)"
 data "The series to traverse"
 body "The values to emit at each iteration"
] [
 foreach :word data compose/only [emit output (body)]
]
; warning: /default handling, other differences with SWITCH
switch: keyword [
 {Selects a choice and emits the block that follows it}
 value "Value to search for"
 cases [block!] "Block of cases to search"
 /local case
] [
 parse cases [
  to value to block! set case block! (emit output case)
  |
  to /default skip set case block! (emit output case)
 ]
]

5. Append values to output according to the contents of values

Append values to output according to the contents of values

parse values [
 some [
  ; lit-words and lit-paths are automatically converted by REBOL
  set value lit-word! (append output value)
  |
  set value lit-path! (append/only output value)
  |
  here: path! :here into [set value word! 'options] here: (
   value: get value
   either macro? :value [
    here: call-macro/options output here value
   ] [
    append output value/options
   ]
  ) :here
  |
  set value path! here: (
   either tmp: in emit-keywords value/1 [
    value/1: tmp
    here: do value output here
   ] [
    append output do value
   ]
  ) :here
  |
  set value word! here: (
   either tmp: in emit-keywords value [
    here: do tmp output here
   ] [
    value: get value
    either macro? :value [
     here: call-macro output here value
    ] [
     append output :value
    ]
   ]
  ) :here
  |
  set value paren! (append output do value)
  |
  set value skip (append/only output :value)
 ]
]
output

5.1 emit's locals

emit's locals

value here tmp

6. Define a macro for the EMIT dialect

Define a macro for the EMIT dialect

spec*: make block! 3 + length? spec
types: make block! 3 + length? spec
parse spec [
 some [
  set name set-word! do-next [set type block! | (type: none)] (
   insert/only insert tail spec* name pop-result
   append/only types type
  )
  |
  set name word! [set type block! | (type: none)] (
   insert insert tail spec* to set-word! name none
   append/only types type
  )
  |
  [/local | /options] (unless mandatory [mandatory: length? types])
  |
  refinement! (make error! "Refinements not supported.")
  |
  skip
 ]
]
unless mandatory [mandatory: length? types]
spec*: construct spec*
bind body spec*
reduce [
 'macro spec* get spec* mandatory types
 either custom [
  func [output args] compose/deep/only [
   parse args [
    (body)
    args:
   ]
   args
  ]
 ] [
  body
 ]
 spec
]

6.1 macro's locals

macro's locals

spec* name val type types mandatory

7. Support functions

Support functions

macro?: func [value] [
 all [
  block? :value
  parse value ['macro object! block! integer! block! [block! | function!] block!]
 ]
]
expect-arg: func [func-name arg-name types near] [
 make error! reduce ['script 'expect-arg func-name arg-name types near]
]
no-arg: func [func-name arg-name near] [
 make error! reduce ['script 'no-arg func-name arg-name none near]
]
call-macro: func [
 output [series! port!]
 args [block!]
 macro [block!]
 /options
 /local saved values value types name near
] [
 ; 1 2 3 4 5 6 7
 ; macro ctx defaults numargs types body/func spec
 saved: get macro/2
 either function? pick macro 6 [
  ; reset to defaults
  set macro/2 macro/3
  args: macro/6 output args
 ] [
  near: copy/part back args 5
  values: copy macro/3
  types: macro/5
  loop macro/4 [
   if empty? args [
    no-arg near/1 pick first macro/2 1 + index? types near
   ]
   set [value args] do/next args
   ; !!! problem !!!
   if all [types/1 not find types/1 type?/word :value] [
    expect-arg near/1 pick first macro/2 1 + index? types types/1 near
   ]
   values/1: :value
   values: next values
   types: next types
  ]
  set macro/2 head values
  if options [
   if empty? args [
    no-arg near/1 'options near
   ]
   set [value args] do/next args
   unless block? :value [
    expect-arg near/1 'options [block!] near
   ]
   options: copy skip first macro/2 macro/4 + 1
   types: copy types
   parse value [
    some [
     set name set-word! do-next (
      if name: find options to word! name [
       ; !!! problem !!!
       types: at head types index? name
       value: pop-result
       if all [types/1 not find types/1 type?/word :value] [
        expect-arg near/1 name/1 types/1 near
       ]
       set in macro/2 name/1 :value
      ]
     )
     |
     skip
    ]
   ]
  ]
  emit output macro/6
 ]
 set macro/2 saved
 args
]
make-keywords: func [
 keywords [block!]
 /local result name spec body actual-spec actual-body value
] [
 result: clear [ ]
 parse keywords [
  some [
   set name set-word! 'keyword block! block!
   (append result name)
  ]
 ]
 append result none
 result: context result
 parse keywords [
  some [
   set name set-word! 'keyword set spec block! set body block! (
    actual-spec: copy [
     output [series! port!]
     args [block!]
     /local
    ]
    actual-body: make block! 16
    parse spec [
     some [
      set value word! (
       append actual-spec value
       append actual-body compose/deep [
        set [(value) args] do/next args
       ]
      )
      |
      /local some [set value word! (append actual-spec value) | skip]
      |
      set value refinement! (
       insert find actual-spec /local value
      )
      |
      skip
     ]
    ]
    append actual-body body
    append actual-body 'args
    result/(to word! name): func actual-spec actual-body
   )
  ]
 ]
 result
]
emit-keywords: make-keywords [
 Dialect keywords
]