Skip to content

Commit

Permalink
Rework handling of assignment words (#176)
Browse files Browse the repository at this point in the history
  • Loading branch information
Niols authored Sep 17, 2023
2 parents a2853a7 + a52431c commit c750447
Show file tree
Hide file tree
Showing 22 changed files with 149 additions and 629 deletions.
1 change: 0 additions & 1 deletion src/CST.mli
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,6 @@ and word_cst = word_component list
and word_component =
| WordSubshell of subshell_kind * program located
| WordName of string
| WordAssignmentWord of assignment_word
| WordDoubleQuoted of word
| WordSingleQuoted of word
| WordTildePrefix of string
Expand Down
18 changes: 16 additions & 2 deletions src/CSTHelpers.ml
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ let unName (Name s) = s

let word_of_name (Name w) = Word (w, [WordName w])

let word_of_assignment_word (Name n, (Word (s, _) as w)) =
Word (n ^ "=" ^ s, [WordAssignmentWord (Name n, w)])
let word_of_assignment_word (Name name, Word (word_str, word_cst)) =
Word (name ^ "=" ^ word_str, WordLiteral (name ^ "=") :: word_cst)

let string_of_word (Word (s, _)) = s

Expand Down Expand Up @@ -230,3 +230,17 @@ let io_redirect_list_of_simple_command = function
io_redirect_list_of_cmd_suffix cmd_suffix'.value
| SimpleCommand_CmdName _ ->
[]

let merge_leading_literals =
let buf = Buffer.create 80 in
let rec extract_leading_literals = function
| WordLiteral lit :: rest ->
Buffer.add_string buf lit;
extract_leading_literals rest
| rest -> rest
in
fun word ->
let rest = extract_leading_literals word in
let lit = Buffer.contents buf in
Buffer.reset buf;
if lit = "" then word else WordLiteral lit :: rest
3 changes: 3 additions & 0 deletions src/CSTHelpers.mli
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,6 @@ val io_redirect_list_of_cmd_prefix : cmd_prefix -> io_redirect' list
val io_redirect_list_of_cmd_suffix : cmd_suffix -> io_redirect' list
val io_redirect_list_of_simple_command : simple_command -> io_redirect' list
val io_redirect_list_of_redirect_list : redirect_list -> io_redirect' list

(** Merges several leading [WordLiteral] into one. *)
val merge_leading_literals : word_cst -> word_cst
44 changes: 31 additions & 13 deletions src/assignment.ml
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,36 @@ open Name
*)

