REBOL [ Title: "Include" File: %include.r Author: "Ladislav Mecir" WWW: http://www.rebol.net/wiki/INCLUDE_documentation License: { Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 } Purpose: {A REBOL script processor: loader, preprocessor and evaluator.} Notes: { Global variables used: INCLUDE the function INCLUDE-CTX the context defining useful variables and functions INCLUDE-CTX functions: INCLUDE-SCRIPT function used by INCLUDE and by directives to preprocess a script INCLUDE-BLOCK generated by the SET-DIRECTIVES function, used by INCLUDE-SCRIPT and by directives to preprocess a block or a paren GET-DIRECTIVES used to get the current directives SET-DIRECTIVES used to define the current directives UPDATE-DIRECTIVES used to update the current directives PUSH saves the current PATH, replacing it temporarily by a new one POP restores the PATH previously saved by the POP function DO-NEXT do next expression, using the "R3 convention", compatible with R2 and R3 LOAD-NEXT load the next value, compatible with R2 and R3 READ-BINARY read the source as binary compatible with R2 and R3 MAKE-ERROR make the error according to the given SPEC, compatible with R2 and R3, allows setting the NEAR and WHERE attributes even in R2 DISARM-ERROR disarm the error, compatible with R2 and R3 REDO-ERROR "enhance" (if needed) and redo the given error INCLUDE-ERROR create and trigger a new INCLUDE-type error SPLIT-PATH a function splitting the given path FINDPFILE find the given file using the given search path FIND-FILE find a file (using INCLUDE-CTX/PATH if desired) INCLUDE-CTX variables: PATH the search path used by the INCLUDE function, user-modifiable LOG a block listing included files, user-modifiable STACK used by the PUSH/POP functions to save/restore the current PATH BLOCK-DIRECTIVE a directive handling a subblock or paren STANDARD-DIRECTIVES the block contains the definitions of the standard directives except for the conditional directives and special includes SPECIAL-INCLUDES the block contains the definitions of #include-binary, #include-string and #include-files CONDITIONAL-DIRECTIVES the block contains the definitions of the conditional directives DIRECTIVES the block contains the currently used definitions of INCLUDE directives Do not use directly! STANDARD-HEADER the script header prototype object FILE the file processed by INCLUDE New INCLUDE error type defined, with following ids: ENHANCED an, otherwise "normal" error, that occurred during code preprocessing; enhanced to "know" the file in which it occurred FILE-OR-URL-IN-PATH a file or a URL was expected in the PATH STACK-EMPTY the POP function found the STACK to be empty FILE-NOT-FOUND file to be included was not found EXPECTED the directive expected an argument INVALID-DIRECTIVE the UPDATE-DIRECTIVES function obtained an invalid directive in the DIRECTIVES-TO-UPDATE block SCRIPT-BUG SCRIPT? bug - did not succeed to skip preface Global variables used by directives: KEEP-COMMENTS if defined, COMMENTs are kept Patches: SCRIPT? patched to handle strings } ] comment [ ; Usage ; to find and do a file %myfile.r: include %myfile.r ; to append a URL or a directory to the search path: append include-ctx/path url-or-directory ; to find out, how the include-ctx/path looks: print include-ctx/path ; if you want to start using a totally new include-ctx/path: include-ctx/path: [%/my-search-dir/ %/etc/ http://www.myserv.dom/] ; to include %somefile.r if not included before: include/check %somefile.r ; to obtain a linked file: include/link %somefile.r %outfile.r ; to obtain a Rebol block: include/only %somefile.r ] unless value? 'include [ ; patch the SCRIPT? function if needed if error? try [script? ""] [ script?: func [ {Checks file, url, or string for a valid script header.} source [file! url! binary! string!] ] [ switch type?/word source [ file! url! [source: read source] string! [source: to binary! source] ] find-script source ] ] whitespace: charset [#"^A" - #" " #"^(7F)" #"^(A0)"] include-ctx: make object! [ ; for compatibility with R2 and R3 make-error: none ; for compatibility with R2 and R3 disarm-error: none ; the currently processed file file: none ; enhance the encountered error if needed and redo it redo-error: func [ error [error!] /local disarmed ] [ disarmed: disarm-error error either disarmed/type = 'include [do error] [ do make-error 'include 'enhanced compose/only/deep [ (disarmed/arg1) (rejoin ["" disarmed/type " " disarmed/id " in " file]) [ file: (file) type: (disarmed/type) id: (disarmed/id) arg2: (disarmed/arg2) arg3: (disarmed/arg3) ] (disarmed/near) (disarmed/where) ] ] ] ; cause INCLUDE error include-error: func [ {Cause INCLUDE error} id near [block!] /expected arg3 /local arg2 ] [ if expected [ arg2: id id: 'expected ] do make-error 'include id compose/deep/only [ (file) (arg2) (arg3) (near) ] ] ; for compatibility with R2 and R3 do-next: none load-next: none read-binary: none standard-header: none include-error-type-spec: [ type: "include error" enhanced: [:arg2] file-or-URL-in-path: [ "A file or URL expected in INCLUDE-CTX/PATH, a" :arg1 "obtained" ] stack-empty: ["POP stack empty"] file-not-found: [ "File" :arg1 "to be included from" :arg2 "was not found" ] expected: [ "A" :arg2 "was expected by the directive in" :arg1 "," :arg3 "obtained" ] unexpected-directive: ["Unexpected directive found in" :arg1] invalid-directive: ["UPDATE-DIRECTIVES found an invalid directive"] script-bug: ["SCRIPT? bug, use capital R in Rebol [] header"] ] either in system 'error [ make-error: func [ type [word!] id [word!] args /local error disarmed ] [ disarmed: disarm error: make error! compose [ (type) (id) (args) ] disarmed/near: pick args 4 disarmed/where: pick args 5 return :error ] disarm-error: :disarm do-next: func [ block [block!] var [word!] /local result ] [ set/any reduce ['result var] do/next block return get/any 'result ] load-next: func [ {load the next value} [catch] 'source [word!] {source position (modified)} /local result ] [ throw-on-error [ set/any reduce ['result source] load/next get/any source ] return get/any 'result ] read-binary: func [ {Reads from a file, url, or port-spec (block or object).} source [file! url! object! block!] ] [ read/binary source ] unless value? 'body-of [ body-of: func [ "Returns a copy of the body of a function or object." value ] [ case [ object? :value [third :value] function? :value [ copy/deep second :value ; Note: Still bound! ] any-function? :value [none] ; none if native 'else [ do make error! reduce [ 'script 'cannot-use 'reflect type? :value ] ] ] ] ] system/error: make system/error [ include: make object! compose [ code: system/error/script/code + 50 (include-error-type-spec) ] ] standard-header: system/standard/script ] [ make-error: func [ err-type [word!] err-id [word!] args ] [ make error! [ type: err-type id: err-id arg1: pick args 1 arg2: pick args 2 arg3: pick args 3 near: pick args 4 where: pick args 5 ] ] disarm-error: func [error] [:error] do-next: func [ block [block!] var [word!] /local result ] [ set/any 'result do/next block var return get/any 'result ] load-next: func [ {load the next value} 'source [word!] {word referring to source position (modified)} /local result ] [ set/any reduce ['result source] transcode/next get/any source :result ] read-binary: :read append system/catalog/errors compose [ include: ( make object! compose [ code: system/catalog/errors/script/code + 50 (include-error-type-spec) ] ) ] standard-header: system/standard/header ] split-path: func [ { Splits a file or URL. Returns a block containing path and target. Overcomes some limitations of the Rebol/Core 2.2 split-path, like strange results for: split-path %file.r split-path %"" The following equality holds: file = append first split-path file second split-path file } file [file! url!] /local target ] [ target: tail file if (pick target -1) = #"/" [target: back target] target: find/reverse target #"/" target: either target [next target] [file] reduce [copy/part file target to file! target] ] findpfile: func [ {Find a file using the given search path} path [block!] file [file! url!] /local dir found ] [ while [not empty? path] [ unless any [file? first :path url? first :path] [ do make-error 'include 'file-or-URL reduce [ type? first :path ] ] if exists? found: append dirize copy dir: first :path :file [ return found ] path: next :path ] none ] find-file: func [ {Find a file using an appropriate search path} file [file! url!] /local dir target ] [ set [dir target] split-path file case [ empty? :dir [findpfile include-ctx/path target] exists? file [file] ] ] ; include-ctx/path is initialized to contain the %. directory ; and the directory, where the %include.r was run from path: reduce [%. what-dir] ; to prevent multiple includes and create a log log: copy [] ; push/pop operation support stack: copy [] push: func [ {use the NEW-PATH temporarily} new-path [block!] ] [ append/only stack path path: new-path ] pop: func [ {restore the old INCLUDE-CTX/PATH} ] [ if empty? stack [ do make-error 'include 'stack-empty [] ] path: last stack remove back tail stack path ] ; support for user-defined directives directives: copy [] get-directives: func [ {Returns the INCLUDE-CTX/DIRECTIVES block} ] [ directives ] ; the function used to preprocess a block or a paren, ; generated by the SET-DIRECTIVES function include-block: none set-directives: func [ { Sets the INCLUDE-CTX/DIRECTIVES block , and generates the INCLUDE-BLOCK function } new-directives [block!] ] [ directives: new-directives include-block: func [ linked [block! paren!] { block or paren containing the result of the preprocessing } pos1 [block! paren!] {block or paren to be preprocessed} /local pos2 value1 value2 value3 ] compose/deep [ parse pos1 [ any [( append copy directives [ | set value1 skip (insert/only tail linked get/any 'value1) ] )] ] linked ] ] update-directives: func [ { Updates INCLUDE DIRECTIVES. If a directive in the DIRECTIVES-TO-UPDATE block already exists, it is updated. If the directive does not exist, it is appended. } directives-to-update [block!] /only {change the DIRECTIVES block only} /local directive dir-start dir-end cont finish define-directive update-directive to-next-directive ] [ ; make sure the block is not modified directives: copy directives ; define the directive, if not found: define-directive: [( append directives '| append directives directive )] ; update the directive, if found, and stop searching update-directive: [ ; find the end of the directive [to '| | to end] dir-end: (change/part dir-start directive dir-end) end skip ] ; used when the directive has not been found yet to-next-directive: [thru '| | to end] parse directives-to-update [ any [ '| ( ; invalid directive found do make-error 'include 'invalid-directive [] ) | [copy directive to '| '| | copy directive [skip to end]] ( finish: define-directive cont: to-next-directive ; look for the directive parse directives [ any [ dir-start: skip ( if equal? first dir-start first directive [ ; directive found cont: update-directive finish: none ] ) cont | finish end skip ] ] ) ] ] unless only [set-directives directives] ] set 'include func [ {A script processor} [catch] file [file! url!] {the file to process} /check {include if the script hasn't been included yet} /link {create a linked file} output [file!] /only {create a Rebol block} /args {set arguments} arg ] [ include-script/start copy [] file check output only arg ] include-script: func [ {Include a script file} linked [block!] {block to append the processed code to (modified)} source [file! url!] {the file to process} check [none! logic!] {include if the script hasn't been included yet} /start {used by INCLUDE} output {create a linked file} only {create a REBOL block} arg {arguments} /local file-name file-path dir binary-base result old-header err old-file temp oldargs ] [ if start [ ; set script args, remember the old args oldargs: system/script/args system/script/args: arg ] ; find the file unless result: find-file source [ do make-error 'include 'file-not-found reduce [source file] ] result: clean-path result ; prevent multiple includes either all [find log :result check] [linked] [ append log lowercase :result set [file-path file-name] split-path result ; remember the old FILE before change old-file: file file: result ; remember the old DIR before change dir: what-dir if file? file-path [ change-dir file-path result: file-name ] ; read the file if error? err: try [result: read result] [redo-error err] ; skip the preface if err: script? result [ unless parse/all err [ copy temp 5 skip ( temp: either "rebol" <> to string! temp [ [:err] ] [ [any whitespace] ] ) temp #"[" to end ] [ ; a SCRIPT? bug do make-error 'include 'script-bug [] ] result: err ] if error? err: try [ ; load the script result: either #"[" = pick result 1 [ ; embedded script load-next result ] [ ; script not embedded load/all result ] ] [redo-error err] ; skip the header if it is not needed unless start [parse result ['REBOL block! result:]] ; preprocess if error? result: try [include-block linked result] [ redo-error result ] ; finish the job case [ output [ binary-base: system/options/binary-base system/options/binary-base: 64 write output mold/only/all/flat result system/options/binary-base: binary-base ] all [start not only] [ ; save the old header before setting the new one old-header: system/script/header either parse result ['rebol block! to end] [ system/script/header: construct/with second result standard-header result: skip result 2 ] [ system/script/header: none ] set/any 'result do result ; reset script args system/script/args: oldargs ; restore the header system/script/header: old-header ] ] ; return to the "original" DIR change-dir dir ; return to the "original" FILE file: old-file get/any 'result ] ] set-directives block-directive: [ [set value1 block!] (append/only linked include-block make value1 0 value1) ] ; define local directive groups standard-directives: none conditional-directives: none special-includes: none ; guard the definitions below against preprocessing do ([ update-directives standard-directives: [ #include-check pos1: ( set/any 'value1 do-next pos1 'pos2 any [ file? get/any 'value1 url? get/any 'value1 include-error/expected "file or URL" pos1 type? get/any 'value1 ] include-script linked value1 true ) :pos2 | #include pos1: ( set/any 'value1 do-next pos1 'pos2 any [ file? get/any 'value1 url? get/any 'value1 include-error/expected "file or URL" pos1 type? get/any 'value1 ] include-script linked value1 none ) :pos2 | #do pos1: [ set value1 block! | (include-error/expected "do-block" pos1 none) ] (insert tail linked do value1) | #paren pos1: [ set value1 paren! | (include-error/expected "paren" pos1 none) ] (append/only linked include-block make value1 0 value1) | 'comment pos1: ( set/any 'value1 do-next pos1 'pos2 if value? 'keep-comments [ append linked 'comment insert/only tail linked get/any 'value1 ] ) :pos2 ] update-directives conditional-directives: [ #if pos1: [ set value1 block! | (include-error/expected "condition-block" pos1 none) ] [ set value2 block! | (include-error/expected "then-block" pos1 none) ] ( case [ unset? set/any 'value1 do value1 [ include-error/expected "condition" pos1 "#[unset!]" ] :value1 [include-block linked value2] ] ) | #either pos1: [ set value1 block! | (include-error/expected "condition-block" pos1 none) ] [ set value2 block! | (include-error/expected "then-block" pos1 none) ] [ set value3 block! | (include-error/expected "else-block" pos1 none) ] ( if unset? set/any 'value1 do value1 [ include-error/expected "condition" pos1 "#[unset!]" ] include-block linked either :value1 [value2] [value3] ) ] update-directives special-includes: [ #include-string pos1: ( set/any 'value1 do-next pos1 'pos2 any [ file? get/any 'value1 url? get/any 'value1 include-error/expected "file or URL" pos1 type? get/any 'value1 ] unless value2: find-file value1 [ do make-error 'include 'file-not-found reduce [ value1 file ] ] append linked read value2 ) :pos2 | #include-binary pos1: ( set/any 'value1 do-next pos1 'pos2 any [ file? get/any 'value1 url? get/any 'value1 include-error/expected "file or URL" file pos1 type? get/any 'value1 ] unless value2: find-file value1 [ do make-error 'include 'file-not-found reduce [ value1 file ] ] append linked read-binary value2 ) :pos2 | #include-files pos1: [ set value1 file! | (include-error/expected "path" pos1 none) ] [ set value2 block! | (include-error/expected "path and a block" pos1 none) ] ( value3: make block! length? value2 foreach file value2 [ append value3 file append value3 read-binary value1/:file ] append/only linked value3 ) ] ]) ] ]