From eef36282694945ed2690ca87993efaa05dc3f947 Mon Sep 17 00:00:00 2001 From: Tony Tkacik Date: Sun, 16 Jul 2017 18:37:42 +0200 Subject: [PATCH] nuxsh: Added custom syntax to bash. Signed-off-by: Tony Tkacik --- .gitignore | 1 + inc/nux/dsl.inc.sh | 142 +++++++++++++++++++++++++++++ inc/nux/nuxsh.inc.sh | 209 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 352 insertions(+) create mode 100644 .gitignore create mode 100644 inc/nux/dsl.inc.sh create mode 100644 inc/nux/nuxsh.inc.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..06cf653 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +cache diff --git a/inc/nux/dsl.inc.sh b/inc/nux/dsl.inc.sh new file mode 100644 index 0000000..1420ea6 --- /dev/null +++ b/inc/nux/dsl.inc.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash + +## nulang - NUX Custom DSL Support Library +## +## # Language Definition +## +## Language is defined in terms of BASH REGEX matches and functions that process +## or execute particular match. + +NUDSL_CACHE_SUFFIX=".nux.dsl.sh" + +nux.dsl.eval() { + $nudsl_eval "$@" +} + +nux.dsl.env() { + .process.highlight() { + echo "$line"; + } + .match._unmatched.highlight() { + echo "${_gen_highlight_unmatched}$line${nc_end}" + } + + .gen.parser._unmatched.process() { + nux.exec.or .match._unmatched.$action .process.$action + } + + .highlight() { + nux.dsl.eval _gen_highlight_$1='$nc_'$2 + } + .match() { + local type=$1; + local pattern=$2; + shift; shift; + i=0; + local parse_body=""; + nux.dsl.eval _gen_parser_types='"$_gen_parser_types '$type'"' + nux.dsl.eval _gen_parser_pattern_$type="'"$pattern"'" + nux.dsl.eval """.gen.parser.$type.process() { + $( + for group in "$@"; do + let i=$i+1; + if [ "$group" != "-" ]; then + echo local ${group}='${BASH_REMATCH['$i']}' + fi + done + ) + nux.exec.or .match.$type.\$action .process.\$action + } + """ + + nux.dsl.eval """.match.$type.highlight() { + $( + for group in "$@"; do + let i=$i+1; + if [ "$group" != "-" ]; then + echo ' echo -n "${_gen_highlight_'$group'}$'$group'${nc_end}"' + fi + done + ) + echo; + } + """ + + } +} + +nudsl_eval=eval + +nux.dsl.process() { + local action=$1; + local language=$2; + local file=$3; + ( + nux.dsl.env + $language + cat "$file" | nux.dsl.process0 $action) + +} + +nux.dsl.exec() { + local language="$1"; + local file="$2"; + local cached="${3:-$file${NUDSL_CACHE_SUFFIX}}"; + if nux.dsl.plan "$language" "$file" "$cached"; then + source "$cached"; + fi +} + +nux.dsl.plan.file() { + local language="$1" + local file="$2"; + echo "$file${NUDSL_CACHE_SUFFIX}"; +} + +nux.dsl.plan() { + local language="$1"; + local file="$2"; + local cached="${3:-$file${NUDSL_CACHE_SUFFIX}}"; + if [ "$file" -ot "$cached" ]; then + nux.log debug No need to recompile. + return; + fi + + nux.log debug Needs regeneration, creating new version. + + local dirname=$(dirname "$cached") + mkdir -p "$dirname"; + local execution_plan=$(mktemp "$dirname/.nux.dsl.XXXX") + if (nux.dsl.process plan "$language" "$file" > "$execution_plan") ; then + mv -f "$execution_plan" "$cached"; + else + echo "Plan could not be generated. See errors." + rm "$execution_plan" + return -1; + fi +} + +nux.dsl.process.fail() { + process_failed=true + echo "$linenum:$@" >&2 +} + +nux.dsl.process0() { + local _gen_parser_pattern__unmatched='(.*)'; + local patterns="$_gen_parser_types _unmatched"; + local linenum=0; + while IFS= read -r line ; + do + let linenum=$linenum+1 + for t in $patterns; do + local pattern=_gen_parser_pattern_$t + if [[ "$line" =~ ${!pattern} ]]; then + .gen.parser.$t.process + break; + fi + done + if [ -n "$process_failed" ]; then + return -1; + fi + done; +} diff --git a/inc/nux/nuxsh.inc.sh b/inc/nux/nuxsh.inc.sh new file mode 100644 index 0000000..bc86807 --- /dev/null +++ b/inc/nux/nuxsh.inc.sh @@ -0,0 +1,209 @@ +nux.use nux/dsl + +nux.nuxsh.language.def() { + local identifier='[^ ;{}=()$]+' + local comment='(( *)(#.*))?' + local whitespace="([ ]*)" + local uarg="([^ #\{\}\"'\"'\"';]+)"; + local sarg="('\"'\"'[^'\"'\"']+'\"'\"')"; + local darg='("[^"]*")'; + + local args="((($uarg|$darg|$sarg) *)*)"; + + local prefixed_id="([^ :]*:)?($identifier)" + + .match.line() { + local type="$1"; + local match="^( *)$2$comment$"; + shift;shift; + .match "$type" "$match" indent "$@" - indent_comment comment; + } + + .match.line comment '' + .match.line rule "(@)([^ ]+)( +)$args?( *);?"\ + syntaxM rule indent2 args - - - - - indent3 syntax2 + + .match.line namespace_block_start "(@)(namespace)(( +)$uarg)( *)(\{)" \ + syntaxM keyword - indent2 identifier indent3 syntax3 + #.match.line namespace_start "@(namespace)( +)$uarg( *)(\{)" \ + # namespace indent2 args indent3 syntax + .match.line block_end '(\})' \ + syntax + + + .match.line if_start "(if)( +)$prefixed_id( +)$args?( *)(\{)" \ + keyword indent2 prefix identifier indent3 args - - - - - indent4 syntax3 + + .match.line function_start "((function)( +))($identifier)((\()|( *))(($identifier,? *)*)(\))?( *)(\{)" \ + - keyword indent2 identifier - syntax indent3 args - syntax2 indent4 syntax3 + + .match.line block_start "($identifier)(( +)$args)?( *)(\{)" \ + identifier - indent2 args - - - - - indent3 syntax3 + + .match.line statement "$prefixed_id(( +)$args)?( *)(;?)"\ + prefix identifier - indent2 args - - - - - indent3 syntax2 + + #.match.line variable "([^ ]+=)$args( *)(;?)"\ + # variable args - - - - - indent3 syntax + + .highlight rule cyan + .highlight syntaxM cyan + + .highlight prefix cyan + .highlight identifier green + .highlight keyword blue + .highlight args yellow + + .highlight comment magenta + + .highlight unmatched red + + .highlight syntax white + .highlight syntax2 white + .highlight syntax3 white + + blocktrac_root="#blocktrac_root" + _block_type[${#_block_type[@]}]="$blocktrac_root" + + function .block.get { + echo ${_block_type[${#_block_type[@]}-1]} + } + + function .block.pop { + unset _block_type[${#_block_type[@]}-1] + } + + function .block.push { + _block_type[${#_block_type[@]}]="$1" + } + + .match.block_start.plan() { + .block.push $identifier; + nux.exec.or .block.$identifier.start.plan .block.start.plan + } + + .match.block_end.plan() { + local identifier=$(.block.get) + if [ "$identifier" == "$blocktrac_root" ]; then + nux.dsl.process.fail "unnecessary block end '$line' " + return -1; + fi + nux.exec.or .block.$identifier.end.plan .block.end.plan + .block.pop; + } + + .action.alias() { + local alias=$1; shift; + echo "# alias: $alias $@"; + eval "_alias_$alias='$@'"; + } + + .action.prefix() { + echo "# prefix: $1 $2" + eval "_import_prefix_$1='$2'"; + } + + + .identifier() { + if [ -n "$prefix" ]; then + local var=_import_prefix_${prefix%:} + local prepend=${!var}; + if [ -z "$prepend" ] ; then + nudsl.process.fail "undefined prefix: $prefix"; + fi + echo "$prepend$identifier" + else + echo "$identifier" + fi + } + + + .match.statement.plan() { + echo "${indent}$(.identifier) ${args}" + } + + .match.rule.plan() { + eval ".action.${rule//:/.} $args"; + } + + .process.plan() { + echo "$line"; + } + + + .match.if_start.plan() { + .block.push lang.if; + echo "${indent}${keyword} $(.identifier) ${args} ; then" + } + + .block.lang.if.end.plan() { + echo "${indent}fi"; + } + + .match.namespace_block_start.plan() { + .block.push rule.namespace; + echo "# namespace $identifier" + _namespace="$identifier" + _import_prefix_="$identifier" + } + + .block.rule.namespace.end.plan() { + _namespace="" + _import_prefix_="" + echo "#namespace end" + } + + .match.function_start.plan() { + .block.push function + case $identifier in + .*) ;; + :*) identifier="$_namespace${identifier#:}" + esac; + echo "${indent}$identifier() {"; + for arg in ${args//,/ }; do + echo "${indent} local $arg="'"$1"'";shift;" + done + } + + .block.start.plan() { + case $identifier in + function) echo "$line";; + *"()") echo "$line";; + *) nudsl.process.fail Invalid block syntax: "'$identifier' '$line'"; + esac; + } + + .block.end.plan() { + .process.plan; + } + + .do.function.prefix() { + echo "${indent}function $1$args {" + } + + .action.block.rewrite.function.prefix() { + echo "# block:rewrite:function:prefix $@" + eval """.block.$1.start.plan() { + .do.function.prefix "$2" + } + """ + } + + .action.block.rewrite.call() { + echo "# block:rewrite:block:call $@" + eval """.block.$1.start.plan() { + echo \"\${indent}\"'${2}'\" \$args\"'${3}' + } + + .block.$1.end.plan() { + echo \"\${indent}\"'${4}' + } + """ + } +} + +function nux.nuxsh.use { + local file="$1"; + local cached="$2"; + nux.dsl.exec nux.nuxsh.language.def "$file" "$cached" +}