let recognize_assignment checkpoint pretoken word_cst = FirstSuccessMonad.(
let recognize_assignment checkpoint pretoken (Word (word_str, word_cst)) =
FirstSuccessMonad.(
match word_cst with
| [WordAssignmentWord ((Name n) as name, w)] ->
if is_name n then
let (_, pstart, pstop) = pretoken in
let token = ASSIGNMENT_WORD (name, w) in
if accepted_token checkpoint (token, pstart, pstop) <> Wrong then
return token
else
fail
else
fail
| _ ->
fail
| WordLiteral literal :: word_cst_leftover ->
(
match String.index_opt literal '=' with
| None | Some 0 -> fail
| Some i ->
let name = String.sub literal 0 i in
if is_name name then
let (_, pstart, pstop) = pretoken in
let literal_leftover = String.sub literal (i + 1) (String.length literal - i - 1) in
let word_str_leftover = String.sub word_str (i + 1) (String.length word_str - i - 1) in
let word_cst =
if literal_leftover = "" then
word_cst_leftover
else
WordLiteral literal_leftover :: word_cst_leftover
in
(* Now that we know we're in an assignment, we know there are
potentially more tilde prefixes to recognize. *)
let word_cst = TildePrefix.recognize ~in_assignment:true word_cst in
let word = Word (word_str_leftover, word_cst) in
let token = ASSIGNMENT_WORD (Name name, word) in
if accepted_token checkpoint (token, pstart, pstop) <> Wrong then
return token
else
fail
else
fail
)
| _ -> fail
)
2 changes: 1 addition & 1 deletion src/engine.ml
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ module Lexer (U : sig end) : Lexer = struct
| _ -> false
in
let token = FirstSuccessMonad.(
(recognize_assignment checkpoint p cst)
(recognize_assignment checkpoint p (Word (w0, cst)))
+> (recognize_reserved_word_if_relevant
well_delimited_keyword checkpoint p w)
+> return word
Expand Down
48 changes: 4 additions & 44 deletions src/prelexerState.ml
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ let string_of_word (Word (s, _)) = s

let string_of_attribute = function
| NoAttribute -> ""
| ParameterLength -> "#"
| ParameterLength -> "#"
| UseDefaultValues (p, w) -> p ^ string_of_word w
| AssignDefaultValues (p, w) -> p ^ string_of_word w
| IndicateErrorifNullorUnset (p, w) -> p ^ string_of_word w
Expand Down Expand Up @@ -287,46 +287,6 @@ let is_assignment_mark = function
| AssignmentMark -> true
| _ -> false

let recognize_assignment current =
let rhs, prefix = take_until is_assignment_mark (buffer current) in
if prefix = buffer current then (
current
) else
let buffer = AtomBuffer.make (rhs @ List.tl prefix) in
let current' = { current with buffer } in
match prefix with
| AssignmentMark :: WordComponent (s, _) :: prefix ->
assert (s.[String.length s - 1] = '='); (* By after_equal unique call. *)
(* [s] is a valid name. We have an assignment here. *)
let lhs = try String.(sub s 0 (length s - 1)) with _ -> assert false in

(* FIXME: The following check could be done directly with
ocamllex rules, right?*)

if Name.is_name lhs then (
let rhs_string = contents_of_atom_list rhs in
let crhs = components_of_atom_list rhs in
let cst = WordComponent (
s ^ rhs_string,
WordAssignmentWord (Name lhs, Word (rhs_string, crhs)))
in
let buffer = AtomBuffer.make (cst :: prefix) in
{ current with buffer }
) else
(*
If [lhs] is not a name, then the corresponding word
literal must be merged with the preceding one, if it exists.
*) (
begin match List.rev rhs with
| WordComponent (s_rhs, WordLiteral s_rhs') :: rev_rhs ->
let word = WordComponent (s ^ s_rhs, WordLiteral (s ^ s_rhs')) in
let buffer = AtomBuffer.make (List.rev rev_rhs @ word :: prefix) in
{ current with buffer }
| _ ->
current'
end)
| _ -> current'

(** [(return ?with_newline lexbuf current tokens)] returns a list of
pretokens consisting of, in that order:
Expand All @@ -349,8 +309,6 @@ let return ?(with_newline=false) lexbuf (current : prelexer_state) tokens =
not (List.exists (function (Pretoken.PreWord _)->true |_-> false) tokens)
);

let current = recognize_assignment current in

let flush_word b =
let buf = Buffer.create 13 in
List.iter (function WordComponent (s, _) -> Buffer.add_string buf s
Expand Down Expand Up @@ -408,7 +366,9 @@ let return ?(with_newline=false) lexbuf (current : prelexer_state) tokens =
| QuotingMark _ -> []
) (buffer current)))
in
let csts = TildePrefix.recognize csts in
(* At this point, we cannot say whether this will be an assignment word,
so we do a minimal tilde prefixes recognition. *)
let csts = TildePrefix.recognize ~in_assignment:false csts in
[Pretoken.PreWord (w, csts)]
in
let tokens = if with_newline then tokens @ [Pretoken.NEWLINE] else tokens in
Expand Down
27 changes: 5 additions & 22 deletions src/tildePrefix.ml
Original file line number Diff line number Diff line change
Expand Up @@ -61,25 +61,10 @@ let extract_tilde_prefix_from_literal (literal : string) : word_cst =
let (first, rest) = ExtPervasives.string_split i literal in
[WordTildePrefix (strip_tilde first); WordLiteral rest]

(** Merges several leading [WordLiteral] into one. *)
let merge_leading_literals : word_cst -> word_cst =
let buf = Buffer.create 80 in
let rec extract_leading_literals = function
| WordLiteral lit :: rest ->
Buffer.add_string buf lit;
extract_leading_literals rest
| rest -> rest
in
fun word ->
let rest = extract_leading_literals word in
let lit = Buffer.contents buf in
Buffer.reset buf;
if lit = "" then word else WordLiteral lit :: rest

(** Extracts the tilde-prefix at the beginning of the given word CST if there is
one. Otherwise, returns the word as-is. *)
let extract_tilde_prefix_from_word_if_present (word : word_cst) : word_cst =
match merge_leading_literals word with
match CSTHelpers.merge_leading_literals word with
| WordLiteral literal :: word when starts_with_tilde literal ->
extract_tilde_prefix_from_literal literal @ word
| word -> word
Expand Down Expand Up @@ -136,12 +121,10 @@ let rec concat_words_with_colon (words : word_cst list) : word_cst =
(** Recognises tilde prefixes in a word, that is recognises eg. [WordLiteral
"~foo"] and replaces it by [WordTildePrefix "foo"] when in the right
position. *)
let recognize (word : word_cst) =
match word with
| [WordAssignmentWord (name, Word (s, word))] ->
let recognize ~in_assignment (word : word_cst) =
if in_assignment then
let words = split_word_on_colon word in
let words = List.map extract_tilde_prefix_from_word_if_present words in
let word = concat_words_with_colon words in
[WordAssignmentWord (name, Word (s, word))]
| _ ->
concat_words_with_colon words
else
extract_tilde_prefix_from_word_if_present word
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,8 @@
"rename=alias",
[
[
"WordAssignmentWord",
[
[
"Name",
"rename"
],
[
"Word",
"alias",
[
[
"WordLiteral",
"alias"
]
]
]
]
"WordLiteral",
"rename=alias"
]
]
]
Expand Down Expand Up @@ -109,23 +94,8 @@
"time=date",
[
[
"WordAssignmentWord",
[
[
"Name",
"time"
],
[
"Word",
"date",
[
[
"WordLiteral",
"date"
]
]
]
]
"WordLiteral",
"time=date"
]
]
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,8 @@
"show=echo",
[
[
"WordAssignmentWord",
[
[
"Name",
"show"
],
[
"Word",
"echo",
[
[
"WordLiteral",
"echo"
]
]
]
]
"WordLiteral",
"show=echo"
]
]
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,18 @@
"ls='ls -h -t -l'",
[
[
"WordAssignmentWord",
"WordLiteral",
"ls="
],
[
"WordSingleQuoted",
[
"Word",
"ls -h -t -l",
[
"Name",
"ls"
],
[
"Word",
"'ls -h -t -l'",
[
[
"WordSingleQuoted",
[
"Word",
"ls -h -t -l",
[
[
"WordLiteral",
"ls -h -t -l"
]
]
]
]
"WordLiteral",
"ls -h -t -l"
]
]
]
Expand Down
Loading

0 comments on commit c750447

Please sign in to comment.