2 # SPDX-FileCopyrightText: 2025, 2026 Lady <https://www.ladys.computer/about/#lady>
3 # SPDX-License-Identifier: MPL-2.0
5 ## ⋯ 🧮🖍 Codemark ∷ sed ∷ SYNTAXES ∷ sh.sed
7 ## ⁌ Shell script syntax
9 ## ] Copyright © 2026 Lady [@ Ladys Computer].
11 ## ] This Source Code Form is subject to the terms of the Mozilla
12 ## ] Public License, version 2.0.
13 ## ] If a copy of the M·P·L was not distributed with this file, You can
14 ## ] obtain one at {🔗<https://mozilla.org/MPL/2.0/>}.
18 ## S·P·D·X copyright comments are made empty with·out replacement.
19 ## Rather, any copyright information should be expressed in prose.
26 ## Documentation comments begin with two hashes.
27 ## The hashes are removed and the next cycle is begun.
29 ## If the line which follows a documentation comment is blank, it is
30 ## kept that way rather than processed further.
40 ## The behaviour of remaining blank lines varies depending on the line
42 ## If it is not a documentation comment, a pipe is prepended.
51 ## We can now assume that the current line did not begin with two
52 ## hashes; it should be treated as code.
54 ## Lines which begin with (single) hashes are comments and not
57 s/^\( *\)\(#.*\)/|\1⟦\2⟧/
61 ## An initial and final space is added to make processing easier.
62 ## These will be removed at the end.
67 ## Variable defaults must follow a colon `:´ command and be specified
68 ## either as a string or as a reference to another variable.
71 s/"[$]{\([^:]*\):=\([^$"}]*\)}"/⟨"${⸤\1⸥:=\2}"⟩{@class="string"}/
72 s/"[$]{\([^:]*\):=\([$]{[^}]*}\)}"/⟨"${⸤\1⸥:=\2}"⟩{@class="string"}/
75 ## Control flow keywords such as `if´ or `for´ must begin their line.
76 ## They are handled upfront as they are easily recognized.
78 ## For `then´, `else´, and `do´, following them with the no·op command
79 ## `:´ is recommended as a matter of style, but not required.
81 /^ *if [^ ]/s/if \([^ ]*\)/⦃︎if⦄︎ ⦃︎\1⦄︎/
82 /^ *then [^ ]/s/then \([^ ]*\)/⦃then⦄︎ ⦃︎\1⦄︎/
83 /^ *else [^ ]/s/else \([^ ]*\)/⦃else⦄︎ ⦃︎\1⦄︎/
85 /^ *for [^ ][^ ]* in /s/for \([^ ]*\) in/⦃︎for⦄︎ ⸤†1⸥ ⦃︎in⦄︎/
86 /^ *do [^ ]/s/do \([^ ]*\)/⦃do⦄︎ ⦃︎\1⦄︎/
87 /^ *done /s/done/⦃&⦄︎/
89 ## It is not permitted to pipe into a control flow keyword.
90 ## So, after pipes, the script will jump here to begin processing.
94 ## When a line begins with the sequence `)"´, it is assumed to be
95 ## closing a subshell.
96 ## In this case, the script skips ahead to avoid recognizing this
97 ## sequence as a command.
101 ## The first word which is not a variable assignment is assumed to be
102 ## the name of a command, unless this line already contains markup
103 ## from control flow processing.
105 /⦃︎/!s/ \([^ =]\{1,\}\) / ⦃︎\1⦄︎ /
107 ## Variables must be processed in a loop because there can be multiple
109 ## However, they are only processed up to the first instance of `⦃︎´
110 ## markup (presumably inserted by the above rules).
113 /^\([^=]*\([^A-Za-z][^0-9A-Za-z_]*=\)*\)*⦃︎/!{
114 s/ \([A-Za-z][0-9A-Za-z_]*\)=/ ⸤\1⸥=/
115 / [A-Za-z][0-9A-Za-z_]*=/b variable
118 ## After backslashes (line continuations) or subshells, it is assumed
119 ## that the line is in the middle of a command.
120 ## By jumping here, the program skips variable and command name
125 ## After `&&´, `||´, and `|´, the following word is a command.
127 s/ \(&&\) \([^ ]*\) / \1 ⦃\2⦄︎ /
128 s/ || \([^ ]*\) / || ⦃\1⦄︎ /
129 s/ | \([^ ]*\) / | ⦃\1⦄︎ /
131 ## Some rules are enforced for strings :—
133 ## • When a string wraps a variable reference, it must have the form
134 ## `"${VARIABLE_NAME}"´ (the variable must be the only thing in the
137 ## • When a string wraps a subshell, the text of the subshell
138 ## invocation must be on its own line.
139 ## The string must not contain anything aside from the subshell; it
140 ## must begin `"$(´ and end `)"´.
142 ## • Strings consisting of only a straight single quote must be
145 ## • Otherwise, strings must be single‐quoted.
147 ## The shell syntax allows strings to be concatenated by placing them
148 ## directly adjacent to each other, so a variable reference and a
149 ## literal value may be joined together in this manner.
151 s/"'"/⟨"'"⟩{@class="string"}/g
152 s/"${\([^}]*\)}"/⟨"${\1}"⟩{@class="string"}/g
153 /^ *)"/s/)"/⟨)"⟩{@class="string"}/g
154 s/"$(/⟨"$(⟩{@class="string"}/g
155 s/\('[^']*'\)/⟨\1⟩{@class="string"}/g
157 ## The initial and final spaces added above can now be removed.
162 ## Finally, a `|´ can be prepended to mark the line as preformatted.
166 ## If the current line of code ends in a backslash or a pipe, the
167 ## following line is a continuation of it.
168 ## The `n´ command manually advances to the next line with·out starting
169 ## a new cycle, and the program then jumps to the correct place for
170 ## what might follow.